Compare commits
410 Commits
kubernetes
...
master
Author | SHA1 | Date |
---|---|---|
|
2da25b2322 | |
|
110ba5998e | |
|
716737e46a | |
|
8a11631ed9 | |
|
330f86df8e | |
|
55101ca654 | |
|
ab62ac8cf1 | |
|
af5ade99d8 | |
|
4d27286e9c | |
|
9130183f39 | |
|
e808dbbf1f | |
|
579a7a5a35 | |
|
0b4adb247f | |
|
49afb3c466 | |
|
17c0dde6b1 | |
|
bc4c094b08 | |
|
800afb48a7 | |
|
2375a3a9f6 | |
|
18f24e791d | |
|
9c13527bac | |
|
17bb82b84d | |
|
abe43f6e92 | |
|
bb9c5182ea | |
|
4ee16d2b51 | |
|
46d6f63709 | |
|
8500d2979d | |
|
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 | |
|
dc53668bca | |
|
399c585899 | |
|
ece3c8c1d0 | |
|
5b96de1a99 | |
|
5366de04e1 | |
|
7577f36fbc | |
|
f38b1de6c2 | |
|
0d5516dcdc | |
|
5cbdedb625 | |
|
6203603c4a | |
|
f97d1f5267 | |
|
90ba96e4f3 | |
|
ed22800c0c | |
|
920f676703 | |
|
88b66ece35 | |
|
5e1a193aef | |
|
e4ccb76c1a | |
|
277c37ab57 | |
|
2bbe460b18 | |
|
f630fabade | |
|
a108757aa8 | |
|
8c173253d9 | |
|
deef152435 | |
|
cfdaf20447 | |
|
8883001c90 | |
|
24bba46b9d | |
|
cab51f793f | |
|
a9d88dcafc | |
|
561b18e2ed | |
|
594231109f | |
|
1fe7110fa9 | |
|
3a69c59961 | |
|
246e544fc4 | |
|
ef54a58dd7 | |
|
71aa003817 | |
|
4ccb6a52b9 | |
|
480491c908 | |
|
f6d73c948b | |
|
f1e93ac324 | |
|
7f2f474232 | |
|
2e38fc2204 | |
|
2fd8e3617a | |
|
313e838cfa | |
|
79fb20cb8b | |
|
316ed014ab | |
|
d74f0e1af1 | |
|
1ab40ed1c8 | |
|
81562142de | |
|
bcafb59748 | |
|
8af785f4be | |
|
064a840924 | |
|
7346aee2d4 | |
|
d384e79d1a | |
|
7d49790b11 | |
|
7f667b45ca | |
|
cee2a595e4 | |
|
3e68cbc2c4 | |
|
6990be915a | |
|
1613733931 | |
|
be364695f5 | |
|
ddd9a046e7 | |
|
a8a00dbee1 | |
|
120105a0a9 | |
|
088003e485 | |
|
816d854fc8 | |
|
1a5fd321ec | |
|
575c21a9f0 | |
|
94246703f3 | |
|
74629f681a | |
|
86048014d6 | |
|
56c9d286fe | |
|
92bb3cdeda | |
|
beeec3d7a0 | |
|
40fde549ca | |
|
ee2ac712ef | |
|
e26c49a5f9 | |
|
9f75b795dc | |
|
348f12d905 | |
|
61dc2ecb38 | |
|
e2968fb8ad | |
|
608a0553d7 | |
|
169a952417 | |
|
d0bc9691f3 | |
|
133d258d8e | |
|
1811ebafa9 | |
|
7ff7f80add | |
|
6e4fe32a45 | |
|
9a565d149e | |
|
1f9df3421a | |
|
2bb31e1a0f | |
|
2cccb2df6a | |
|
4ced6667a4 | |
|
b7988e1775 | |
|
2ebf801b89 | |
|
b22c40d47c | |
|
4b9836ac7a | |
|
913dcdf388 | |
|
e91a2f1400 | |
|
2fceb953a8 | |
|
dc24746c3a | |
|
6989e5df8d | |
|
739aca7b89 | |
|
7ec5989b2d | |
|
c5abf9ddc7 | |
|
a886863a93 | |
|
4532c53760 | |
|
3cd67e0222 | |
|
69f03b081e | |
|
b67296a41b | |
|
3553001de4 | |
|
c7e911a6b5 | |
|
bb485d40d9 | |
|
dc6975b78c | |
|
af49e676b2 | |
|
a499023358 | |
|
1014b1d5f2 | |
|
816c382177 | |
|
4c8c153513 | |
|
484ede079f | |
|
73f8c2980c | |
|
87fbd1d830 | |
|
f829c4b789 | |
|
1b29da9296 | |
|
80dc149179 | |
|
84e8e731c9 | |
|
c1f9faf7c3 | |
|
c90e4a0fd8 | |
|
86e0d9e48c | |
|
3aac470db0 | |
|
1d2b08a308 | |
|
55b0096f22 | |
|
fb82836788 | |
|
9e16ef8eda | |
|
fa1f5fc526 | |
|
a0488cfbe5 | |
|
184746c687 | |
|
7ed2361d8a | |
|
983dddad9d | |
|
2bb291358c | |
|
7c89fa5dca | |
|
0f462824c7 | |
|
c329ccff7c | |
|
4106fc8791 | |
|
f3a7f811d0 | |
|
5f5894cd61 | |
|
867c9194ad | |
|
4315ad9aaa | |
|
36d3a02b4d | |
|
d6ad8b12b7 | |
|
4d5b682af1 | |
|
5d2e9155d4 | |
|
e4a11c9548 | |
|
c0e80201dc | |
|
6c52e7fa5d | |
|
bbfe64c1ac | |
|
f92e952318 | |
|
cfb4ff091f | |
|
a0d920bb63 | |
|
20877bc489 | |
|
5e52c27b4e | |
|
90452b2e92 | |
|
650e81b1f2 | |
|
262825a8a6 | |
|
fee9d648a7 | |
|
1945b46b05 | |
|
36d480f43c | |
|
3aa701d129 | |
|
111ebd712e | |
|
11efae9ae4 | |
|
9ca6564d24 | |
|
e13117fcac | |
|
57edd7ccc7 | |
|
7640fec64e | |
|
b2a7d2626d | |
|
9a0e211708 | |
|
dca3776905 | |
|
14f6a11dd8 | |
|
b0a9adb1c6 | |
|
9035822a02 | |
|
c3d194bcd2 | |
|
b06b2ef141 | |
|
e1b9b58b72 | |
|
c4be63c54b | |
|
bd15ba4612 | |
|
4a18df8277 | |
|
2c705c6f91 | |
|
823a551020 | |
|
d80cbcdb4d | |
|
cf5c214b25 | |
|
c63fb82d09 | |
|
ee337ad2d6 | |
|
03a826a301 | |
|
4fe61440d3 | |
|
3f1b31120b | |
|
8f2f49349b | |
|
619e8f9f0d | |
|
acdf12bd01 | |
|
2e31adb070 | |
|
05a8bf872e | |
|
b315eb8455 | |
|
2c588bc5ed | |
|
b4d17b87f5 | |
|
60dccb9d13 | |
|
072e9981f1 | |
|
d0ed078ea5 | |
|
58672617cf | |
|
15aefcb085 | |
|
eaa3175fb1 | |
|
78100b683e | |
|
df17d35554 | |
|
a6de79e3d4 | |
|
a421f02b7c | |
|
07726d29e8 | |
|
68dbd0767a | |
|
1c0cdd03d9 | |
|
5eeba3a134 | |
|
1a58d7f437 | |
|
b3015b8e3e | |
|
b2d7179a03 | |
|
69ef2e6a97 | |
|
172bb858a6 | |
|
2f6a8aff11 | |
|
980dabe2f2 | |
|
3b755991af | |
|
582c9dabb0 | |
|
e91710120d | |
|
770b201e9c | |
|
dcb1192020 | |
|
42f728c3d6 | |
|
872eb0883e | |
|
c5396bb9d0 | |
|
a9fe9eb5f7 | |
|
269ced5ce8 | |
|
6c46eadd33 | |
|
7e6366765e | |
|
d5a8f05803 | |
|
3f7d004bdf | |
|
f508e12184 | |
|
6921b6008e | |
|
030f865b1b | |
|
d187b13731 | |
|
8a8cd02be0 | |
|
97bc4a2eb7 | |
|
d087fa13f4 | |
|
e799af9379 | |
|
13611d4701 | |
|
3802ec81a2 | |
|
ce7ca659e3 | |
|
0b359223b4 | |
|
041879def4 | |
|
cefe6cb0a0 | |
|
157dc2e404 | |
|
81ad0ffc39 | |
|
209732ca71 | |
|
25c985cf66 | |
|
4657409ea3 | |
|
290ff2d971 | |
|
f74c97d4ab | |
|
21275cf4d0 | |
|
b57bb4f06e | |
|
f183fdb3b3 | |
|
6b4730ae70 | |
|
79f145bee1 | |
|
24a31a929b | |
|
d3ad753245 | |
|
627b5dabb0 | |
|
382ad8ff11 | |
|
e06e0c6134 | |
|
ff551a76e3 | |
|
e5da5e005a | |
|
5b2540dc63 | |
|
dec98f7709 | |
|
b56ea4761e | |
|
8941dd6b94 | |
|
85119a1e56 | |
|
fe3be32c18 | |
|
0bd9881b18 | |
|
df7520c1f3 | |
|
7d69e0104e | |
|
a4551ef19a | |
|
55821cad07 | |
|
b31dce3246 | |
|
8d8b5a6120 | |
|
5b7c8b24b4 | |
|
bfb7f4aa02 | |
|
2e9d71e05d | |
|
b9d568d7e4 | |
|
5450795bac | |
|
514f46729f | |
|
8288417631 | |
|
57112bd241 | |
|
b1dfef4f42 | |
|
0f357874d3 | |
|
f825bd0b72 | |
|
6985b81ab4 | |
|
5f33246cf8 | |
|
649e09882b | |
|
5aca4630e4 | |
|
771a13a844 | |
|
5ff591adc6 | |
|
3d99e3ab7e | |
|
c572c1d825 | |
|
4f380d07c5 | |
|
801b2f2907 | |
|
150571ea52 | |
|
4196229029 | |
|
852454f7f7 | |
|
1cb4e5b9e2 | |
|
d0e936c703 | |
|
94aa535f7b | |
|
41b0711fd3 | |
|
32e71fd995 | |
|
f0276dce1a | |
|
04bb64c802 | |
|
389b2eeaf6 | |
|
54dfe4c559 | |
|
b465426392 | |
|
adfcfcf7c1 | |
|
25dbaefaa9 | |
|
eded7a36b1 | |
|
426055bedd | |
|
0140ee6944 | |
|
bea8dbbff9 | |
|
41e94233a1 | |
|
31199ade16 | |
|
3e85d33843 | |
|
eb98199dbe | |
|
64300085bb | |
|
f8b298459c | |
|
10fe45a704 | |
|
6c2c451048 | |
|
2f762a69a2 | |
|
713b7e79c0 | |
|
6b5d9dfa70 | |
|
9957529f9c | |
|
916a6ea031 | |
|
64a1fe556b | |
|
9a6ad3e08e | |
|
733ee4d85e | |
|
7f19a8d4f7 | |
|
1d05cd7a4e | |
|
0ee0d225f2 |
|
@ -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
|
||||
|
|
2
doc.go
2
doc.go
|
@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kubectl // import "k8s.io/kubectl"
|
||||
package kubectl
|
||||
|
|
127
go.mod
127
go.mod
|
@ -2,106 +2,93 @@
|
|||
|
||||
module k8s.io/kubectl
|
||||
|
||||
go 1.22.0
|
||||
go 1.24.0
|
||||
|
||||
godebug default=go1.24
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/chai2010/gettext-go v1.0.2
|
||||
github.com/daviddengcn/go-colortext v1.0.0
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f
|
||||
github.com/fatih/camelcase v1.0.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/go-openapi/jsonreference v0.20.2
|
||||
github.com/google/gnostic-models v0.6.8
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/jonboulle/clockwork v0.2.2
|
||||
github.com/google/gnostic-models v0.6.9
|
||||
github.com/google/go-cmp v0.7.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.0.0-20221205130635-1aeaba878587
|
||||
github.com/onsi/ginkgo/v2 v2.15.0
|
||||
github.com/onsi/gomega v1.31.0
|
||||
github.com/pkg/errors v0.9.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/russross/blackfriday/v2 v2.1.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/sys v0.17.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.0.0-20240305044759-dd2e75089b33
|
||||
k8s.io/apimachinery v0.0.0-20240305011844-67cb3a878cd3
|
||||
k8s.io/cli-runtime v0.0.0-20240301215537-eeedba799671
|
||||
k8s.io/client-go v0.0.0-20240305045158-64334495a6f0
|
||||
k8s.io/component-base v0.0.0-20240301210028-15d726cdca18
|
||||
k8s.io/component-helpers v0.0.0-20240301210226-e6e24ac471b0
|
||||
k8s.io/klog/v2 v2.120.1
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
|
||||
k8s.io/metrics v0.0.0-20240301215215-36af2eaa1e6d
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/sys v0.31.0
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0
|
||||
k8s.io/api v0.0.0-20250612195650-7efafe3627c8
|
||||
k8s.io/apimachinery v0.0.0-20250612195403-e0270fe44c97
|
||||
k8s.io/cli-runtime v0.0.0-20250612204029-ea98d716c955
|
||||
k8s.io/client-go v0.0.0-20250612200049-4e82e684120e
|
||||
k8s.io/component-base v0.0.0-20250612201519-d0c00e6471f7
|
||||
k8s.io/component-helpers v0.0.0-20250612201654-2b90e129ba7a
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/metrics v0.0.0-20250612203843-e8073a5e5ba0
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
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.8.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // 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.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.10.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/term v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // 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
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20240305044759-dd2e75089b33
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20240305011844-67cb3a878cd3
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20240301215537-eeedba799671
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20240305045158-64334495a6f0
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20240301205840-f8417dff616b
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20240301210028-15d726cdca18
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20240301210226-e6e24ac471b0
|
||||
k8s.io/metrics => k8s.io/metrics v0.0.0-20240301215215-36af2eaa1e6d
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
)
|
||||
|
|
312
go.sum
312
go.sum
|
@ -1,109 +1,68 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/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/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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/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/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/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=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE=
|
||||
github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/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/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
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-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
|
||||
github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A=
|
||||
github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE=
|
||||
github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ=
|
||||
github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
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/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.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
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=
|
||||
|
@ -125,25 +84,26 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
|||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-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=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
||||
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
|
||||
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
|
@ -152,163 +112,123 @@ 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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
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.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
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/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.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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20240305044759-dd2e75089b33 h1:t4rX/ZVL/5i5E/Z+kQFnCkqRn4Ni6vf+y7M399XPmSM=
|
||||
k8s.io/api v0.0.0-20240305044759-dd2e75089b33/go.mod h1:vubRQCvGk3ZRs8VJewbBCTFfaSYNGtDSO4rI+Ur8d0M=
|
||||
k8s.io/apimachinery v0.0.0-20240305011844-67cb3a878cd3 h1:OQnlxechgbZid21Q0KyffHDuHHZz+G7RTGDA3SIkC+o=
|
||||
k8s.io/apimachinery v0.0.0-20240305011844-67cb3a878cd3/go.mod h1:qPsrq6INURDMMgqxK78MEuC8GzI1f2oHvfHzg5ZOa6s=
|
||||
k8s.io/cli-runtime v0.0.0-20240301215537-eeedba799671 h1:kGyGe23DZ0cF3EkTk/+U9/yvFNKLuwiKMMIdFlgXyak=
|
||||
k8s.io/cli-runtime v0.0.0-20240301215537-eeedba799671/go.mod h1:SzEB7/qbLFBQ32B8zlKrnbU9oxTxKuMjyDsNroi2wnA=
|
||||
k8s.io/client-go v0.0.0-20240305045158-64334495a6f0 h1:GPra71+5jYpURLYNJrvhcJuBc+WuuXcChI+aUGyi2Kw=
|
||||
k8s.io/client-go v0.0.0-20240305045158-64334495a6f0/go.mod h1:v+mRdwGXf74Ta3x5vOAqajn7JsX93/+WlQR0KE/EuL8=
|
||||
k8s.io/component-base v0.0.0-20240301210028-15d726cdca18 h1:kiqSkGxkfImyPIdgBwK6mLoS8yLDijT7wKm35LduYLs=
|
||||
k8s.io/component-base v0.0.0-20240301210028-15d726cdca18/go.mod h1:ovtVM/EGyY/M89mMKkFZ3tdQREOE2u9pODk7+C6VENQ=
|
||||
k8s.io/component-helpers v0.0.0-20240301210226-e6e24ac471b0 h1:Zlo5ysZBa3gDwE/zDiinJfH4O6yY43lCGpWRD3dmTgc=
|
||||
k8s.io/component-helpers v0.0.0-20240301210226-e6e24ac471b0/go.mod h1:MyIpmc2hdnH3LLnX5Rss7MhDdh6tg69DZLU/XsrA3z8=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/metrics v0.0.0-20240301215215-36af2eaa1e6d h1:YtmAAJAxUsuxeEHjuzjUYq5bQw4l51mwFts54Xf4Z9A=
|
||||
k8s.io/metrics v0.0.0-20240301215215-36af2eaa1e6d/go.mod h1:OXISdzD18L/I8IAHUq0094fOFSEkzCjenaSRZLEnYRc=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 h1:vq2TtoDcQomhy7OxXLUOzSbHMuMYq0Bjn93cDtJEdKw=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3/go.mod h1:/d88dHCvoy7d0AKFT0yytezSGZKjsZBVs9YTkBHSGFk=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
k8s.io/api v0.0.0-20250612195650-7efafe3627c8 h1:K1AnJQBQTKLy2C/up2YSFuuQ+OBucYGcDCBO2cafjlQ=
|
||||
k8s.io/api v0.0.0-20250612195650-7efafe3627c8/go.mod h1:+9QbMyXTXctHAXg3fdhJbuZgyzhYgprCn43M5NqoJzw=
|
||||
k8s.io/apimachinery v0.0.0-20250612195403-e0270fe44c97 h1:h2og30eGCCk1GOEZK6+LNhhlydDcWY3wJaWDIs05xR8=
|
||||
k8s.io/apimachinery v0.0.0-20250612195403-e0270fe44c97/go.mod h1:EZ7eIfFAwky7ktmG4Pu9XWxBxFG++4dxPDOM0GL3abw=
|
||||
k8s.io/cli-runtime v0.0.0-20250612204029-ea98d716c955 h1:/vNS33lahptlYENPecDoe4fD8SOE+aGTxP5wozvK7zw=
|
||||
k8s.io/cli-runtime v0.0.0-20250612204029-ea98d716c955/go.mod h1:Exo7hMRapEHo/QMSGxd1tqaGuZBQpgGZt7Sintr5L/M=
|
||||
k8s.io/client-go v0.0.0-20250612200049-4e82e684120e h1:xoSxEgTvcAD7YG46B6RN1yZx5KhF0YKNe4SSoY+qSQA=
|
||||
k8s.io/client-go v0.0.0-20250612200049-4e82e684120e/go.mod h1:hktzpPyrdfB1WrXOvdnDayNSrngzEwWjiwTGqq6Zjns=
|
||||
k8s.io/component-base v0.0.0-20250612201519-d0c00e6471f7 h1:PmR3IJeL8qbnqdH70lmCLxZjHFr+Cbz5v6VY6ZFlMsI=
|
||||
k8s.io/component-base v0.0.0-20250612201519-d0c00e6471f7/go.mod h1:eMJvxKozNu3AbHhH6mWUJbzNhElacCbAilLeMjxIW5k=
|
||||
k8s.io/component-helpers v0.0.0-20250612201654-2b90e129ba7a h1:AccUaO5Y8Z/rRivu4l0eqZbocGbxHGqiuo19dc0vcPw=
|
||||
k8s.io/component-helpers v0.0.0-20250612201654-2b90e129ba7a/go.mod h1:5Grn35PpLsSHt+WUrQbXI2QN85eBCWnH5nx4fGBbYYU=
|
||||
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/metrics v0.0.0-20250612203843-e8073a5e5ba0 h1:Eo70T+FEfbC83+3nBRAqcUNQf/YnGlkCYj+c1q+lldY=
|
||||
k8s.io/metrics v0.0.0-20250612203843-e8073a5e5ba0/go.mod h1:wyXjSOSPspEM73pO1WXNVAafLtPfWHMnRPgVrX57niY=
|
||||
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-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
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/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.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.7.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=
|
||||
|
|
|
@ -21,8 +21,8 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/spf13/cobra"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
|
|
@ -123,11 +123,11 @@ func NewCmdAPIResources(restClientGetter genericclioptions.RESTClientGetter, ioS
|
|||
|
||||
// Validate checks to the APIResourceOptions to see if there is sufficient information run the command
|
||||
func (o *APIResourceOptions) Validate() error {
|
||||
supportedOutputTypes := sets.NewString("", "wide", "name")
|
||||
supportedOutputTypes := sets.New[string]("", "wide", "name")
|
||||
if !supportedOutputTypes.Has(o.Output) {
|
||||
return fmt.Errorf("--output %v is not available", o.Output)
|
||||
}
|
||||
supportedSortTypes := sets.NewString("", "name", "kind")
|
||||
supportedSortTypes := sets.New[string]("", "name", "kind")
|
||||
if len(o.SortBy) > 0 {
|
||||
if !supportedSortTypes.Has(o.SortBy) {
|
||||
return fmt.Errorf("--sort-by accepts only name or kind")
|
||||
|
@ -193,11 +193,11 @@ func (o *APIResourceOptions) RunAPIResources() error {
|
|||
continue
|
||||
}
|
||||
// filter to resources that support the specified verbs
|
||||
if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
|
||||
if len(o.Verbs) > 0 && !sets.New[string](resource.Verbs...).HasAll(o.Verbs...) {
|
||||
continue
|
||||
}
|
||||
// filter to resources that belong to the specified categories
|
||||
if len(o.Categories) > 0 && !sets.NewString(resource.Categories...).HasAll(o.Categories...) {
|
||||
if len(o.Categories) > 0 && !sets.New[string](resource.Categories...).HasAll(o.Categories...) {
|
||||
continue
|
||||
}
|
||||
resources = append(resources, groupResource{
|
||||
|
|
|
@ -43,7 +43,7 @@ import (
|
|||
"k8s.io/client-go/util/csaupgrade"
|
||||
"k8s.io/component-base/version"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/cmd/delete"
|
||||
cmddelete "k8s.io/kubectl/pkg/cmd/delete"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
@ -61,7 +61,7 @@ type ApplyFlags struct {
|
|||
RecordFlags *genericclioptions.RecordFlags
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
DeleteFlags *delete.DeleteFlags
|
||||
DeleteFlags *cmddelete.DeleteFlags
|
||||
|
||||
FieldManager string
|
||||
Selector string
|
||||
|
@ -71,6 +71,7 @@ type ApplyFlags struct {
|
|||
All bool
|
||||
Overwrite bool
|
||||
OpenAPIPatch bool
|
||||
Subresource string
|
||||
|
||||
PruneAllowlist []string
|
||||
|
||||
|
@ -84,7 +85,7 @@ type ApplyOptions struct {
|
|||
PrintFlags *genericclioptions.PrintFlags
|
||||
ToPrinter func(string) (printers.ResourcePrinter, error)
|
||||
|
||||
DeleteOptions *delete.DeleteOptions
|
||||
DeleteOptions *cmddelete.DeleteOptions
|
||||
|
||||
ServerSideApply bool
|
||||
ForceConflicts bool
|
||||
|
@ -97,6 +98,7 @@ type ApplyOptions struct {
|
|||
All bool
|
||||
Overwrite bool
|
||||
OpenAPIPatch bool
|
||||
Subresource string
|
||||
|
||||
ValidationDirective string
|
||||
Validator validation.Schema
|
||||
|
@ -182,7 +184,7 @@ var ApplySetToolVersion = version.Get().GitVersion
|
|||
func NewApplyFlags(streams genericiooptions.IOStreams) *ApplyFlags {
|
||||
return &ApplyFlags{
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
DeleteFlags: delete.NewDeleteFlags("The files that contain the configurations to apply."),
|
||||
DeleteFlags: cmddelete.NewDeleteFlags("The files that contain the configurations to apply."),
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
|
||||
Overwrite: true,
|
||||
|
@ -235,6 +237,7 @@ func (flags *ApplyFlags) AddFlags(cmd *cobra.Command) {
|
|||
cmdutil.AddPruningFlags(cmd, &flags.Prune, &flags.PruneAllowlist, &flags.All, &flags.ApplySetRef)
|
||||
cmd.Flags().BoolVar(&flags.Overwrite, "overwrite", flags.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
|
||||
cmd.Flags().BoolVar(&flags.OpenAPIPatch, "openapi-patch", flags.OpenAPIPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
|
||||
cmdutil.AddSubresourceFlags(cmd, &flags.Subresource, "If specified, apply will operate on the subresource of the requested object. Only allowed when using --server-side.")
|
||||
}
|
||||
|
||||
// ToOptions converts from CLI inputs to runtime inputs
|
||||
|
@ -356,6 +359,7 @@ func (flags *ApplyFlags) ToOptions(f cmdutil.Factory, cmd *cobra.Command, baseNa
|
|||
All: flags.All,
|
||||
Overwrite: flags.Overwrite,
|
||||
OpenAPIPatch: flags.OpenAPIPatch,
|
||||
Subresource: flags.Subresource,
|
||||
|
||||
Recorder: recorder,
|
||||
Namespace: namespace,
|
||||
|
@ -438,6 +442,9 @@ func (o *ApplyOptions) Validate() error {
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(o.Subresource) > 0 && !o.ServerSideApply {
|
||||
return fmt.Errorf("--subresource can only be specified for --server-side")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -577,13 +584,15 @@ func (o *ApplyOptions) applyOneObject(info *resource.Info) error {
|
|||
options := metav1.PatchOptions{
|
||||
Force: &o.ForceConflicts,
|
||||
}
|
||||
obj, err := helper.Patch(
|
||||
info.Namespace,
|
||||
info.Name,
|
||||
types.ApplyPatchType,
|
||||
data,
|
||||
&options,
|
||||
)
|
||||
obj, err := helper.
|
||||
WithSubresource(o.Subresource).
|
||||
Patch(
|
||||
info.Namespace,
|
||||
info.Name,
|
||||
types.ApplyPatchType,
|
||||
data,
|
||||
&options,
|
||||
)
|
||||
if err != nil {
|
||||
if isIncompatibleServerError(err) {
|
||||
err = fmt.Errorf("Server-side apply not available on the server: (%v)", err)
|
||||
|
@ -681,6 +690,12 @@ See https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts`
|
|||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
|
||||
// prune nulls when client-side apply does a create to match what will happen when client-side applying an update.
|
||||
// do this after CreateApplyAnnotation so the annotation matches what will be persisted on an update apply of the same manifest.
|
||||
if u, ok := info.Object.(runtime.Unstructured); ok {
|
||||
pruneNullsFromMap(u.UnstructuredContent())
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
// Then create the resource and skip the three-way merge
|
||||
obj, err := helper.Create(info.Namespace, true, info.Object)
|
||||
|
@ -759,6 +774,29 @@ See https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts`
|
|||
return nil
|
||||
}
|
||||
|
||||
func pruneNullsFromMap(data map[string]interface{}) {
|
||||
for k, v := range data {
|
||||
if v == nil {
|
||||
delete(data, k)
|
||||
} else {
|
||||
pruneNulls(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
func pruneNullsFromSlice(data []interface{}) {
|
||||
for _, v := range data {
|
||||
pruneNulls(v)
|
||||
}
|
||||
}
|
||||
func pruneNulls(v interface{}) {
|
||||
switch v := v.(type) {
|
||||
case map[string]interface{}:
|
||||
pruneNullsFromMap(v)
|
||||
case []interface{}:
|
||||
pruneNullsFromSlice(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Saves the last-applied-configuration annotation in a separate SSA field manager
|
||||
// to prevent it from being dropped by users who have transitioned to SSA.
|
||||
//
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -59,8 +60,7 @@ import (
|
|||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
"k8s.io/utils/strings/slices"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
@ -2087,6 +2087,9 @@ func TestForceApply(t *testing.T) {
|
|||
"post": 1,
|
||||
}
|
||||
|
||||
// Set the patch retry back off period to something low, so the test can run more quickly
|
||||
patchRetryBackOffPeriod = 1 * time.Millisecond
|
||||
|
||||
for _, testingOpenAPISchema := range testingOpenAPISchemas {
|
||||
for _, openAPIFeatureToggle := range applyFeatureToggles {
|
||||
|
||||
|
@ -2112,7 +2115,7 @@ func TestForceApply(t *testing.T) {
|
|||
var bodyRC io.ReadCloser
|
||||
if isScaledDownToZero {
|
||||
rcObj := readReplicationControllerFromFile(t, filenameRC)
|
||||
rcObj.Spec.Replicas = utilpointer.Int32Ptr(0)
|
||||
rcObj.Spec.Replicas = ptr.To[int32](0)
|
||||
rcBytes, err := runtime.Encode(codec, rcObj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -2602,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
|
||||
|
@ -2636,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
|
||||
|
@ -2671,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
|
||||
|
@ -2706,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
|
||||
|
@ -2869,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
|
||||
|
@ -3087,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
|
||||
|
@ -3429,7 +3426,7 @@ func TestApplySetDryRun(t *testing.T) {
|
|||
cmd.Run(cmd, []string{})
|
||||
})
|
||||
assert.Equal(t, "replicationcontroller/test-rc serverside-applied (server dry run)\n", outbuff.String())
|
||||
assert.Equal(t, len(serverSideData), 1, "unexpected creation")
|
||||
assert.Len(t, serverSideData, 1, "unexpected creation")
|
||||
require.Nil(t, serverSideData[pathSecret], "secret was created")
|
||||
})
|
||||
|
||||
|
@ -3446,7 +3443,7 @@ func TestApplySetDryRun(t *testing.T) {
|
|||
cmd.Run(cmd, []string{})
|
||||
})
|
||||
assert.Equal(t, "replicationcontroller/test-rc configured (dry run)\n", outbuff.String())
|
||||
assert.Equal(t, len(serverSideData), 1, "unexpected creation")
|
||||
assert.Len(t, serverSideData, 1, "unexpected creation")
|
||||
require.Nil(t, serverSideData[pathSecret], "secret was created")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,13 +17,12 @@ limitations under the License.
|
|||
package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
@ -49,8 +48,6 @@ import (
|
|||
const (
|
||||
// maxPatchRetry is the maximum number of conflicts retry for during a patch operation before returning failure
|
||||
maxPatchRetry = 5
|
||||
// backOffPeriod is the period to back off when apply patch results in error.
|
||||
backOffPeriod = 1 * time.Second
|
||||
// how many times we can retry before back off
|
||||
triesBeforeBackOff = 1
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
|
@ -59,7 +56,10 @@ const (
|
|||
groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
)
|
||||
|
||||
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
||||
// 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: %w"
|
||||
|
||||
// Patcher defines options to patch OpenAPI objects.
|
||||
type Patcher struct {
|
||||
|
@ -118,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
|
||||
|
@ -176,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,7 +363,7 @@ func (p *Patcher) Patch(current runtime.Object, modified []byte, source, namespa
|
|||
}
|
||||
for i := 1; i <= p.Retries && apierrors.IsConflict(err); i++ {
|
||||
if i > triesBeforeBackOff {
|
||||
p.BackOff.Sleep(backOffPeriod)
|
||||
p.BackOff.Sleep(patchRetryBackOffPeriod)
|
||||
}
|
||||
current, getErr = p.Helper.Get(namespace, name)
|
||||
if getErr != nil {
|
||||
|
@ -386,7 +386,7 @@ func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, name
|
|||
return modified, nil, err
|
||||
}
|
||||
// TODO: use wait
|
||||
if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
|
||||
if err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, p.Timeout, true, func(ctx context.Context) (bool, error) {
|
||||
if _, err := p.Helper.Get(namespace, name); !apierrors.IsNotFound(err) {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -184,7 +184,9 @@ func createExecutor(url *url.URL, config *restclient.Config) (remotecommand.Exec
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exec, err = remotecommand.NewFallbackExecutor(websocketExec, exec, httpstream.IsUpgradeFailure)
|
||||
exec, err = remotecommand.NewFallbackExecutor(websocketExec, exec, func(err error) bool {
|
||||
return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -204,7 +206,7 @@ func (o *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
|
|||
|
||||
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
|
||||
if err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, err.Error())
|
||||
return cmdutil.UsageErrorf(cmd, "%s", err.Error())
|
||||
}
|
||||
|
||||
o.Builder = f.NewBuilder
|
||||
|
@ -305,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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,9 +83,8 @@ var (
|
|||
# Check to see if I can list deployments in my current namespace
|
||||
kubectl auth can-i list deployments.apps
|
||||
|
||||
# Check to see if service account "foo" of namespace "dev" can list pods
|
||||
# in the namespace "prod".
|
||||
# You must be allowed to use impersonation for the global option "--as".
|
||||
# Check to see if service account "foo" of namespace "dev" can list pods in the namespace "prod"
|
||||
# You must be allowed to use impersonation for the global option "--as"
|
||||
kubectl auth can-i list pods --as=system:serviceaccount:dev:foo -n prod
|
||||
|
||||
# Check to see if I can do everything in my current namespace ("*" means all)
|
||||
|
@ -100,13 +99,16 @@ var (
|
|||
# Check to see if I can access the URL /logs/
|
||||
kubectl auth can-i get /logs/
|
||||
|
||||
# Check to see if I can approve certificates.k8s.io
|
||||
kubectl auth can-i approve certificates.k8s.io
|
||||
|
||||
# List all allowed actions in namespace "foo"
|
||||
kubectl auth can-i --list --namespace=foo`)
|
||||
|
||||
resourceVerbs = sets.NewString("get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "use", "bind", "impersonate", "*")
|
||||
nonResourceURLVerbs = sets.NewString("get", "put", "post", "head", "options", "delete", "patch", "*")
|
||||
resourceVerbs = sets.New[string]("get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "use", "bind", "impersonate", "*", "approve", "sign", "escalate", "attest")
|
||||
nonResourceURLVerbs = sets.New[string]("get", "put", "post", "head", "options", "delete", "patch", "*")
|
||||
// holds all the server-supported resources that cannot be discovered by clients. i.e. users and groups for the impersonate verb
|
||||
nonStandardResourceNames = sets.NewString("users", "groups")
|
||||
nonStandardResourceNames = sets.New[string]("users", "groups")
|
||||
)
|
||||
|
||||
// NewCmdCanI returns an initialized Command for 'auth can-i' sub command
|
||||
|
@ -183,7 +185,7 @@ func (o *CanIOptions) Complete(f cmdutil.Factory, args []string) error {
|
|||
default:
|
||||
errString := "you must specify two arguments: verb resource or verb resource/resourceName."
|
||||
usageString := "See 'kubectl auth can-i -h' for help and examples."
|
||||
return errors.New(fmt.Sprintf("%s\n%s", errString, usageString))
|
||||
return fmt.Errorf("%s\n%s", errString, usageString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ func TestRunAccessCheck(t *testing.T) {
|
|||
test.serverErr
|
||||
}),
|
||||
}
|
||||
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
|
||||
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}, ContentType: runtime.ContentTypeJSON}}
|
||||
|
||||
if err := test.o.Complete(tf, test.args); err != nil {
|
||||
t.Errorf("%s: %v", test.name, err)
|
||||
|
@ -196,6 +196,7 @@ func TestRunAccessList(t *testing.T) {
|
|||
" [/version] [] [get]\n"
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
tf.ClientConfigVal.ContentType = runtime.ContentTypeJSON
|
||||
defer tf.Cleanup()
|
||||
|
||||
ns := scheme.Codecs.WithoutConversion()
|
||||
|
|
|
@ -122,16 +122,16 @@ var (
|
|||
whoAmILong = templates.LongDesc(`
|
||||
Experimental: Check who you are and your attributes (groups, extra).
|
||||
|
||||
This command is helpful to get yourself aware of the current user attributes,
|
||||
especially when dynamic authentication, e.g., token webhook, auth proxy, or OIDC provider,
|
||||
This command is helpful to get yourself aware of the current user attributes,
|
||||
especially when dynamic authentication, e.g., token webhook, auth proxy, or OIDC provider,
|
||||
is enabled in the Kubernetes cluster.
|
||||
`)
|
||||
|
||||
whoAmIExample = templates.Examples(`
|
||||
# Get your subject attributes.
|
||||
# Get your subject attributes
|
||||
kubectl auth whoami
|
||||
|
||||
# Get your subject attributes in JSON format.
|
||||
|
||||
# Get your subject attributes in JSON format
|
||||
kubectl auth whoami -o json
|
||||
`)
|
||||
)
|
||||
|
@ -250,7 +250,7 @@ func printTableSelfSubjectAccessReview(obj runtime.Object, out io.Writer) error
|
|||
}
|
||||
|
||||
if len(ui.Extra) > 0 {
|
||||
for _, k := range sets.StringKeySet(ui.Extra).List() {
|
||||
for _, k := range sets.List(sets.KeySet(ui.Extra)) {
|
||||
v := ui.Extra[k]
|
||||
_, err := fmt.Fprintf(w, "Extra: %s\t%v\n", k, v)
|
||||
if err != nil {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -24,13 +24,17 @@ import (
|
|||
"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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
|
||||
autoscalingv2client "k8s.io/client-go/kubernetes/typed/autoscaling/v2"
|
||||
"k8s.io/client-go/scale"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
|
@ -43,6 +47,7 @@ import (
|
|||
var (
|
||||
autoscaleLong = templates.LongDesc(i18n.T(`
|
||||
Creates an autoscaler that automatically chooses and sets the number of pods that run in a Kubernetes cluster.
|
||||
The command will attempt to use the autoscaling/v2 API first, in case of an error, it will fall back to autoscaling/v1 API.
|
||||
|
||||
Looks up a deployment, replica set, stateful set, or replication controller by name and creates an autoscaler that uses the given resource as a reference.
|
||||
An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))
|
||||
|
@ -78,7 +83,8 @@ type AutoscaleOptions struct {
|
|||
builder *resource.Builder
|
||||
fieldManager string
|
||||
|
||||
HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
|
||||
HPAClientV1 autoscalingv1client.HorizontalPodAutoscalersGetter
|
||||
HPAClientV2 autoscalingv2client.HorizontalPodAutoscalersGetter
|
||||
scaleKindResolver scale.ScaleKindResolver
|
||||
|
||||
genericiooptions.IOStreams
|
||||
|
@ -157,7 +163,8 @@ func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.HPAClient = kubeClient.AutoscalingV1()
|
||||
o.HPAClientV2 = kubeClient.AutoscalingV2()
|
||||
o.HPAClientV1 = kubeClient.AutoscalingV1()
|
||||
|
||||
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
|
@ -186,7 +193,6 @@ func (o *AutoscaleOptions) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution
|
||||
func (o *AutoscaleOptions) Run() error {
|
||||
r := o.builder.
|
||||
Unstructured().
|
||||
|
@ -212,44 +218,19 @@ func (o *AutoscaleOptions) Run() error {
|
|||
return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
|
||||
}
|
||||
|
||||
hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)
|
||||
|
||||
if err := o.Recorder.Record(hpa); err != nil {
|
||||
klog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
|
||||
if o.dryRunStrategy == cmdutil.DryRunClient {
|
||||
count++
|
||||
|
||||
printer, err := o.ToPrinter("created")
|
||||
if err != nil {
|
||||
// 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)
|
||||
hpaV1 := o.createHorizontalPodAutoscalerV1(info.Name, mapping)
|
||||
if err := o.handleHPA(hpaV1); err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(hpa, o.Out)
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.fieldManager != "" {
|
||||
createOptions.FieldManager = o.fieldManager
|
||||
}
|
||||
if o.dryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), hpa, createOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count++
|
||||
printer, err := o.ToPrinter("autoscaled")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(actualHPA, o.Out)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -260,7 +241,96 @@ func (o *AutoscaleOptions) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
|
||||
// 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 {
|
||||
return fmt.Errorf("error recording current command: %w", err)
|
||||
}
|
||||
|
||||
if o.dryRunStrategy == cmdutil.DryRunClient {
|
||||
printer, err := o.ToPrinter("created")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(hpa, o.Out)
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.fieldManager != "" {
|
||||
createOptions.FieldManager = o.fieldManager
|
||||
}
|
||||
if o.dryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
|
||||
var actualHPA runtime.Object
|
||||
var err error
|
||||
switch typedHPA := hpa.(type) {
|
||||
case *autoscalingv2.HorizontalPodAutoscaler:
|
||||
actualHPA, err = o.HPAClientV2.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), typedHPA, createOptions)
|
||||
case *autoscalingv1.HorizontalPodAutoscaler:
|
||||
actualHPA, err = o.HPAClientV1.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), typedHPA, createOptions)
|
||||
default:
|
||||
return fmt.Errorf("unsupported HorizontalPodAutoscaler type %T", hpa)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer, err := o.ToPrinter("autoscaled")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(actualHPA, o.Out)
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) *autoscalingv2.HorizontalPodAutoscaler {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
}
|
||||
|
||||
scaler := autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
|
||||
Kind: mapping.GroupVersionKind.Kind,
|
||||
Name: refName,
|
||||
},
|
||||
MaxReplicas: o.Max,
|
||||
},
|
||||
}
|
||||
|
||||
if o.Min > 0 {
|
||||
scaler.Spec.MinReplicas = &o.Min
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &scaler
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
|
|
|
@ -0,0 +1,572 @@
|
|||
/*
|
||||
Copyright 2024 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 autoscale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type validateTestCase struct {
|
||||
name string
|
||||
options *AutoscaleOptions
|
||||
expectedError error
|
||||
}
|
||||
|
||||
func TestAutoscaleValidate(t *testing.T) {
|
||||
tests := []validateTestCase{
|
||||
{
|
||||
name: "valid options",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 10,
|
||||
Min: 1,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "max less than 1",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 0,
|
||||
Min: 1,
|
||||
},
|
||||
expectedError: fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: 0"),
|
||||
},
|
||||
{
|
||||
name: "min greater than max",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 1,
|
||||
Min: 2,
|
||||
},
|
||||
expectedError: fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: 1, min: 2"),
|
||||
},
|
||||
{
|
||||
name: "zero min replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: 0,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "negative min replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: -2,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errorGot := tc.options.Validate()
|
||||
assert.Equal(t, tc.expectedError, errorGot)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type createHorizontalPodAutoscalerTestCase struct {
|
||||
name string
|
||||
options *AutoscaleOptions
|
||||
refName string
|
||||
mapping *meta.RESTMapping
|
||||
expectedHPAV2 *autoscalingv2.HorizontalPodAutoscaler
|
||||
expectedHPAV1 *autoscalingv1.HorizontalPodAutoscaler
|
||||
}
|
||||
|
||||
func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
|
||||
tests := []createHorizontalPodAutoscalerTestCase{
|
||||
{
|
||||
name: "create with all options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
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(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without min replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-2",
|
||||
Max: 10,
|
||||
Min: -1,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
refName: "deployment-2",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-2",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-2",
|
||||
},
|
||||
MinReplicas: nil,
|
||||
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(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without max replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-3",
|
||||
Max: -1,
|
||||
Min: 2,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
refName: "deployment-3",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-3",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-3",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(-1),
|
||||
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(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without cpu utilization",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-4",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPUPercent: -1,
|
||||
},
|
||||
refName: "deployment-4",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-4",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-4",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with replicaset reference",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "replicaset-hpa",
|
||||
Max: 5,
|
||||
Min: 1,
|
||||
CPUPercent: 70,
|
||||
},
|
||||
refName: "frontend",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "ReplicaSet",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "replicaset-hpa",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "frontend",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(1)),
|
||||
MaxReplicas: int32(5),
|
||||
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)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with statefulset reference",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "statefulset-hpa",
|
||||
Max: 8,
|
||||
Min: 2,
|
||||
CPUPercent: 60,
|
||||
},
|
||||
refName: "web",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "statefulset-hpa",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "StatefulSet",
|
||||
Name: "web",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(8),
|
||||
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(60)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hpa := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
|
||||
assert.Equal(t, tc.expectedHPAV2, hpa)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateHorizontalPodAutoscalerV1(t *testing.T) {
|
||||
tests := []createHorizontalPodAutoscalerTestCase{
|
||||
{
|
||||
name: "create with all options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
TargetCPUUtilizationPercentage: ptr.To(int32(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without min replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-2",
|
||||
Max: 10,
|
||||
Min: -1,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
refName: "deployment-2",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-2",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-2",
|
||||
},
|
||||
MinReplicas: nil,
|
||||
MaxReplicas: int32(10),
|
||||
TargetCPUUtilizationPercentage: ptr.To(int32(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without max replicas",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-3",
|
||||
Max: -1,
|
||||
Min: 2,
|
||||
CPUPercent: 80,
|
||||
},
|
||||
refName: "deployment-3",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-3",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-3",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(-1),
|
||||
TargetCPUUtilizationPercentage: ptr.To(int32(80)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create without cpu utilization",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name-4",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPUPercent: -1,
|
||||
},
|
||||
refName: "deployment-4",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name-4",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-4",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with replicaset reference",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "replicaset-hpa",
|
||||
Max: 5,
|
||||
Min: 1,
|
||||
CPUPercent: 70,
|
||||
},
|
||||
refName: "frontend",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "ReplicaSet",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "replicaset-hpa",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "frontend",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(1)),
|
||||
MaxReplicas: int32(5),
|
||||
TargetCPUUtilizationPercentage: ptr.To(int32(70)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with statefulset reference",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "statefulset-hpa",
|
||||
Max: 8,
|
||||
Min: 2,
|
||||
CPUPercent: 60,
|
||||
},
|
||||
refName: "web",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
},
|
||||
expectedHPAV1: &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "statefulset-hpa",
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "StatefulSet",
|
||||
Name: "web",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(8),
|
||||
TargetCPUUtilizationPercentage: ptr.To(int32(60)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hpa := tc.options.createHorizontalPodAutoscalerV1(tc.refName, tc.mapping)
|
||||
assert.Equal(t, tc.expectedHPAV1, hpa)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -33,7 +33,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
ct "github.com/daviddengcn/go-colortext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -156,12 +155,8 @@ func (o *ClusterInfoOptions) Run() error {
|
|||
}
|
||||
|
||||
func printService(out io.Writer, name, link string) {
|
||||
ct.ChangeColor(ct.Green, false, ct.None, false)
|
||||
fmt.Fprint(out, name)
|
||||
ct.ResetColor()
|
||||
fmt.Fprint(out, " is running at ")
|
||||
ct.ChangeColor(ct.Yellow, false, ct.None, false)
|
||||
fmt.Fprint(out, link)
|
||||
ct.ResetColor()
|
||||
fmt.Fprintln(out, "")
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -118,8 +119,8 @@ func setupOutputWriter(dir string, defaultWriter io.Writer, filename string, fil
|
|||
if len(dir) == 0 || dir == "-" {
|
||||
return defaultWriter
|
||||
}
|
||||
fullFile := path.Join(dir, filename) + fileExtension
|
||||
parent := path.Dir(fullFile)
|
||||
fullFile := filepath.Join(dir, filename) + fileExtension
|
||||
parent := filepath.Dir(fullFile)
|
||||
cmdutil.CheckErr(os.MkdirAll(parent, 0755))
|
||||
|
||||
file, err := os.Create(fullFile)
|
||||
|
|
|
@ -18,7 +18,7 @@ package clusterinfo
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -60,7 +60,7 @@ func TestSetupOutputWriterFile(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
fullPath := path.Join(dir, file) + extension
|
||||
fullPath := filepath.Join(dir, file) + extension
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
_, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
|
|
101
pkg/cmd/cmd.go
101
pkg/cmd/cmd.go
|
@ -73,6 +73,7 @@ import (
|
|||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/cmd/version"
|
||||
"k8s.io/kubectl/pkg/cmd/wait"
|
||||
"k8s.io/kubectl/pkg/kuberc"
|
||||
utilcomp "k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
@ -82,8 +83,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/cmd/kustomize"
|
||||
)
|
||||
|
||||
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
|
||||
|
||||
type KubectlOptions struct {
|
||||
PluginHandler PluginHandler
|
||||
Arguments []string
|
||||
|
@ -136,31 +135,29 @@ func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
|
|||
case "help", cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd:
|
||||
// Don't search for a plugin
|
||||
default:
|
||||
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, false); err != nil {
|
||||
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, 1); err != nil {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
} 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, true); 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +256,7 @@ func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environme
|
|||
|
||||
// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
|
||||
// a plugin executable on the PATH that satisfies the given arguments.
|
||||
func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string, exactMatch bool) error {
|
||||
func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string, minArgs int) error {
|
||||
var remainingArgs []string // all "non-flag" arguments
|
||||
for _, arg := range cmdArgs {
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
|
@ -279,13 +276,14 @@ func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string, exactMat
|
|||
for len(remainingArgs) > 0 {
|
||||
path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
|
||||
if !found {
|
||||
if exactMatch {
|
||||
// if exactMatch is true, we shouldn't continue searching with shorter names.
|
||||
remainingArgs = remainingArgs[:len(remainingArgs)-1]
|
||||
if len(remainingArgs) < minArgs {
|
||||
// we shouldn't continue searching with shorter names.
|
||||
// this is especially for not searching kubectl-create plugin
|
||||
// when kubectl-create-foo plugin is not found.
|
||||
break
|
||||
}
|
||||
remainingArgs = remainingArgs[:len(remainingArgs)-1]
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -360,6 +358,11 @@ 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.IsDisabled() {
|
||||
pref.AddFlags(flags)
|
||||
}
|
||||
|
||||
kubeConfigFlags := o.ConfigFlags
|
||||
if kubeConfigFlags == nil {
|
||||
kubeConfigFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams)
|
||||
|
@ -382,6 +385,8 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
// Avoid import cycle by setting ValidArgsFunction here instead of in NewCmdGet()
|
||||
getCmd := get.NewCmdGet("kubectl", f, o.IOStreams)
|
||||
getCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
|
||||
debugCmd := debug.NewCmdDebug(f, o.IOStreams)
|
||||
debugCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
|
||||
|
||||
groups := templates.CommandGroups{
|
||||
{
|
||||
|
@ -433,7 +438,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
proxyCmd,
|
||||
cp.NewCmdCp(f, o.IOStreams),
|
||||
auth.NewCmdAuth(f, o.IOStreams),
|
||||
debug.NewCmdDebug(f, o.IOStreams),
|
||||
debugCmd,
|
||||
events.NewCmdEvents(f, o.IOStreams),
|
||||
},
|
||||
},
|
||||
|
@ -479,7 +484,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
registerCompletionFuncForGlobalFlags(cmds, f)
|
||||
|
||||
cmds.AddCommand(alpha)
|
||||
cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), o.IOStreams))
|
||||
cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), o.IOStreams))
|
||||
cmds.AddCommand(plugin.NewCmdPlugin(o.IOStreams))
|
||||
cmds.AddCommand(version.NewCmdVersion(f, o.IOStreams))
|
||||
cmds.AddCommand(apiresources.NewCmdAPIVersions(f, o.IOStreams))
|
||||
|
@ -489,6 +494,15 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
// Stop warning about normalization of flags. That makes it possible to
|
||||
// add the klog flags later.
|
||||
cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
|
||||
|
||||
if !cmdutil.KubeRC.IsDisabled() {
|
||||
_, err := pref.Apply(cmds, o.Arguments, o.IOStreams.ErrOut)
|
||||
if err != nil {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "error occurred while applying preferences %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
||||
|
@ -506,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{}
|
||||
|
@ -565,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) {
|
||||
|
@ -196,6 +197,18 @@ func TestKubectlCommandHandlesPlugins(t *testing.T) {
|
|||
expectPlugin: "plugin/testdata/kubectl-foo",
|
||||
expectPluginArgs: []string{"--bar"},
|
||||
},
|
||||
{
|
||||
name: "test that a plugin executable is found based on command args with positional argument",
|
||||
args: []string{"kubectl", "foo", "positional", "--bar"},
|
||||
expectPlugin: "plugin/testdata/kubectl-foo",
|
||||
expectPluginArgs: []string{"positional", "--bar"},
|
||||
},
|
||||
{
|
||||
name: "test that an allowed subcommand plugin executable is found based on command args with positional argument",
|
||||
args: []string{"kubectl", "create", "foo", "positional", "--bar"},
|
||||
expectPlugin: "plugin/testdata/kubectl-create-foo",
|
||||
expectPluginArgs: []string{"positional", "--bar"},
|
||||
},
|
||||
{
|
||||
name: "test that a plugin does not execute over an existing command by the same name",
|
||||
args: []string{"kubectl", "version", "--client=true"},
|
||||
|
@ -330,6 +343,7 @@ func (h *testPluginHandler) Lookup(filename string) (string, bool) {
|
|||
for _, p := range plugins {
|
||||
filenameWithSuportedPrefix = fmt.Sprintf("%s-%s", prefix, filename)
|
||||
if p.Name() == filenameWithSuportedPrefix {
|
||||
h.lookupErr = nil
|
||||
return fmt.Sprintf("%s/%s", h.pluginsDirectory, p.Name()), true
|
||||
}
|
||||
}
|
||||
|
@ -368,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 {
|
||||
|
@ -381,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 {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
@ -31,7 +32,7 @@ import (
|
|||
)
|
||||
|
||||
// NewCmdConfig creates a command object for the "config" action, and adds all child commands to it.
|
||||
func NewCmdConfig(pathOptions *clientcmd.PathOptions, streams genericiooptions.IOStreams) *cobra.Command {
|
||||
func NewCmdConfig(restClientGetter genericclioptions.RESTClientGetter, pathOptions *clientcmd.PathOptions, streams genericiooptions.IOStreams) *cobra.Command {
|
||||
if len(pathOptions.ExplicitFileFlag) == 0 {
|
||||
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ func NewCmdConfig(pathOptions *clientcmd.PathOptions, streams genericiooptions.I
|
|||
cmd.AddCommand(NewCmdConfigView(streams, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetCluster(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetCredentials(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetContext(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSetContext(restClientGetter, streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigSet(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigUnset(streams.Out, pathOptions))
|
||||
cmd.AddCommand(NewCmdConfigCurrentContext(streams.Out, pathOptions))
|
||||
|
|
|
@ -19,7 +19,7 @@ package config
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
)
|
||||
|
||||
|
@ -68,7 +69,6 @@ func Example_view() {
|
|||
// name: federal-context
|
||||
// current-context: federal-context
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: red-user
|
||||
// user:
|
||||
|
@ -415,7 +415,7 @@ func TestEmptyTokenAndCertAllowed(t *testing.T) {
|
|||
defer utiltesting.CloseAndRemove(t, fakeCertFile)
|
||||
expectedConfig := newRedFederalCowHammerConfig()
|
||||
authInfo := clientcmdapi.NewAuthInfo()
|
||||
authInfo.ClientCertificate = path.Base(fakeCertFile.Name())
|
||||
authInfo.ClientCertificate = filepath.Base(fakeCertFile.Name())
|
||||
expectedConfig.AuthInfos["another-user"] = authInfo
|
||||
|
||||
test := configCommandTest{
|
||||
|
@ -660,7 +660,7 @@ func TestCAClearsInsecure(t *testing.T) {
|
|||
clusterInfoWithInsecure.InsecureSkipTLSVerify = true
|
||||
|
||||
clusterInfoWithCA := clientcmdapi.NewCluster()
|
||||
clusterInfoWithCA.CertificateAuthority = path.Base(fakeCAFile.Name())
|
||||
clusterInfoWithCA.CertificateAuthority = filepath.Base(fakeCAFile.Name())
|
||||
|
||||
startingConfig := newRedFederalCowHammerConfig()
|
||||
startingConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
|
||||
|
@ -948,8 +948,11 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes
|
|||
argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
|
||||
argsToUse = append(argsToUse, args...)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdConfig(clientcmd.NewDefaultPathOptions(), streams)
|
||||
cmd := NewCmdConfig(tf, clientcmd.NewDefaultPathOptions(), streams)
|
||||
// "context" is a global flag, inherited from base kubectl command in the real world
|
||||
cmd.PersistentFlags().String("context", "", "The name of the kubeconfig context to use")
|
||||
cmd.SetArgs(argsToUse)
|
||||
|
|
|
@ -89,7 +89,7 @@ func NewCmdConfigGetContexts(streams genericiooptions.IOStreams, configAccess cl
|
|||
|
||||
// Complete assigns GetContextsOptions from the args.
|
||||
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error {
|
||||
supportedOutputTypes := sets.NewString("", "name")
|
||||
supportedOutputTypes := sets.New[string]("", "name")
|
||||
if !supportedOutputTypes.Has(o.outputFormat) {
|
||||
return fmt.Errorf("--output %v is not available in kubectl config get-contexts; resetting to default output format", o.outputFormat)
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func newNavigationSteps(path string) (*navigationSteps, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextPart := findNameStep(individualParts[currPartIndex:], sets.StringKeySet(mapValueOptions))
|
||||
nextPart := findNameStep(individualParts[currPartIndex:], sets.KeySet(mapValueOptions))
|
||||
|
||||
steps = append(steps, navigationStep{nextPart, mapValueType})
|
||||
currPartIndex += len(strings.Split(nextPart, "."))
|
||||
|
@ -98,7 +98,7 @@ func (s *navigationSteps) moreStepsRemaining() bool {
|
|||
|
||||
// findNameStep takes the list of parts and a set of valid tags that can be used after the name. It then walks the list of parts
|
||||
// until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts
|
||||
func findNameStep(parts []string, typeOptions sets.String) string {
|
||||
func findNameStep(parts []string, typeOptions sets.Set[string]) string {
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, er
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func findKnownValue(parts []string, valueOptions sets.String) int {
|
||||
func findKnownValue(parts []string, valueOptions sets.Set[string]) int {
|
||||
for i := range parts {
|
||||
if valueOptions.Has(parts[i]) {
|
||||
return i
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
|
@ -53,7 +54,7 @@ var (
|
|||
)
|
||||
|
||||
// NewCmdConfigSetContext returns a Command instance for 'config set-context' sub command
|
||||
func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
func NewCmdConfigSetContext(restClientGetter genericclioptions.RESTClientGetter, out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
options := &setContextOptions{configAccess: configAccess}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -79,6 +80,12 @@ func NewCmdConfigSetContext(out io.Writer, configAccess clientcmd.ConfigAccess)
|
|||
cmd.Flags().Var(&options.cluster, clientcmd.FlagClusterName, clientcmd.FlagClusterName+" for the context entry in kubeconfig")
|
||||
cmd.Flags().Var(&options.authInfo, clientcmd.FlagAuthInfoName, clientcmd.FlagAuthInfoName+" for the context entry in kubeconfig")
|
||||
cmd.Flags().Var(&options.namespace, clientcmd.FlagNamespace, clientcmd.FlagNamespace+" for the context entry in kubeconfig")
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
|
||||
"namespace",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return completion.CompGetResource(cmdutil.NewFactory(restClientGetter), "namespace", toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
)
|
||||
|
||||
type setContextTest struct {
|
||||
|
@ -122,7 +122,11 @@ func (test setContextTest) run(t *testing.T) {
|
|||
pathOptions.GlobalFile = fakeKubeFile.Name()
|
||||
pathOptions.EnvVar = ""
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
cmd := NewCmdConfigSetContext(buf, pathOptions)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
cmd := NewCmdConfigSetContext(tf, buf, pathOptions)
|
||||
cmd.SetArgs(test.args)
|
||||
cmd.Flags().Parse(test.flags)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -18,12 +18,13 @@ package cp
|
|||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -267,8 +268,8 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
|
|||
options := &exec.ExecOptions{
|
||||
StreamOptions: exec.StreamOptions{
|
||||
IOStreams: genericiooptions.IOStreams{
|
||||
Out: bytes.NewBuffer([]byte{}),
|
||||
ErrOut: bytes.NewBuffer([]byte{}),
|
||||
Out: io.Discard,
|
||||
ErrOut: io.Discard,
|
||||
},
|
||||
|
||||
Namespace: dest.PodNamespace,
|
||||
|
@ -279,7 +280,21 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
|
|||
Executor: &exec.DefaultRemoteExecutor{},
|
||||
}
|
||||
|
||||
return o.execute(options)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error)
|
||||
|
||||
go func() {
|
||||
done <- o.execute(options)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-done:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) error {
|
||||
|
@ -295,6 +310,10 @@ func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) e
|
|||
// If no error, dest.File was found to be a directory.
|
||||
// Copy specified src into it
|
||||
destFile = destFile.Join(srcFile.Base())
|
||||
} else if errors.Is(err, context.DeadlineExceeded) {
|
||||
// we haven't decided destination is directory or not because context timeout is exceeded.
|
||||
// That's why, we should shortcut the process in here.
|
||||
return err
|
||||
}
|
||||
|
||||
go func(src localPath, dest remotePath, writer io.WriteCloser) {
|
||||
|
|
|
@ -988,6 +988,6 @@ func cmpFileData(t *testing.T, filePath, data string) {
|
|||
type testWriter testing.T
|
||||
|
||||
func (t *testWriter) Write(p []byte) (n int, err error) {
|
||||
t.Logf(string(p))
|
||||
t.Log(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
|
|
@ -109,12 +109,6 @@ func NewCmdCreate(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobr
|
|||
Long: createLong,
|
||||
Example: createExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
|
||||
ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n"))
|
||||
defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
|
||||
defaultRunFunc(cmd, args)
|
||||
return
|
||||
}
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunCreate(f, cmd))
|
||||
|
@ -159,8 +153,12 @@ func NewCmdCreate(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobr
|
|||
return cmd
|
||||
}
|
||||
|
||||
// Validate makes sure there is no discrepency in command options
|
||||
// Validate makes sure there is no discrepancy in command options
|
||||
func (o *CreateOptions) Validate() error {
|
||||
if err := o.FilenameOptions.RequireFilenameOrKustomize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(o.Raw) > 0 {
|
||||
if o.EditBeforeCreate {
|
||||
return fmt.Errorf("--raw and --edit are mutually exclusive")
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
|
@ -320,7 +320,7 @@ func handleConfigMapFromFileSources(configMap *corev1.ConfigMap, fileSources []s
|
|||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
||||
}
|
||||
for _, item := range fileList {
|
||||
itemPath := path.Join(filePath, item.Name())
|
||||
itemPath := filepath.Join(filePath, item.Name())
|
||||
if item.Type().IsRegular() {
|
||||
keyName = item.Name()
|
||||
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -261,10 +262,21 @@ func (o *CreateJobOptions) createJobFromCronJob(cronJob *batchv1.CronJob) *batch
|
|||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Annotations: annotations,
|
||||
Labels: cronJob.Spec.JobTemplate.Labels,
|
||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(cronJob, batchv1.SchemeGroupVersion.WithKind("CronJob"))},
|
||||
Name: o.Name,
|
||||
Annotations: annotations,
|
||||
Labels: cronJob.Spec.JobTemplate.Labels,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
// we are not using metav1.NewControllerRef because it
|
||||
// sets BlockOwnerDeletion to true which additionally mandates
|
||||
// cronjobs/finalizer role and not backwards-compatible.
|
||||
APIVersion: batchv1.SchemeGroupVersion.String(),
|
||||
Kind: "CronJob",
|
||||
Name: cronJob.GetName(),
|
||||
UID: cronJob.GetUID(),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: cronJob.Spec.JobTemplate.Spec,
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestCreateJobValidation(t *testing.T) {
|
||||
|
@ -161,9 +162,17 @@ func TestCreateJobFromCronJob(t *testing.T) {
|
|||
expected: &batchv1.Job{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jobName,
|
||||
Annotations: map[string]string{"cronjob.kubernetes.io/instantiate": "manual"},
|
||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(cronJob, batchv1.SchemeGroupVersion.WithKind("CronJob"))},
|
||||
Name: jobName,
|
||||
Annotations: map[string]string{"cronjob.kubernetes.io/instantiate": "manual"},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: batchv1.SchemeGroupVersion.String(),
|
||||
Kind: "CronJob",
|
||||
Name: cronJob.GetName(),
|
||||
UID: cronJob.GetUID(),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
|
|
|
@ -116,7 +116,7 @@ var (
|
|||
func AddSpecialVerb(verb string, gr schema.GroupResource) {
|
||||
resources, ok := specialVerbs[verb]
|
||||
if !ok {
|
||||
resources = make([]schema.GroupResource, 1)
|
||||
resources = make([]schema.GroupResource, 0, 1)
|
||||
}
|
||||
resources = append(resources, gr)
|
||||
specialVerbs[verb] = resources
|
||||
|
@ -425,7 +425,7 @@ func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resourc
|
|||
|
||||
// Create separate rule for each of the api group.
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
for _, g := range sets.StringKeySet(groupResourceMapping).List() {
|
||||
for _, g := range sets.List(sets.KeySet(groupResourceMapping)) {
|
||||
rule := rbacv1.PolicyRule{}
|
||||
rule.Verbs = verbs
|
||||
rule.Resources = groupResourceMapping[g]
|
||||
|
|
|
@ -684,14 +684,17 @@ func TestAddSpecialVerb(t *testing.T) {
|
|||
testCases := map[string]struct {
|
||||
verb string
|
||||
resource schema.GroupResource
|
||||
isNew bool
|
||||
}{
|
||||
"existing verb": {
|
||||
verb: "use",
|
||||
resource: schema.GroupResource{Group: "my.custom.io", Resource: "one"},
|
||||
isNew: false,
|
||||
},
|
||||
"new verb": {
|
||||
verb: "new",
|
||||
resource: schema.GroupResource{Group: "my.custom.io", Resource: "two"},
|
||||
isNew: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -703,6 +706,16 @@ func TestAddSpecialVerb(t *testing.T) {
|
|||
t.Errorf("missing expected verb: %s", tc.verb)
|
||||
}
|
||||
|
||||
if tc.isNew {
|
||||
if len(resources) != 1 {
|
||||
t.Errorf("new verb should only contain one resource resources:%#v", resources)
|
||||
}
|
||||
if !reflect.DeepEqual(tc.resource, resources[0]) {
|
||||
t.Errorf("miss expected resource:%#v", tc.resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, res := range resources {
|
||||
if reflect.DeepEqual(tc.resource, res) {
|
||||
return
|
||||
|
|
|
@ -44,7 +44,7 @@ var (
|
|||
# Create a role binding for user1, user2, and group1 using the admin cluster role
|
||||
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1
|
||||
|
||||
# Create a role binding for serviceaccount monitoring:sa-dev using the admin role
|
||||
# Create a role binding for service account monitoring:sa-dev using the admin role
|
||||
kubectl create rolebinding admin-binding --role=admin --serviceaccount=monitoring:sa-dev`))
|
||||
)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -349,7 +349,7 @@ func handleSecretFromFileSources(secret *corev1.Secret, fileSources []string) er
|
|||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
||||
}
|
||||
for _, item := range fileList {
|
||||
itemPath := path.Join(filePath, item.Name())
|
||||
itemPath := filepath.Join(filePath, item.Name())
|
||||
if item.Type().IsRegular() {
|
||||
keyName = item.Name()
|
||||
if err := addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -58,7 +59,7 @@ var (
|
|||
kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
|
||||
|
||||
# Create a new secret named my-secret from ~/.docker/config.json
|
||||
kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json`))
|
||||
kubectl create secret docker-registry my-secret --from-file=path/to/.docker/config.json`))
|
||||
)
|
||||
|
||||
// DockerConfigJSON represents a local docker auth config file
|
||||
|
@ -152,7 +153,11 @@ func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericioopti
|
|||
cmd.Flags().StringVar(&o.Email, "docker-email", o.Email, i18n.T("Email for Docker registry"))
|
||||
cmd.Flags().StringVar(&o.Server, "docker-server", o.Server, i18n.T("Server location for Docker registry"))
|
||||
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
|
||||
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
|
||||
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, "+
|
||||
"in which case a default name of "+corev1.DockerConfigJsonKey+" will be given to them, "+
|
||||
"or optionally with a name and file path, in which case the given name will be used. "+
|
||||
"Specifying a directory will iterate each named file in the directory that is a valid secret key. "+
|
||||
"For this command, the key should always be "+corev1.DockerConfigJsonKey+".")
|
||||
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
|
@ -204,6 +209,11 @@ func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cob
|
|||
return err
|
||||
}
|
||||
|
||||
for i := range o.FileSources {
|
||||
if !strings.Contains(o.FileSources[i], "=") {
|
||||
o.FileSources[i] = corev1.DockerConfigJsonKey + "=" + o.FileSources[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,16 @@ limitations under the License.
|
|||
package create
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
)
|
||||
|
||||
func TestCreateSecretDockerRegistry(t *testing.T) {
|
||||
|
@ -183,3 +188,80 @@ func TestCreateSecretDockerRegistry(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateSecretDockerRegistryFromFile(t *testing.T) {
|
||||
username, password, email, server := "test-user", "test-password", "test-user@example.org", "https://index.docker.io/v1/"
|
||||
secretData, err := handleDockerCfgJSONContent(username, password, email, server)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
secret := &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: corev1.SchemeGroupVersion.String(),
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Type: corev1.SecretTypeDockerConfigJson,
|
||||
Data: map[string][]byte{
|
||||
corev1.DockerConfigJsonKey: secretData,
|
||||
},
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
withKey bool
|
||||
expected *corev1.Secret
|
||||
}{
|
||||
"create_secret_docker_registry_from_file_with_keyname": {
|
||||
withKey: true,
|
||||
expected: secret,
|
||||
},
|
||||
"create_secret_docker_registry_from_file_without_keyname": {
|
||||
withKey: false,
|
||||
expected: secret,
|
||||
},
|
||||
}
|
||||
|
||||
// Run all the tests
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tmp, _ := os.MkdirTemp("", "input")
|
||||
defer func() {
|
||||
err := os.RemoveAll(tmp)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to teardown: %s", err)
|
||||
}
|
||||
}()
|
||||
dockerCfgFile := tmp + "/dockerconfig.json"
|
||||
err := os.WriteFile(dockerCfgFile, secretData, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
ioStreams, _, out, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreateSecretDockerRegistry(tf, ioStreams)
|
||||
args := []string{"foo", "--dry-run=client", "-ojson"}
|
||||
if test.withKey {
|
||||
args = append(args, fmt.Sprintf("--from-file=%s=%s", corev1.DockerConfigJsonKey, dockerCfgFile))
|
||||
} else {
|
||||
args = append(args, fmt.Sprintf("--from-file=%s", dockerCfgFile))
|
||||
}
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
got := &corev1.Secret{}
|
||||
err = json.Unmarshal(out.Bytes(), got)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(got, test.expected) {
|
||||
t.Errorf("test %s\n expected:\n%#v\ngot:\n%#v", name, test.expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package create
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -197,8 +197,8 @@ func write(path, contents string, t *testing.T) {
|
|||
}
|
||||
|
||||
func writeKeyPair(tmpDirPath, key, cert string, t *testing.T) (keyPath, certPath string) {
|
||||
keyPath = path.Join(tmpDirPath, "tls.key")
|
||||
certPath = path.Join(tmpDirPath, "tls.cert")
|
||||
keyPath = filepath.Join(tmpDirPath, "tls.key")
|
||||
certPath = filepath.Join(tmpDirPath, "tls.cert")
|
||||
write(keyPath, key, t)
|
||||
write(certPath, cert, t)
|
||||
return
|
||||
|
|
|
@ -18,6 +18,7 @@ package create
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -399,12 +400,12 @@ func parsePorts(portString string) (int32, intstr.IntOrString, error) {
|
|||
var targetPort intstr.IntOrString
|
||||
if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil {
|
||||
if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 {
|
||||
return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
|
||||
return 0, intstr.FromInt32(0), errors.New(strings.Join(errs, ","))
|
||||
}
|
||||
targetPort = intstr.FromString(portStringSlice[1])
|
||||
} else {
|
||||
if errs := validation.IsValidPortNum(portNum); len(errs) != 0 {
|
||||
return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
|
||||
return 0, intstr.FromInt32(0), errors.New(strings.Join(errs, ","))
|
||||
}
|
||||
targetPort = intstr.FromInt32(int32(portNum))
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
|
@ -150,3 +151,35 @@ func TestCreateDirectory(t *testing.T) {
|
|||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingFilenameError(t *testing.T) {
|
||||
var errStr string
|
||||
var exitCode int
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
if errStr == "" {
|
||||
errStr = str
|
||||
exitCode = code
|
||||
}
|
||||
})
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
||||
ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdCreate(tf, ioStreams)
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.Len() > 0 {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
|
||||
if len(errStr) == 0 {
|
||||
t.Errorf("unexpected non-error")
|
||||
} else if errStr != "error: must specify one of -f and -k" {
|
||||
t.Errorf("unexpected error: %s", errStr)
|
||||
}
|
||||
|
||||
if exitCode != 1 {
|
||||
t.Errorf("unexpected exit code: %d", exitCode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package create
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -39,7 +38,7 @@ import (
|
|||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// TokenOptions is the data required to perform a token request operation.
|
||||
|
@ -99,15 +98,10 @@ var (
|
|||
`)
|
||||
)
|
||||
|
||||
func boundObjectKindToAPIVersions() map[string]string {
|
||||
kinds := map[string]string{
|
||||
"Pod": "v1",
|
||||
"Secret": "v1",
|
||||
}
|
||||
if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" {
|
||||
kinds["Node"] = "v1"
|
||||
}
|
||||
return kinds
|
||||
var boundObjectKinds = map[string]string{
|
||||
"Pod": "v1",
|
||||
"Secret": "v1",
|
||||
"Node": "v1",
|
||||
}
|
||||
|
||||
func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
|
||||
|
@ -151,7 +145,7 @@ func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
|
|||
cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.")
|
||||
|
||||
cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
|
||||
"Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+
|
||||
"Supported kinds are "+strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", ")+". "+
|
||||
"If set, --bound-object-name must be provided.")
|
||||
cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+
|
||||
"The token will expire when the object is deleted. "+
|
||||
|
@ -228,8 +222,8 @@ func (o *TokenOptions) Validate() error {
|
|||
return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
|
||||
}
|
||||
} else {
|
||||
if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok {
|
||||
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", "))
|
||||
if _, ok := boundObjectKinds[o.BoundObjectKind]; !ok {
|
||||
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", "))
|
||||
}
|
||||
if len(o.BoundObjectName) == 0 {
|
||||
return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided")
|
||||
|
@ -247,12 +241,12 @@ func (o *TokenOptions) Run() error {
|
|||
},
|
||||
}
|
||||
if o.Duration > 0 {
|
||||
request.Spec.ExpirationSeconds = pointer.Int64(int64(o.Duration / time.Second))
|
||||
request.Spec.ExpirationSeconds = ptr.To(int64(o.Duration / time.Second))
|
||||
}
|
||||
if len(o.BoundObjectKind) > 0 {
|
||||
request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
|
||||
Kind: o.BoundObjectKind,
|
||||
APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind],
|
||||
APIVersion: boundObjectKinds[o.BoundObjectKind],
|
||||
Name: o.BoundObjectName,
|
||||
UID: types.UID(o.BoundObjectUID),
|
||||
}
|
||||
|
|
|
@ -21,13 +21,11 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"k8s.io/utils/pointer"
|
||||
kjson "sigs.k8s.io/json"
|
||||
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
|
@ -39,6 +37,7 @@ import (
|
|||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestCreateToken(t *testing.T) {
|
||||
|
@ -54,8 +53,6 @@ func TestCreateToken(t *testing.T) {
|
|||
audiences []string
|
||||
duration time.Duration
|
||||
|
||||
enableNodeBindingFeature bool
|
||||
|
||||
serverResponseToken string
|
||||
serverResponseError string
|
||||
|
||||
|
@ -102,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
|
||||
|
@ -118,14 +114,13 @@ status:
|
|||
test: "bad bound object kind",
|
||||
name: "mysa",
|
||||
boundObjectKind: "Foo",
|
||||
expectStderr: `error: supported --bound-object-kind values are Pod, Secret`,
|
||||
expectStderr: `error: supported --bound-object-kind values are Node, Pod, Secret`,
|
||||
},
|
||||
{
|
||||
test: "bad bound object kind (node feature enabled)",
|
||||
name: "mysa",
|
||||
enableNodeBindingFeature: true,
|
||||
boundObjectKind: "Foo",
|
||||
expectStderr: `error: supported --bound-object-kind values are Node, Pod, Secret`,
|
||||
test: "bad bound object kind (node feature enabled)",
|
||||
name: "mysa",
|
||||
boundObjectKind: "Foo",
|
||||
expectStderr: `error: supported --bound-object-kind values are Node, Pod, Secret`,
|
||||
},
|
||||
{
|
||||
test: "missing bound object name",
|
||||
|
@ -172,10 +167,9 @@ status:
|
|||
test: "valid bound object (Node)",
|
||||
name: "mysa",
|
||||
|
||||
enableNodeBindingFeature: true,
|
||||
boundObjectKind: "Node",
|
||||
boundObjectName: "mynode",
|
||||
boundObjectUID: "myuid",
|
||||
boundObjectKind: "Node",
|
||||
boundObjectName: "mynode",
|
||||
boundObjectUID: "myuid",
|
||||
|
||||
expectRequestPath: "/api/v1/namespaces/test/serviceaccounts/mysa/token",
|
||||
expectTokenRequest: &authenticationv1.TokenRequest{
|
||||
|
@ -237,7 +231,7 @@ status:
|
|||
expectTokenRequest: &authenticationv1.TokenRequest{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "authentication.k8s.io/v1", Kind: "TokenRequest"},
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
ExpirationSeconds: pointer.Int64(1000),
|
||||
ExpirationSeconds: ptr.To[int64](1000),
|
||||
},
|
||||
},
|
||||
serverResponseToken: "abc",
|
||||
|
@ -367,10 +361,6 @@ status:
|
|||
if test.duration != 0 {
|
||||
cmd.Flags().Set("duration", test.duration.String())
|
||||
}
|
||||
if test.enableNodeBindingFeature {
|
||||
os.Setenv("KUBECTL_NODE_BOUND_TOKENS", "true")
|
||||
defer os.Unsetenv("KUBECTL_NODE_BOUND_TOKENS")
|
||||
}
|
||||
cmd.Run(cmd, []string{test.name})
|
||||
|
||||
if !reflect.DeepEqual(tokenRequest, test.expectTokenRequest) {
|
||||
|
|
|
@ -25,8 +25,6 @@ import (
|
|||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -46,6 +44,7 @@ import (
|
|||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/cmd/attach"
|
||||
"k8s.io/kubectl/pkg/cmd/exec"
|
||||
"k8s.io/kubectl/pkg/cmd/logs"
|
||||
|
@ -56,6 +55,8 @@ import (
|
|||
"k8s.io/kubectl/pkg/util/interrupt"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -74,6 +75,9 @@ var (
|
|||
debugging utilities without restarting the pod.
|
||||
* 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
|
||||
by debug profile may not work.
|
||||
`))
|
||||
|
||||
debugExample = templates.Examples(i18n.T(`
|
||||
|
@ -111,29 +115,35 @@ type DebugAttachFunc func(ctx context.Context, restClientGetter genericclioption
|
|||
|
||||
// DebugOptions holds the options for an invocation of kubectl debug.
|
||||
type DebugOptions struct {
|
||||
Args []string
|
||||
ArgsOnly bool
|
||||
Attach bool
|
||||
AttachFunc DebugAttachFunc
|
||||
Container string
|
||||
CopyTo string
|
||||
Replace bool
|
||||
Env []corev1.EnvVar
|
||||
Image string
|
||||
Interactive bool
|
||||
Namespace string
|
||||
TargetNames []string
|
||||
PullPolicy corev1.PullPolicy
|
||||
Quiet bool
|
||||
SameNode bool
|
||||
SetImages map[string]string
|
||||
ShareProcesses bool
|
||||
TargetContainer string
|
||||
TTY bool
|
||||
Profile string
|
||||
CustomProfileFile string
|
||||
CustomProfile *corev1.Container
|
||||
Applier ProfileApplier
|
||||
Args []string
|
||||
ArgsOnly bool
|
||||
Attach bool
|
||||
AttachFunc DebugAttachFunc
|
||||
Container string
|
||||
CopyTo string
|
||||
Replace bool
|
||||
Env []corev1.EnvVar
|
||||
Image string
|
||||
Interactive bool
|
||||
KeepLabels bool
|
||||
KeepAnnotations bool
|
||||
KeepLiveness bool
|
||||
KeepReadiness bool
|
||||
KeepStartup bool
|
||||
KeepInitContainers bool
|
||||
Namespace string
|
||||
TargetNames []string
|
||||
PullPolicy corev1.PullPolicy
|
||||
Quiet bool
|
||||
SameNode bool
|
||||
SetImages map[string]string
|
||||
ShareProcesses bool
|
||||
TargetContainer string
|
||||
TTY bool
|
||||
Profile string
|
||||
CustomProfileFile string
|
||||
CustomProfile *corev1.Container
|
||||
Applier ProfileApplier
|
||||
|
||||
explicitNamespace bool
|
||||
attachChanged bool
|
||||
|
@ -151,10 +161,11 @@ type DebugOptions struct {
|
|||
// NewDebugOptions returns a DebugOptions initialized with default values.
|
||||
func NewDebugOptions(streams genericiooptions.IOStreams) *DebugOptions {
|
||||
return &DebugOptions{
|
||||
Args: []string{},
|
||||
IOStreams: streams,
|
||||
TargetNames: []string{},
|
||||
ShareProcesses: true,
|
||||
Args: []string{},
|
||||
IOStreams: streams,
|
||||
KeepInitContainers: true,
|
||||
TargetNames: []string{},
|
||||
ShareProcesses: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,6 +200,12 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
|
|||
cmd.Flags().BoolVar(&o.Replace, "replace", o.Replace, i18n.T("When used with '--copy-to', delete the original Pod."))
|
||||
cmd.Flags().StringToString("env", nil, i18n.T("Environment variables to set in the container."))
|
||||
cmd.Flags().StringVar(&o.Image, "image", o.Image, i18n.T("Container image to use for debug container."))
|
||||
cmd.Flags().BoolVar(&o.KeepLabels, "keep-labels", o.KeepLabels, i18n.T("If true, keep the original pod labels.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().BoolVar(&o.KeepAnnotations, "keep-annotations", o.KeepAnnotations, i18n.T("If true, keep the original pod annotations.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().BoolVar(&o.KeepLiveness, "keep-liveness", o.KeepLiveness, i18n.T("If true, keep the original pod liveness probes.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().BoolVar(&o.KeepReadiness, "keep-readiness", o.KeepReadiness, i18n.T("If true, keep the original pod readiness probes.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().BoolVar(&o.KeepStartup, "keep-startup", o.KeepStartup, i18n.T("If true, keep the original startup probes.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().BoolVar(&o.KeepInitContainers, "keep-init-containers", o.KeepInitContainers, i18n.T("Run the init containers for the pod. Defaults to true.(This flag only works when used with '--copy-to')"))
|
||||
cmd.Flags().StringToStringVar(&o.SetImages, "set-image", o.SetImages, i18n.T("When used with '--copy-to', a list of name=image pairs for changing container images, similar to how 'kubectl set image' works."))
|
||||
cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server."))
|
||||
cmd.Flags().BoolVarP(&o.Interactive, "stdin", "i", o.Interactive, i18n.T("Keep stdin open on the container(s) in the pod, even if nothing is attached."))
|
||||
|
@ -198,9 +215,7 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
|
|||
cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
|
||||
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
|
||||
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
|
||||
if cmdutil.DebugCustomProfile.IsEnabled() {
|
||||
cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON file containing a partial container spec to customize built-in debug profiles."))
|
||||
}
|
||||
cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON or YAML file containing a partial container spec to customize built-in debug profiles."))
|
||||
}
|
||||
|
||||
// Complete finishes run-time initialization of debug.DebugOptions.
|
||||
|
@ -257,7 +272,15 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
|
|||
}
|
||||
|
||||
if o.Applier == nil {
|
||||
applier, err := NewProfileApplier(o.Profile)
|
||||
kflags := KeepFlags{
|
||||
Labels: o.KeepLabels,
|
||||
Annotations: o.KeepAnnotations,
|
||||
Liveness: o.KeepLiveness,
|
||||
Readiness: o.KeepReadiness,
|
||||
Startup: o.KeepStartup,
|
||||
InitContainers: o.KeepInitContainers,
|
||||
}
|
||||
applier, err := NewProfileApplier(o.Profile, kflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -272,7 +295,10 @@ func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGet
|
|||
|
||||
err = json.Unmarshal(customProfileBytes, &o.CustomProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
|
||||
err = yaml.Unmarshal(customProfileBytes, &o.CustomProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s does not contain a valid container spec: %w", o.CustomProfileFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,6 +400,11 @@ func (o *DebugOptions) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Warning for legacy profile
|
||||
if o.Profile == ProfileLegacy {
|
||||
fmt.Fprintln(o.ErrOut, `--profile=legacy is deprecated and will be removed in the future. It is recommended to explicitly specify a profile, for example "--profile=general".`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -467,6 +498,8 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
|
|||
}
|
||||
klog.V(2).Infof("new ephemeral container: %#v", debugContainer)
|
||||
|
||||
o.displayWarning((*corev1.Container)(&debugContainer.EphemeralContainerCommon), pod)
|
||||
|
||||
debugJS, err := json.Marshal(debugPod)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error creating JSON for debug container: %v", err)
|
||||
|
@ -583,6 +616,16 @@ func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev
|
|||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var debugContainer *corev1.Container
|
||||
for i := range copied.Spec.Containers {
|
||||
if copied.Spec.Containers[i].Name == dc {
|
||||
debugContainer = &copied.Spec.Containers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
o.displayWarning(debugContainer, copied)
|
||||
|
||||
created, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
@ -596,6 +639,32 @@ func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev
|
|||
return created, dc, nil
|
||||
}
|
||||
|
||||
// Display warning message if some capabilities are set by profile and non-root user is specified in .Spec.SecurityContext.RunAsUser.(#1650)
|
||||
func (o *DebugOptions) displayWarning(container *corev1.Container, pod *corev1.Pod) {
|
||||
if container == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext.RunAsUser == nil || *pod.Spec.SecurityContext.RunAsUser == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if container.SecurityContext == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if (container.SecurityContext.Privileged == nil || !*container.SecurityContext.Privileged) &&
|
||||
(container.SecurityContext.Capabilities == nil || len(container.SecurityContext.Capabilities.Add) == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(o.ErrOut, `Warning: Non-root user is configured for the entire target Pod, and some capabilities granted by debug profile may not work. Please consider using "--custom" with a custom profile that specifies "securityContext.runAsUser: 0".`)
|
||||
}
|
||||
|
||||
// generateDebugContainer returns a debugging pod and an EphemeralContainer suitable for use as a debug container
|
||||
// in the given pod.
|
||||
func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *corev1.EphemeralContainer, error) {
|
||||
|
@ -708,6 +777,7 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
|
|||
Name: o.CopyTo,
|
||||
Namespace: pod.Namespace,
|
||||
Annotations: pod.Annotations,
|
||||
Labels: pod.Labels,
|
||||
},
|
||||
Spec: *pod.Spec.DeepCopy(),
|
||||
}
|
||||
|
@ -715,7 +785,7 @@ func (o *DebugOptions) generatePodCopyWithDebugContainer(pod *corev1.Pod) (*core
|
|||
copied.Spec.EphemeralContainers = nil
|
||||
// change ShareProcessNamespace configuration only when commanded explicitly
|
||||
if o.shareProcessedChanged {
|
||||
copied.Spec.ShareProcessNamespace = pointer.Bool(o.ShareProcesses)
|
||||
copied.Spec.ShareProcessNamespace = ptr.To(o.ShareProcesses)
|
||||
}
|
||||
if !o.SameNode {
|
||||
copied.Spec.NodeName = ""
|
||||
|
@ -916,12 +986,12 @@ func (o *DebugOptions) handleAttachPod(ctx context.Context, restClientGetter gen
|
|||
}
|
||||
if status.State.Terminated != nil {
|
||||
klog.V(1).Info("Ephemeral container terminated, falling back to logs")
|
||||
return logOpts(restClientGetter, pod, opts)
|
||||
return logOpts(ctx, restClientGetter, pod, opts)
|
||||
}
|
||||
|
||||
if err := opts.Run(); err != nil {
|
||||
fmt.Fprintf(opts.ErrOut, "warning: couldn't attach to pod/%s, falling back to streaming logs: %v\n", podName, err)
|
||||
return logOpts(restClientGetter, pod, opts)
|
||||
return logOpts(ctx, restClientGetter, pod, opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -939,7 +1009,7 @@ func getContainerStatusByName(pod *corev1.Pod, containerName string) *corev1.Con
|
|||
}
|
||||
|
||||
// logOpts logs output from opts to the pods log.
|
||||
func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
|
||||
func logOpts(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
|
||||
ctrName, err := opts.GetContainerName(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -950,7 +1020,7 @@ func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Po
|
|||
return err
|
||||
}
|
||||
for _, request := range requests {
|
||||
if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
|
||||
if err := logs.DefaultConsumeRequest(ctx, request, opts.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -32,7 +30,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestGenerateDebugContainer(t *testing.T) {
|
||||
|
@ -287,11 +285,11 @@ func TestGenerateDebugContainer(t *testing.T) {
|
|||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -332,25 +330,31 @@ func TestGenerateDebugContainer(t *testing.T) {
|
|||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
suffixCounter = 0
|
||||
|
||||
if tc.pod == nil {
|
||||
tc.pod = &corev1.Pod{}
|
||||
var err error
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
|
||||
applier, err := NewProfileApplier(tc.opts.Profile)
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.Applier = applier
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
suffixCounter = 0
|
||||
if tc.pod == nil {
|
||||
tc.pod = &corev1.Pod{}
|
||||
}
|
||||
|
||||
_, debugContainer, err := tc.opts.generateDebugContainer(tc.pod)
|
||||
if err != nil {
|
||||
|
@ -426,6 +430,9 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
havePod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "target",
|
||||
Labels: map[string]string{
|
||||
"app": "business",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -792,13 +799,63 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pod with probes",
|
||||
opts: &DebugOptions{
|
||||
CopyTo: "debugger",
|
||||
Container: "debugger",
|
||||
Image: "busybox",
|
||||
KeepLiveness: true,
|
||||
KeepReadiness: true,
|
||||
KeepStartup: true,
|
||||
PullPolicy: corev1.PullIfNotPresent,
|
||||
Profile: ProfileLegacy,
|
||||
},
|
||||
havePod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "target",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "business",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "debugger",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "business",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "debugger",
|
||||
Image: "busybox",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pod with init containers",
|
||||
opts: &DebugOptions{
|
||||
CopyTo: "debugger",
|
||||
Image: "busybox",
|
||||
PullPolicy: corev1.PullIfNotPresent,
|
||||
Profile: ProfileLegacy,
|
||||
CopyTo: "debugger",
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
PullPolicy: corev1.PullIfNotPresent,
|
||||
Profile: ProfileLegacy,
|
||||
},
|
||||
havePod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -937,7 +994,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1182,7 +1239,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1220,7 +1277,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1260,7 +1317,7 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(false),
|
||||
ShareProcessNamespace: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1297,16 +1354,16 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
Image: "busybox",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1349,14 +1406,22 @@ func TestGeneratePodCopyWithDebugContainer(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
|
@ -1668,11 +1733,11 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
TerminationMessagePolicy: corev1.TerminationMessageReadFile,
|
||||
VolumeMounts: nil,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -1739,7 +1804,15 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
|
@ -1783,7 +1856,7 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1802,7 +1875,7 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -1855,11 +1928,11 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -1983,7 +2056,7 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
},
|
||||
},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2013,23 +2086,29 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
} {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
|
||||
var err error
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
var err error
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
|
||||
pod, err := tc.opts.generateNodeDebugPod(tc.node)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Name = pod.Name
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
pod, err := tc.opts.generateNodeDebugPod(tc.node)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Name = pod.Name
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2062,7 +2141,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2079,7 +2158,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2090,7 +2169,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
HostNetwork: false,
|
||||
HostPID: false,
|
||||
Volumes: nil,
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2115,7 +2194,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2132,8 +2211,8 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2148,7 +2227,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
HostNetwork: false,
|
||||
HostPID: false,
|
||||
Volumes: nil,
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2173,7 +2252,7 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2190,8 +2269,8 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2206,30 +2285,36 @@ func TestGenerateCopyDebugPodCustomProfile(t *testing.T) {
|
|||
HostNetwork: false,
|
||||
HostPID: false,
|
||||
Volumes: nil,
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
|
||||
var err error
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
var err error
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
|
||||
pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Spec.Containers[0].Name = dc
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
pod, dc, err := tc.opts.generatePodCopyWithDebugContainer(tc.copyPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Spec.Containers[0].Name = dc
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2262,7 +2347,7 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2281,7 +2366,7 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2317,7 +2402,7 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2336,8 +2421,8 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2377,7 +2462,7 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2396,8 +2481,8 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
Stdin: true,
|
||||
TTY: false,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
RunAsNonRoot: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
RunAsNonRoot: ptr.To(false),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
|
@ -2419,23 +2504,29 @@ func TestGenerateEphemeralDebugPodCustomProfile(t *testing.T) {
|
|||
} {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.DebugCustomProfile}, t, func(t *testing.T) {
|
||||
var err error
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
var err error
|
||||
kflags := KeepFlags{
|
||||
Labels: tc.opts.KeepLabels,
|
||||
Annotations: tc.opts.KeepAnnotations,
|
||||
Liveness: tc.opts.KeepLiveness,
|
||||
Readiness: tc.opts.KeepReadiness,
|
||||
Startup: tc.opts.KeepStartup,
|
||||
InitContainers: tc.opts.KeepInitContainers,
|
||||
}
|
||||
tc.opts.Applier, err = NewProfileApplier(tc.opts.Profile, kflags)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to create profile applier: %s: %v", tc.opts.Profile, err)
|
||||
}
|
||||
tc.opts.IOStreams = genericiooptions.NewTestIOStreamsDiscard()
|
||||
|
||||
pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
pod, ec, err := tc.opts.generateDebugContainer(tc.copyPod)
|
||||
if err != nil {
|
||||
t.Fatalf("Fail to generate node debug pod: %v", err)
|
||||
}
|
||||
tc.expected.Spec.EphemeralContainers[0].Name = ec.Name
|
||||
if diff := cmp.Diff(tc.expected, pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2486,94 +2577,101 @@ func TestCompleteAndValidate(t *testing.T) {
|
|||
name: "Set image pull policy",
|
||||
args: "--image=busybox --image-pull-policy=Always mypod",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
PullPolicy: corev1.PullPolicy("Always"),
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{},
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
PullPolicy: corev1.PullPolicy("Always"),
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple targets",
|
||||
args: "--image=busybox mypod1 mypod2",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod1", "mypod2"},
|
||||
Args: []string{},
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod1", "mypod2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Arguments with dash",
|
||||
args: "--image=busybox mypod1 mypod2 -- echo 1 2",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{"echo", "1", "2"},
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod1", "mypod2"},
|
||||
Args: []string{"echo", "1", "2"},
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod1", "mypod2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Interactive no attach",
|
||||
args: "-ti --image=busybox --attach=false mypod",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: false,
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
Args: []string{},
|
||||
Attach: false,
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set environment variables",
|
||||
args: "--image=busybox --env=FOO=BAR mypod",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Env: []corev1.EnvVar{{Name: "FOO", Value: "BAR"}},
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{},
|
||||
Env: []corev1.EnvVar{{Name: "FOO", Value: "BAR"}},
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ephemeral container: interactive session minimal args",
|
||||
args: "mypod -it --image=busybox",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ephemeral container: non-interactive debugger with image and name",
|
||||
args: "--image=myproj/debug-tools --image-pull-policy=Always -c debugger mypod",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Container: "debugger",
|
||||
Image: "myproj/debug-tools",
|
||||
Namespace: "test",
|
||||
PullPolicy: corev1.PullPolicy("Always"),
|
||||
Profile: ProfileLegacy,
|
||||
ShareProcesses: true,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{},
|
||||
Container: "debugger",
|
||||
Image: "myproj/debug-tools",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
PullPolicy: corev1.PullPolicy("Always"),
|
||||
Profile: ProfileLegacy,
|
||||
ShareProcesses: true,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2605,67 +2703,72 @@ func TestCompleteAndValidate(t *testing.T) {
|
|||
name: "Pod copy: interactive debug container minimal args",
|
||||
args: "mypod -it --image=busybox --copy-to=my-debugger",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pod copy: non-interactive with debug container, image name and command",
|
||||
args: "mypod --image=busybox --container=my-container --copy-to=my-debugger -- sleep 1d",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{"sleep", "1d"},
|
||||
Container: "my-container",
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{"sleep", "1d"},
|
||||
Container: "my-container",
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pod copy: explicit attach",
|
||||
args: "mypod --image=busybox --copy-to=my-debugger --attach -- sleep 1d",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{"sleep", "1d"},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{"sleep", "1d"},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pod copy: replace single image of existing container",
|
||||
args: "mypod --image=busybox --container=my-container --copy-to=my-debugger",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Container: "my-container",
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
Args: []string{},
|
||||
Container: "my-container",
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pod copy: mutate existing container images",
|
||||
args: "mypod --set-image=*=busybox,app=app-debugger --copy-to=my-debugger",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
CopyTo: "my-debugger",
|
||||
Namespace: "test",
|
||||
Args: []string{},
|
||||
CopyTo: "my-debugger",
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
SetImages: map[string]string{
|
||||
"*": "busybox",
|
||||
"app": "app-debugger",
|
||||
|
@ -2679,12 +2782,13 @@ func TestCompleteAndValidate(t *testing.T) {
|
|||
name: "Pod copy: add container and also mutate images",
|
||||
args: "mypod -it --copy-to=my-debugger --image=debian --set-image=app=app:debug,sidecar=sidecar:debug",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "debian",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "debian",
|
||||
Interactive: true,
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
SetImages: map[string]string{
|
||||
"app": "app:debug",
|
||||
"sidecar": "sidecar:debug",
|
||||
|
@ -2699,16 +2803,39 @@ func TestCompleteAndValidate(t *testing.T) {
|
|||
name: "Pod copy: change command",
|
||||
args: "mypod -it --copy-to=my-debugger --container=mycontainer -- sh",
|
||||
wantOpts: &DebugOptions{
|
||||
Attach: true,
|
||||
Args: []string{"sh"},
|
||||
Container: "mycontainer",
|
||||
CopyTo: "my-debugger",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
Attach: true,
|
||||
Args: []string{"sh"},
|
||||
Container: "mycontainer",
|
||||
CopyTo: "my-debugger",
|
||||
Interactive: true,
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pod copy: change keep options from defaults",
|
||||
args: "mypod -it --image=busybox --copy-to=my-debugger --keep-labels=true --keep-annotations=true --keep-liveness=true --keep-readiness=true --keep-startup=true --keep-init-containers=false",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
CopyTo: "my-debugger",
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
KeepLabels: true,
|
||||
KeepAnnotations: true,
|
||||
KeepLiveness: true,
|
||||
KeepReadiness: true,
|
||||
KeepStartup: true,
|
||||
KeepInitContainers: false,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"mypod"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2740,15 +2867,16 @@ func TestCompleteAndValidate(t *testing.T) {
|
|||
name: "Node: interactive session minimal args",
|
||||
args: "node/mynode -it --image=busybox",
|
||||
wantOpts: &DebugOptions{
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"node/mynode"},
|
||||
TTY: true,
|
||||
Args: []string{},
|
||||
Attach: true,
|
||||
Image: "busybox",
|
||||
Interactive: true,
|
||||
KeepInitContainers: true,
|
||||
Namespace: "test",
|
||||
ShareProcesses: true,
|
||||
Profile: ProfileLegacy,
|
||||
TargetNames: []string{"node/mynode"},
|
||||
TTY: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubectl/pkg/util/podutils"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type debugStyle int
|
||||
|
@ -64,54 +64,91 @@ type ProfileApplier interface {
|
|||
}
|
||||
|
||||
// NewProfileApplier returns a new Options for the given profile name.
|
||||
func NewProfileApplier(profile string) (ProfileApplier, error) {
|
||||
func NewProfileApplier(profile string, kflags KeepFlags) (ProfileApplier, error) {
|
||||
switch profile {
|
||||
case ProfileLegacy:
|
||||
return &legacyProfile{}, nil
|
||||
return &legacyProfile{kflags}, nil
|
||||
case ProfileGeneral:
|
||||
return &generalProfile{}, nil
|
||||
return &generalProfile{kflags}, nil
|
||||
case ProfileBaseline:
|
||||
return &baselineProfile{}, nil
|
||||
return &baselineProfile{kflags}, nil
|
||||
case ProfileRestricted:
|
||||
return &restrictedProfile{}, nil
|
||||
return &restrictedProfile{kflags}, nil
|
||||
case ProfileNetadmin:
|
||||
return &netadminProfile{}, nil
|
||||
return &netadminProfile{kflags}, nil
|
||||
case ProfileSysadmin:
|
||||
return &sysadminProfile{}, nil
|
||||
return &sysadminProfile{kflags}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown profile: %s", profile)
|
||||
}
|
||||
|
||||
type legacyProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
type generalProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
type baselineProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
type restrictedProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
type netadminProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
type sysadminProfile struct {
|
||||
KeepFlags
|
||||
}
|
||||
|
||||
func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
switch target.(type) {
|
||||
case *corev1.Pod:
|
||||
// do nothing to the copied pod
|
||||
return nil
|
||||
case *corev1.Node:
|
||||
mountRootPartition(pod, containerName)
|
||||
useHostNamespaces(pod)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("the %s profile doesn't support objects of type %T", ProfileLegacy, target)
|
||||
// KeepFlags holds the flag set that determine which fields to keep in the copy pod.
|
||||
type KeepFlags struct {
|
||||
Labels bool
|
||||
Annotations bool
|
||||
Liveness bool
|
||||
Readiness bool
|
||||
Startup bool
|
||||
InitContainers bool
|
||||
}
|
||||
|
||||
// RemoveLabels removes labels from the pod.
|
||||
func (kflags KeepFlags) RemoveLabels(p *corev1.Pod) {
|
||||
if !kflags.Labels {
|
||||
p.Labels = nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAnnotations remove annotations from the pod.
|
||||
func (kflags KeepFlags) RemoveAnnotations(p *corev1.Pod) {
|
||||
if !kflags.Annotations {
|
||||
p.Annotations = nil
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveProbes remove probes from all containers of the pod.
|
||||
func (kflags KeepFlags) RemoveProbes(p *corev1.Pod) {
|
||||
for i := range p.Spec.Containers {
|
||||
if !kflags.Liveness {
|
||||
p.Spec.Containers[i].LivenessProbe = nil
|
||||
}
|
||||
if !kflags.Readiness {
|
||||
p.Spec.Containers[i].ReadinessProbe = nil
|
||||
}
|
||||
if !kflags.Startup {
|
||||
p.Spec.Containers[i].StartupProbe = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveInitContainers remove initContainers from the pod.
|
||||
func (kflags KeepFlags) RemoveInitContainers(p *corev1.Pod) {
|
||||
if !kflags.InitContainers {
|
||||
p.Spec.InitContainers = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,10 +167,32 @@ func getDebugStyle(pod *corev1.Pod, target runtime.Object) (debugStyle, error) {
|
|||
return unsupported, fmt.Errorf("objects of type %T are not supported", target)
|
||||
}
|
||||
|
||||
func (p *legacyProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("legacy profile: %w", err)
|
||||
}
|
||||
|
||||
switch style {
|
||||
case node:
|
||||
mountRootPartition(pod, containerName)
|
||||
useHostNamespaces(pod)
|
||||
|
||||
case podCopy:
|
||||
p.Labels = false
|
||||
p.RemoveLabels(pod)
|
||||
|
||||
case ephemeral:
|
||||
// no additional modifications needed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("general profile: %s", err)
|
||||
return fmt.Errorf("general profile: %w", err)
|
||||
}
|
||||
|
||||
switch style {
|
||||
|
@ -143,7 +202,10 @@ func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target run
|
|||
useHostNamespaces(pod)
|
||||
|
||||
case podCopy:
|
||||
removeLabelsAndProbes(pod)
|
||||
p.RemoveLabels(pod)
|
||||
p.RemoveAnnotations(pod)
|
||||
p.RemoveProbes(pod)
|
||||
p.RemoveInitContainers(pod)
|
||||
allowProcessTracing(pod, containerName)
|
||||
shareProcessNamespace(pod)
|
||||
|
||||
|
@ -157,14 +219,17 @@ func (p *generalProfile) Apply(pod *corev1.Pod, containerName string, target run
|
|||
func (p *baselineProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("baseline profile: %s", err)
|
||||
return fmt.Errorf("baseline profile: %w", err)
|
||||
}
|
||||
|
||||
clearSecurityContext(pod, containerName)
|
||||
|
||||
switch style {
|
||||
case podCopy:
|
||||
removeLabelsAndProbes(pod)
|
||||
p.RemoveLabels(pod)
|
||||
p.RemoveAnnotations(pod)
|
||||
p.RemoveProbes(pod)
|
||||
p.RemoveInitContainers(pod)
|
||||
shareProcessNamespace(pod)
|
||||
|
||||
case ephemeral, node:
|
||||
|
@ -177,7 +242,7 @@ func (p *baselineProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
|||
func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("restricted profile: %s", err)
|
||||
return fmt.Errorf("restricted profile: %w", err)
|
||||
}
|
||||
|
||||
clearSecurityContext(pod, containerName)
|
||||
|
@ -188,6 +253,10 @@ func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target
|
|||
|
||||
switch style {
|
||||
case podCopy:
|
||||
p.RemoveLabels(pod)
|
||||
p.RemoveAnnotations(pod)
|
||||
p.RemoveProbes(pod)
|
||||
p.RemoveInitContainers(pod)
|
||||
shareProcessNamespace(pod)
|
||||
|
||||
case ephemeral, node:
|
||||
|
@ -200,7 +269,7 @@ func (p *restrictedProfile) Apply(pod *corev1.Pod, containerName string, target
|
|||
func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netadmin profile: %s", err)
|
||||
return fmt.Errorf("netadmin profile: %w", err)
|
||||
}
|
||||
|
||||
allowNetadminCapability(pod, containerName)
|
||||
|
@ -210,6 +279,10 @@ func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
|||
useHostNamespaces(pod)
|
||||
|
||||
case podCopy:
|
||||
p.RemoveLabels(pod)
|
||||
p.RemoveAnnotations(pod)
|
||||
p.RemoveProbes(pod)
|
||||
p.RemoveInitContainers(pod)
|
||||
shareProcessNamespace(pod)
|
||||
|
||||
case ephemeral:
|
||||
|
@ -222,7 +295,7 @@ func (p *netadminProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
|||
func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target runtime.Object) error {
|
||||
style, err := getDebugStyle(pod, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sysadmin profile: %s", err)
|
||||
return fmt.Errorf("sysadmin profile: %w", err)
|
||||
}
|
||||
|
||||
setPrivileged(pod, containerName)
|
||||
|
@ -234,7 +307,12 @@ func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
|||
|
||||
case podCopy:
|
||||
// to mimic general, default and baseline
|
||||
p.RemoveLabels(pod)
|
||||
p.RemoveAnnotations(pod)
|
||||
p.RemoveProbes(pod)
|
||||
p.RemoveInitContainers(pod)
|
||||
shareProcessNamespace(pod)
|
||||
|
||||
case ephemeral:
|
||||
// no additional modifications needed
|
||||
}
|
||||
|
@ -242,17 +320,6 @@ func (p *sysadminProfile) Apply(pod *corev1.Pod, containerName string, target ru
|
|||
return nil
|
||||
}
|
||||
|
||||
// removeLabelsAndProbes removes labels from the pod and remove probes
|
||||
// from all containers of the pod.
|
||||
func removeLabelsAndProbes(p *corev1.Pod) {
|
||||
p.Labels = nil
|
||||
for i := range p.Spec.Containers {
|
||||
p.Spec.Containers[i].LivenessProbe = nil
|
||||
p.Spec.Containers[i].ReadinessProbe = nil
|
||||
p.Spec.Containers[i].StartupProbe = nil
|
||||
}
|
||||
}
|
||||
|
||||
// mountRootPartition mounts the host's root path at "/host" in the container.
|
||||
func mountRootPartition(p *corev1.Pod, containerName string) {
|
||||
const volumeName = "host-root"
|
||||
|
@ -286,7 +353,7 @@ func useHostNamespaces(p *corev1.Pod) {
|
|||
// process namespace.
|
||||
func shareProcessNamespace(p *corev1.Pod) {
|
||||
if p.Spec.ShareProcessNamespace == nil {
|
||||
p.Spec.ShareProcessNamespace = pointer.Bool(true)
|
||||
p.Spec.ShareProcessNamespace = ptr.To(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,7 +377,7 @@ func setPrivileged(p *corev1.Pod, containerName string) {
|
|||
if c.SecurityContext == nil {
|
||||
c.SecurityContext = &corev1.SecurityContext{}
|
||||
}
|
||||
c.SecurityContext.Privileged = pointer.Bool(true)
|
||||
c.SecurityContext.Privileged = ptr.To(true)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
@ -324,7 +391,7 @@ func disallowRoot(p *corev1.Pod, containerName string) {
|
|||
if c.SecurityContext == nil {
|
||||
c.SecurityContext = &corev1.SecurityContext{}
|
||||
}
|
||||
c.SecurityContext.RunAsNonRoot = pointer.Bool(true)
|
||||
c.SecurityContext.RunAsNonRoot = ptr.To(true)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
@ -389,7 +456,7 @@ func disallowPrivilegeEscalation(p *corev1.Pod, containerName string) {
|
|||
if c.SecurityContext == nil {
|
||||
c.SecurityContext = &corev1.SecurityContext{}
|
||||
}
|
||||
c.SecurityContext.AllowPrivilegeEscalation = pointer.Bool(false)
|
||||
c.SecurityContext.AllowPrivilegeEscalation = ptr.To(false)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
var testNode = &corev1.Node{
|
||||
|
@ -34,6 +34,203 @@ var testNode = &corev1.Node{
|
|||
},
|
||||
}
|
||||
|
||||
func TestLegacyProfile(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "dbg", Image: "dbgimage",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
pod *corev1.Pod
|
||||
containerName string
|
||||
target runtime.Object
|
||||
expectPod *corev1.Pod
|
||||
expectErr bool
|
||||
}{
|
||||
"bad inputs results in error": {
|
||||
pod: nil,
|
||||
containerName: "dbg",
|
||||
target: runtime.Object(nil),
|
||||
expectErr: true,
|
||||
},
|
||||
"debug by ephemeral container": {
|
||||
pod: pod,
|
||||
containerName: "dbg",
|
||||
target: pod,
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
"debug by pod copy": {
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"debug by node": {
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: testNode,
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
HostPID: true,
|
||||
HostIPC: true,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
MountPath: "/host",
|
||||
Name: "host-root",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "host-root",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{Path: "/"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
applier := &legacyProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err != nil) != test.expectErr {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(test.expectPod, test.pod); diff != "" {
|
||||
t.Error("unexpected diff in generated object: (-want +got):\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneralProfile(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "pod"},
|
||||
|
@ -81,10 +278,25 @@ func TestGeneralProfile(t *testing.T) {
|
|||
},
|
||||
"debug by pod copy": {
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
|
@ -99,16 +311,32 @@ func TestGeneralProfile(t *testing.T) {
|
|||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
|
@ -121,7 +349,7 @@ func TestGeneralProfile(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -169,7 +397,8 @@ func TestGeneralProfile(t *testing.T) {
|
|||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := (&generalProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
applier := &generalProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err != nil) != test.expectErr {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil))
|
||||
}
|
||||
|
@ -230,27 +459,58 @@ func TestBaselineProfile(t *testing.T) {
|
|||
},
|
||||
"debug by pod copy": {
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
|
@ -288,7 +548,8 @@ func TestBaselineProfile(t *testing.T) {
|
|||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := (&baselineProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
applier := &baselineProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err != nil) != test.expectErr {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil))
|
||||
}
|
||||
|
@ -343,11 +604,11 @@ func TestRestrictedProfile(t *testing.T) {
|
|||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "dbg", Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -357,38 +618,69 @@ func TestRestrictedProfile(t *testing.T) {
|
|||
},
|
||||
"debug by pod copy": {
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -408,7 +700,7 @@ func TestRestrictedProfile(t *testing.T) {
|
|||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -425,11 +717,11 @@ func TestRestrictedProfile(t *testing.T) {
|
|||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: pointer.Bool(true),
|
||||
RunAsNonRoot: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
AllowPrivilegeEscalation: pointer.Bool(false),
|
||||
AllowPrivilegeEscalation: ptr.To(false),
|
||||
SeccompProfile: &corev1.SeccompProfile{Type: "RuntimeDefault"},
|
||||
},
|
||||
},
|
||||
|
@ -441,7 +733,8 @@ func TestRestrictedProfile(t *testing.T) {
|
|||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := (&restrictedProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
applier := &restrictedProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err != nil) != test.expectErr {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, (err != nil))
|
||||
}
|
||||
|
@ -506,27 +799,58 @@ func TestNetAdminProfile(t *testing.T) {
|
|||
{
|
||||
name: "debug by pod copy",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
|
@ -548,7 +872,13 @@ func TestNetAdminProfile(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
|
@ -566,14 +896,20 @@ func TestNetAdminProfile(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
|
@ -665,7 +1001,8 @@ func TestNetAdminProfile(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := (&netadminProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
applier := &netadminProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err == nil) != (test.expectErr == nil) || (err != nil && test.expectErr != nil && err.Error() != test.expectErr.Error()) {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, err)
|
||||
}
|
||||
|
@ -718,7 +1055,7 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "dbg", Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -728,37 +1065,68 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
{
|
||||
name: "debug by pod copy",
|
||||
pod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{Name: "dbg", Image: "dbgimage"},
|
||||
},
|
||||
},
|
||||
},
|
||||
containerName: "dbg",
|
||||
target: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "podcopy",
|
||||
Labels: map[string]string{
|
||||
"app": "podcopy",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test": "test",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPod: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{{Name: "init-container"}},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -768,7 +1136,13 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
{
|
||||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
|
@ -786,7 +1160,13 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
ObjectMeta: metav1.ObjectMeta{Name: "podcopy"},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "app", Image: "appimage"},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "appimage",
|
||||
LivenessProbe: &corev1.Probe{},
|
||||
ReadinessProbe: &corev1.Probe{},
|
||||
StartupProbe: &corev1.Probe{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -799,14 +1179,14 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ShareProcessNamespace: pointer.Bool(true),
|
||||
ShareProcessNamespace: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -839,7 +1219,7 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{Name: "host-root", MountPath: "/host"}},
|
||||
},
|
||||
|
@ -884,7 +1264,7 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
Name: "dbg",
|
||||
Image: "dbgimage",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
Privileged: ptr.To(true),
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"SYS_PTRACE"},
|
||||
},
|
||||
|
@ -899,7 +1279,8 @@ func TestSysAdminProfile(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := (&sysadminProfile{}).Apply(test.pod, test.containerName, test.target)
|
||||
applier := &sysadminProfile{KeepFlags{InitContainers: true}}
|
||||
err := applier.Apply(test.pod, test.containerName, test.target)
|
||||
if (err == nil) != (test.expectErr == nil) || (err != nil && test.expectErr != nil && err.Error() != test.expectErr.Error()) {
|
||||
t.Fatalf("expect error: %v, got error: %v", test.expectErr, err)
|
||||
}
|
||||
|
|
|
@ -103,7 +103,10 @@ var (
|
|||
kubectl delete pod foo --force
|
||||
|
||||
# Delete all pods
|
||||
kubectl delete pods --all`))
|
||||
kubectl delete pods --all
|
||||
|
||||
# Delete all pods only if the user confirms the deletion
|
||||
kubectl delete pods --all --interactive`))
|
||||
)
|
||||
|
||||
type DeleteOptions struct {
|
||||
|
@ -521,7 +524,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)
|
||||
|
@ -529,11 +532,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.Fprintf(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
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import (
|
|||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func fakecmd() *cobra.Command {
|
||||
|
@ -60,8 +60,8 @@ func TestDeleteFlagValidation(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
flags: DeleteFlags{
|
||||
Raw: pointer.String("test"),
|
||||
Interactive: pointer.Bool(true),
|
||||
Raw: ptr.To("test"),
|
||||
Interactive: ptr.To(true),
|
||||
},
|
||||
expectedErr: "--interactive can not be used with --raw",
|
||||
},
|
||||
|
@ -306,7 +306,7 @@ func TestDeleteObject(t *testing.T) {
|
|||
|
||||
func TestPreviewResultEqualToResult(t *testing.T) {
|
||||
deleteFlags := NewDeleteCommandFlags("")
|
||||
deleteFlags.Interactive = pointer.Bool(true)
|
||||
deleteFlags.Interactive = ptr.To(true)
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
|
@ -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" {
|
||||
|
|
|
@ -193,7 +193,7 @@ func (o *DescribeOptions) Run() error {
|
|||
allErrs = append(allErrs, err)
|
||||
}
|
||||
|
||||
errs := sets.NewString()
|
||||
errs := sets.New[string]()
|
||||
first := true
|
||||
for _, info := range infos {
|
||||
mapping := info.ResourceMapping()
|
||||
|
|
|
@ -161,7 +161,7 @@ func NewCmdDiff(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co
|
|||
// command it means changes were found.
|
||||
// Thus, it should return status code greater than 1.
|
||||
cmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error {
|
||||
cmdutil.CheckDiffErr(cmdutil.UsageErrorf(cmd, err.Error()))
|
||||
cmdutil.CheckDiffErr(cmdutil.UsageErrorf(cmd, "%s", err.Error()))
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -65,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)
|
||||
|
@ -130,7 +130,7 @@ func TestDiffVersion(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fcontent, err := os.ReadFile(path.Join(diff.Dir.Name, obj.Name()))
|
||||
fcontent, err := os.ReadFile(filepath.Join(diff.Dir.Name, obj.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func TestDiffer(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fcontent, err := os.ReadFile(path.Join(diff.From.Dir.Name, obj.Name()))
|
||||
fcontent, err := os.ReadFile(filepath.Join(diff.From.Dir.Name, obj.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ func TestDiffer(t *testing.T) {
|
|||
t.Fatalf("File has %q, expected %q", string(fcontent), econtent)
|
||||
}
|
||||
|
||||
fcontent, err = os.ReadFile(path.Join(diff.To.Dir.Name, obj.Name()))
|
||||
fcontent, err = os.ReadFile(filepath.Join(diff.To.Dir.Name, obj.Name()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -286,12 +286,12 @@ metadata:
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actualFromContent, _ := os.ReadFile(path.Join(diff.From.Dir.Name, obj.Name()))
|
||||
actualFromContent, _ := os.ReadFile(filepath.Join(diff.From.Dir.Name, obj.Name()))
|
||||
if string(actualFromContent) != tc.expectedFromContent {
|
||||
t.Fatalf("File has %q, expected %q", string(actualFromContent), tc.expectedFromContent)
|
||||
}
|
||||
|
||||
actualToContent, _ := os.ReadFile(path.Join(diff.To.Dir.Name, obj.Name()))
|
||||
actualToContent, _ := os.ReadFile(filepath.Join(diff.To.Dir.Name, obj.Name()))
|
||||
if string(actualToContent) != tc.expectedToContent {
|
||||
t.Fatalf("File has %q, expected %q", string(actualToContent), tc.expectedToContent)
|
||||
}
|
||||
|
|
|
@ -224,8 +224,6 @@ func NewCmdDrain(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra
|
|||
}
|
||||
cmd.Flags().BoolVar(&o.drainer.Force, "force", o.drainer.Force, "Continue even if there are pods that do not declare a controller.")
|
||||
cmd.Flags().BoolVar(&o.drainer.IgnoreAllDaemonSets, "ignore-daemonsets", o.drainer.IgnoreAllDaemonSets, "Ignore DaemonSet-managed pods.")
|
||||
cmd.Flags().BoolVar(&o.drainer.DeleteEmptyDirData, "delete-local-data", o.drainer.DeleteEmptyDirData, "Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).")
|
||||
cmd.Flags().MarkDeprecated("delete-local-data", "This option is deprecated and will be deleted. Use --delete-emptydir-data.")
|
||||
cmd.Flags().BoolVar(&o.drainer.DeleteEmptyDirData, "delete-emptydir-data", o.drainer.DeleteEmptyDirData, "Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained).")
|
||||
cmd.Flags().IntVar(&o.drainer.GracePeriodSeconds, "grace-period", o.drainer.GracePeriodSeconds, "Period of time in seconds given to each pod to terminate gracefully. If negative, the default value specified in the pod will be used.")
|
||||
cmd.Flags().DurationVar(&o.drainer.Timeout, "timeout", o.drainer.Timeout, "The length of time to wait before giving up, zero means infinite")
|
||||
|
@ -245,7 +243,7 @@ func (o *DrainCmdOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
|
|||
var err error
|
||||
|
||||
if len(args) == 0 && !cmd.Flags().Changed("selector") {
|
||||
return cmdutil.UsageErrorf(cmd, fmt.Sprintf("USAGE: %s [flags]", cmd.Use))
|
||||
return cmdutil.UsageErrorf(cmd, "USAGE: %s [flags]", cmd.Use)
|
||||
}
|
||||
if len(args) > 0 && len(o.drainer.Selector) > 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "error: cannot specify both a node name and a --selector option")
|
||||
|
@ -328,7 +326,7 @@ func (o *DrainCmdOptions) RunDrain() error {
|
|||
return err
|
||||
}
|
||||
|
||||
drainedNodes := sets.NewString()
|
||||
drainedNodes := sets.New[string]()
|
||||
var fatal []error
|
||||
|
||||
remainingNodes := []string{}
|
||||
|
@ -343,7 +341,7 @@ func (o *DrainCmdOptions) RunDrain() error {
|
|||
|
||||
printObj(info.Object, o.Out)
|
||||
} else {
|
||||
fmt.Fprintf(o.ErrOut, "error: unable to drain node %q due to error:%s, continuing command...\n", info.Name, err)
|
||||
fmt.Fprintf(o.ErrOut, "error: unable to drain node %q due to error: %s, continuing command...\n", info.Name, err)
|
||||
|
||||
if !drainedNodes.Has(info.Name) {
|
||||
fatal = append(fatal, err)
|
||||
|
|
|
@ -42,7 +42,7 @@ import (
|
|||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/drain"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -277,8 +277,8 @@ func TestDrain(t *testing.T) {
|
|||
Kind: "ReplicationController",
|
||||
Name: "rc",
|
||||
UID: "123",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -309,8 +309,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "apps/v1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -330,8 +330,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "apps/v1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -354,8 +354,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "apps/v1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -404,8 +404,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "v1",
|
||||
Kind: "Job",
|
||||
Name: "job",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -431,8 +431,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "v1",
|
||||
Kind: "Job",
|
||||
Name: "job",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -473,8 +473,8 @@ func TestDrain(t *testing.T) {
|
|||
APIVersion: "v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "rs",
|
||||
BlockOwnerDeletion: utilpointer.BoolPtr(true),
|
||||
Controller: utilpointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: ptr.To(true),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -635,17 +635,6 @@ func TestDrain(t *testing.T) {
|
|||
expectDelete: true,
|
||||
expectOutputToContain: "node/node drained",
|
||||
},
|
||||
{
|
||||
description: "Ensure compatibility for --delete-local-data until fully deprecated",
|
||||
node: node,
|
||||
expected: cordonedNode,
|
||||
pods: []corev1.Pod{jobPod},
|
||||
rcs: []corev1.ReplicationController{rc},
|
||||
args: []string{"node", "--force", "--delete-local-data=true"},
|
||||
expectFatal: false,
|
||||
expectDelete: true,
|
||||
expectOutputToContain: "node/node drained",
|
||||
},
|
||||
{
|
||||
description: "Job-managed terminated pod",
|
||||
node: node,
|
||||
|
|
|
@ -103,6 +103,6 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.
|
|||
"Defaults to the line ending native to your platform.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-edit")
|
||||
cmdutil.AddApplyAnnotationVarFlags(cmd, &o.ApplyAnnotation)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, edit will operate on the subresource of the requested object.", editor.SupportedSubresources...)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, edit will operate on the subresource of the requested object.")
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -41,6 +39,7 @@ 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 {
|
||||
|
@ -173,7 +172,7 @@ func TestEdit(t *testing.T) {
|
|||
t.Setenv("KUBE_EDITOR", "testdata/test_editor.sh")
|
||||
t.Setenv("KUBE_EDITOR_CALLBACK", server.URL+"/callback")
|
||||
|
||||
testcases := sets.NewString()
|
||||
testcases := sets.New[string]()
|
||||
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -195,7 +194,7 @@ func TestEdit(t *testing.T) {
|
|||
t.Fatalf("Error locating edit testcases")
|
||||
}
|
||||
|
||||
for _, testcaseName := range testcases.List() {
|
||||
for _, testcaseName := range testcases.UnsortedList() {
|
||||
t.Run(testcaseName, func(t *testing.T) {
|
||||
i = 0
|
||||
name = testcaseName
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "sigs.k8s.io/yaml/goyaml.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
|
||||
|
|
|
@ -18,11 +18,12 @@ package events
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPrintObj(t *testing.T) {
|
||||
|
@ -50,7 +51,7 @@ func TestPrintObj(t *testing.T) {
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -59,7 +60,7 @@ func TestPrintObj(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: `LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -83,7 +84,7 @@ func TestPrintObj(t *testing.T) {
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -105,7 +106,7 @@ func TestPrintObj(t *testing.T) {
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -116,8 +117,8 @@ func TestPrintObj(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica set bar-002 to 1
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica set bar-002 from 0 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -139,7 +140,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -147,7 +148,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
|
|||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: "12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1\n",
|
||||
expected: "12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1\n",
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
|
@ -168,7 +169,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -177,7 +178,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
|
|||
},
|
||||
},
|
||||
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -199,7 +200,7 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -207,7 +208,7 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
|
|||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -188,7 +188,7 @@ func (flags *EventsFlags) ToOptions() (*EventsOptions, error) {
|
|||
}
|
||||
|
||||
if len(o.FilterTypes) > 0 {
|
||||
o.FilterTypes = sets.NewString(o.FilterTypes...).List()
|
||||
o.FilterTypes = sets.List(sets.New[string](o.FilterTypes...))
|
||||
}
|
||||
|
||||
var printer printers.ResourcePrinter
|
||||
|
|
|
@ -50,7 +50,7 @@ func getFakeEvents() *corev1.EventList {
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-30 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -72,7 +72,7 @@ func getFakeEvents() *corev1.EventList {
|
|||
},
|
||||
Type: corev1.EventTypeWarning,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-28 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -94,7 +94,7 @@ func getFakeEvents() *corev1.EventList {
|
|||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
Message: "Scaled up replica set bar-002 from 0 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-25 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
|
@ -135,9 +135,9 @@ func TestEventIsSorted(t *testing.T) {
|
|||
}
|
||||
|
||||
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
|
@ -172,9 +172,9 @@ func TestEventNoHeaders(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
expected := `foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
|
@ -211,7 +211,7 @@ func TestEventFiltered(t *testing.T) {
|
|||
}
|
||||
|
||||
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
|
|
|
@ -114,18 +114,26 @@ func NewCmdExec(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co
|
|||
|
||||
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
|
||||
type RemoteExecutor interface {
|
||||
// Execute supports executing remote command in a pod.
|
||||
Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
|
||||
|
||||
// ExecuteWithContext, in contrast to Execute, supports stopping the remote command via context cancellation.
|
||||
ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
|
||||
}
|
||||
|
||||
// DefaultRemoteExecutor is the standard implementation of remote command execution
|
||||
type DefaultRemoteExecutor struct{}
|
||||
|
||||
func (*DefaultRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
func (d *DefaultRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
return d.ExecuteWithContext(context.Background(), url, config, stdin, stdout, stderr, tty, terminalSizeQueue)
|
||||
}
|
||||
|
||||
func (*DefaultRemoteExecutor) ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
exec, err := createExecutor(url, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
|
||||
return exec.StreamWithContext(ctx, remotecommand.StreamOptions{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
|
@ -147,7 +155,9 @@ func createExecutor(url *url.URL, config *restclient.Config) (remotecommand.Exec
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exec, err = remotecommand.NewFallbackExecutor(websocketExec, exec, httpstream.IsUpgradeFailure)
|
||||
exec, err = remotecommand.NewFallbackExecutor(websocketExec, exec, func(err error) bool {
|
||||
return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -200,17 +210,8 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
|
|||
}
|
||||
if argsLenAtDash > -1 {
|
||||
p.Command = argsIn[argsLenAtDash:]
|
||||
} else if len(argsIn) > 1 {
|
||||
if !p.Quiet {
|
||||
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n")
|
||||
}
|
||||
p.Command = argsIn[1:]
|
||||
} else if len(argsIn) > 0 && len(p.FilenameOptions.Filenames) != 0 {
|
||||
if !p.Quiet {
|
||||
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n")
|
||||
}
|
||||
p.Command = argsIn[0:]
|
||||
p.ResourceName = ""
|
||||
} else if len(argsIn) > 1 || (len(argsIn) > 0 && len(p.FilenameOptions.Filenames) != 0) {
|
||||
return cmdutil.UsageErrorf(cmd, "exec [POD] [COMMAND] is not supported anymore. Use exec [POD] -- [COMMAND] instead")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -223,7 +224,7 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
|
|||
|
||||
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
|
||||
if err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, err.Error())
|
||||
return cmdutil.UsageErrorf(cmd, "%s", err.Error())
|
||||
}
|
||||
|
||||
p.Builder = f.NewBuilder
|
||||
|
|
|
@ -18,6 +18,7 @@ package exec
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -45,6 +46,10 @@ type fakeRemoteExecutor struct {
|
|||
}
|
||||
|
||||
func (f *fakeRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
return f.ExecuteWithContext(context.Background(), url, config, stdin, stdout, stderr, tty, terminalSizeQueue)
|
||||
}
|
||||
|
||||
func (f *fakeRemoteExecutor) ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
f.url = url
|
||||
return f.execErr
|
||||
}
|
||||
|
@ -110,20 +115,17 @@ func TestPodAndContainer(t *testing.T) {
|
|||
p: &ExecOptions{},
|
||||
args: []string{"foo", "cmd"},
|
||||
argsLenAtDash: -1,
|
||||
expectedPod: "foo",
|
||||
expectedArgs: []string{"cmd"},
|
||||
expectError: true,
|
||||
name: "cmd, cmd is behind dash",
|
||||
obj: execPod(),
|
||||
},
|
||||
{
|
||||
p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
|
||||
args: []string{"foo", "cmd"},
|
||||
argsLenAtDash: -1,
|
||||
expectedPod: "foo",
|
||||
expectedContainer: "bar",
|
||||
expectedArgs: []string{"cmd"},
|
||||
name: "cmd, container in flag",
|
||||
obj: execPod(),
|
||||
p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
|
||||
args: []string{"foo", "cmd"},
|
||||
argsLenAtDash: -1,
|
||||
expectError: true,
|
||||
name: "cmd, container in flag",
|
||||
obj: execPod(),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -238,8 +240,8 @@ func TestExec(t *testing.T) {
|
|||
Executor: ex,
|
||||
}
|
||||
cmd := NewCmdExec(tf, genericiooptions.NewTestIOStreamsDiscard())
|
||||
args := []string{"pod/foo", "command"}
|
||||
if err := params.Complete(tf, cmd, args, -1); err != nil {
|
||||
args := []string{"pod/foo", "--", "command"}
|
||||
if err := params.Complete(tf, cmd, args, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := params.Run()
|
||||
|
|
|
@ -56,83 +56,113 @@ var (
|
|||
|
||||
# Get the documentation of a specific field of a resource
|
||||
kubectl explain pods.spec.containers
|
||||
|
||||
|
||||
# Get the documentation of resources in different format
|
||||
kubectl explain deployment --output=plaintext-openapiv2`))
|
||||
)
|
||||
|
||||
const (
|
||||
plaintextTemplateName = "plaintext"
|
||||
plaintextOpenAPIV2TemplateName = "plaintext-openapiv2"
|
||||
)
|
||||
|
||||
type ExplainOptions struct {
|
||||
genericiooptions.IOStreams
|
||||
|
||||
CmdParent string
|
||||
APIVersion string
|
||||
Recursive bool
|
||||
|
||||
args []string
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
openAPIGetter openapi.OpenAPIResourcesGetter
|
||||
|
||||
// Name of the template to use with the openapiv3 template renderer.
|
||||
// ExplainFlags directly reflect the information that CLI is gathering via flags.
|
||||
// They will be converted to Options, which reflect the runtime requirements for
|
||||
// the command.
|
||||
type ExplainFlags struct {
|
||||
APIVersion string
|
||||
OutputFormat string
|
||||
Recursive bool
|
||||
|
||||
// Client capable of fetching openapi documents from the user's cluster
|
||||
OpenAPIV3Client openapiclient.Client
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
func NewExplainOptions(parent string, streams genericiooptions.IOStreams) *ExplainOptions {
|
||||
return &ExplainOptions{
|
||||
IOStreams: streams,
|
||||
CmdParent: parent,
|
||||
// NewExplainFlags returns a default ExplainFlags
|
||||
func NewExplainFlags(streams genericiooptions.IOStreams) *ExplainFlags {
|
||||
return &ExplainFlags{
|
||||
OutputFormat: plaintextTemplateName,
|
||||
IOStreams: streams,
|
||||
}
|
||||
}
|
||||
|
||||
// AddFlags registers flags for a cli
|
||||
func (flags *ExplainFlags) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVar(&flags.Recursive, "recursive", flags.Recursive, "Print the fields of fields (Currently only 1 level deep)")
|
||||
cmd.Flags().StringVar(&flags.APIVersion, "api-version", flags.APIVersion, "Get different explanations for particular API version (API group/version)")
|
||||
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
|
||||
}
|
||||
|
||||
// ToOptions converts from CLI inputs to runtime input
|
||||
func (flags *ExplainFlags) ToOptions(f cmdutil.Factory, parent string, args []string) (*ExplainOptions, error) {
|
||||
mapper, err := f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only openapi v3 needs the discovery client.
|
||||
openAPIV3Client, err := f.OpenAPIV3Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := &ExplainOptions{
|
||||
IOStreams: flags.IOStreams,
|
||||
|
||||
Recursive: flags.Recursive,
|
||||
APIVersion: flags.APIVersion,
|
||||
OutputFormat: flags.OutputFormat,
|
||||
|
||||
CmdParent: parent,
|
||||
args: args,
|
||||
|
||||
Mapper: mapper,
|
||||
openAPIGetter: f,
|
||||
|
||||
OpenAPIV3Client: openAPIV3Client,
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// NewCmdExplain returns a cobra command for swagger docs
|
||||
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewExplainOptions(parent, streams)
|
||||
flags := NewExplainFlags(streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [--output=plaintext|plaintext-openapiv2]",
|
||||
Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [-o|--output=plaintext|plaintext-openapiv2]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Get documentation for a resource"),
|
||||
Long: explainLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
|
||||
Example: explainExamples,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
o, err := flags.ToOptions(f, parent, args)
|
||||
cmdutil.CheckErr(err)
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&o.Recursive, "recursive", o.Recursive, "When true, print the name of all the fields recursively. Otherwise, print the available fields with their description.")
|
||||
cmd.Flags().StringVar(&o.APIVersion, "api-version", o.APIVersion, "Use given api-version (group/version) of the resource.")
|
||||
|
||||
// Only enable --output as a valid flag if the feature is enabled
|
||||
cmd.Flags().StringVar(&o.OutputFormat, "output", plaintextTemplateName, "Format in which to render the schema. Valid values are: (plaintext, plaintext-openapiv2).")
|
||||
flags.AddFlags(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
type ExplainOptions struct {
|
||||
genericiooptions.IOStreams
|
||||
|
||||
// Only openapi v3 needs the discovery client.
|
||||
o.OpenAPIV3Client, err = f.OpenAPIV3Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Recursive bool
|
||||
APIVersion string
|
||||
// Name of the template to use with the openapiv3 template renderer.
|
||||
OutputFormat string
|
||||
|
||||
// Lazy-load the OpenAPI V2 Resources, so they're not loaded when using OpenAPI V3.
|
||||
o.openAPIGetter = f
|
||||
o.args = args
|
||||
return nil
|
||||
CmdParent string
|
||||
args []string
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
openAPIGetter openapi.OpenAPIResourcesGetter
|
||||
|
||||
// Client capable of fetching openapi documents from the user's cluster
|
||||
OpenAPIV3Client openapiclient.Client
|
||||
}
|
||||
|
||||
func (o *ExplainOptions) Validate() error {
|
||||
|
|
|
@ -57,9 +57,9 @@ func TestExplainInvalidArgs(t *testing.T) {
|
|||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
|
||||
err := opts.Complete(tf, cmd, []string{})
|
||||
flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard())
|
||||
|
||||
opts, err := flags.ToOptions(tf, "kubectl", []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func TestExplainInvalidArgs(t *testing.T) {
|
|||
t.Error("unexpected non-error")
|
||||
}
|
||||
|
||||
err = opts.Complete(tf, cmd, []string{"resource1", "resource2"})
|
||||
opts, err = flags.ToOptions(tf, "kubectl", []string{"resource1", "resource2"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
@ -84,9 +84,9 @@ func TestExplainNotExistResource(t *testing.T) {
|
|||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
|
||||
err := opts.Complete(tf, cmd, []string{"foo"})
|
||||
flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard())
|
||||
|
||||
opts, err := flags.ToOptions(tf, "kubectl", []string{"foo"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
|
|
@ -225,12 +225,12 @@ func (flags *ExposeServiceFlags) AddFlags(cmd *cobra.Command) {
|
|||
}
|
||||
|
||||
func (flags *ExposeServiceFlags) ToOptions(cmd *cobra.Command, args []string) (*ExposeServiceOptions, error) {
|
||||
dryRunStratergy, err := cmdutil.GetDryRunStrategy(cmd)
|
||||
dryRunStrategy, err := cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(flags.PrintFlags, dryRunStratergy)
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(flags.PrintFlags, dryRunStrategy)
|
||||
printer, err := flags.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -243,7 +243,7 @@ func (flags *ExposeServiceFlags) ToOptions(cmd *cobra.Command, args []string) (*
|
|||
}
|
||||
|
||||
e := &ExposeServiceOptions{
|
||||
DryRunStrategy: dryRunStratergy,
|
||||
DryRunStrategy: dryRunStrategy,
|
||||
PrintObj: printer.PrintObj,
|
||||
Recorder: recorder,
|
||||
IOStreams: flags.IOStreams,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@ package get
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
@ -161,7 +162,7 @@ func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error
|
|||
// we need an actual value in order to retrieve the package path for an object.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(printers.InternalObjectPrinterErr)
|
||||
return errors.New(printers.InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
if _, found := out.(*tabwriter.Writer); !found {
|
||||
|
@ -210,7 +211,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
|
|||
switch u := obj.(type) {
|
||||
case *metav1.WatchEvent:
|
||||
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(u.Object.Object)).Type().PkgPath()) {
|
||||
return fmt.Errorf(printers.InternalObjectPrinterErr)
|
||||
return errors.New(printers.InternalObjectPrinterErr)
|
||||
}
|
||||
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(u.Object.Object)
|
||||
if err != nil {
|
||||
|
|
|
@ -47,9 +47,8 @@ import (
|
|||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/interrupt"
|
||||
"k8s.io/kubectl/pkg/util/slice"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// GetOptions contains the input to the get command.
|
||||
|
@ -91,8 +90,8 @@ var (
|
|||
|
||||
Prints a table of the most important information about the specified resources.
|
||||
You can filter the list using a label selector and the --selector flag. If the
|
||||
desired resource type is namespaced you will only see results in your current
|
||||
namespace unless you pass --all-namespaces.
|
||||
desired resource type is namespaced you will only see results in the current
|
||||
namespace if you don't specify any namespace.
|
||||
|
||||
By specifying the output as 'template' and providing a Go template as the value
|
||||
of the --template flag, you can filter the attributes of the fetched resources.`))
|
||||
|
@ -132,15 +131,19 @@ var (
|
|||
kubectl get rc/web service/frontend pods/web-pod-13je7
|
||||
|
||||
# List the 'status' subresource for a single pod
|
||||
kubectl get pod web-pod-13je7 --subresource status`))
|
||||
kubectl get pod web-pod-13je7 --subresource status
|
||||
|
||||
# List all deployments in namespace 'backend'
|
||||
kubectl get deployments.apps --namespace backend
|
||||
|
||||
# List all pods existing in all namespaces
|
||||
kubectl get pods --all-namespaces`))
|
||||
)
|
||||
|
||||
const (
|
||||
useServerPrintColumns = "server-print"
|
||||
)
|
||||
|
||||
var supportedSubresources = []string{"status", "scale"}
|
||||
|
||||
// NewGetOptions returns a GetOptions with default chunk size 500.
|
||||
func NewGetOptions(parent string, streams genericiooptions.IOStreams) *GetOptions {
|
||||
return &GetOptions{
|
||||
|
@ -186,7 +189,7 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStre
|
|||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.")
|
||||
cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize)
|
||||
cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.", supportedSubresources...)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -267,9 +270,13 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
|
|||
}
|
||||
|
||||
switch {
|
||||
case o.Watch || o.WatchOnly:
|
||||
case o.Watch:
|
||||
if len(o.SortBy) > 0 {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch or --watch-only requested, --sort-by will be ignored\n")
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch requested, --sort-by will be ignored for watch events received\n")
|
||||
}
|
||||
case o.WatchOnly:
|
||||
if len(o.SortBy) > 0 {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch-only requested, --sort-by will be ignored\n")
|
||||
}
|
||||
default:
|
||||
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
|
||||
|
@ -280,7 +287,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
|
|||
usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName)
|
||||
}
|
||||
|
||||
return cmdutil.UsageErrorf(cmd, usageString)
|
||||
return cmdutil.UsageErrorf(cmd, "%s", usageString)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,9 +316,6 @@ func (o *GetOptions) Validate() error {
|
|||
if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) {
|
||||
return fmt.Errorf("--output-watch-events option can only be used with --watch or --watch-only")
|
||||
}
|
||||
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
|
||||
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -482,7 +486,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, args []string) error {
|
|||
}
|
||||
|
||||
allErrs := []error{}
|
||||
errs := sets.NewString()
|
||||
errs := sets.New[string]()
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
|
@ -627,7 +631,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
|
|||
|
||||
info := infos[0]
|
||||
mapping := info.ResourceMapping()
|
||||
outputObjects := utilpointer.BoolPtr(!o.WatchOnly)
|
||||
outputObjects := ptr.To(!o.WatchOnly)
|
||||
printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -1438,7 +1438,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "foo",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "10"
|
||||
|
@ -1457,7 +1456,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "bar",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "11"
|
||||
|
@ -1476,7 +1474,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "baz",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "12"
|
||||
|
@ -2367,10 +2364,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 +2376,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 +2393,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "10"
|
||||
|
@ -2415,7 +2410,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "11"
|
||||
|
@ -2433,7 +2427,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "12"
|
||||
|
|
|
@ -32,8 +32,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
|
||||
"github.com/fvbommel/sortorder"
|
||||
)
|
||||
|
||||
// SortingPrinter sorts list types before delegating to another printer.
|
||||
|
@ -180,7 +178,7 @@ func isLess(i, j reflect.Value) (bool, error) {
|
|||
case reflect.Float32, reflect.Float64:
|
||||
return i.Float() < j.Float(), nil
|
||||
case reflect.String:
|
||||
return sortorder.NaturalLess(i.String(), j.String()), nil
|
||||
return i.String() < j.String(), nil
|
||||
case reflect.Pointer:
|
||||
return isLess(i.Elem(), j.Elem())
|
||||
case reflect.Struct:
|
||||
|
@ -274,11 +272,11 @@ func isLess(i, j reflect.Value) (bool, error) {
|
|||
// check if it's a Quantity
|
||||
itypeQuantity, err := resource.ParseQuantity(itype)
|
||||
if err != nil {
|
||||
return sortorder.NaturalLess(itype, jtype), nil
|
||||
return itype < jtype, nil
|
||||
}
|
||||
jtypeQuantity, err := resource.ParseQuantity(jtype)
|
||||
if err != nil {
|
||||
return sortorder.NaturalLess(itype, jtype), nil
|
||||
return itype < jtype, nil
|
||||
}
|
||||
// Both strings are quantity
|
||||
return itypeQuantity.Cmp(jtypeQuantity) < 0, nil
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func toUnstructuredOrDie(data []byte) *unstructured.Unstructured {
|
||||
|
@ -103,8 +104,6 @@ func createUnstructuredPodResource(t *testing.T, memReq, memLimit, cpuReq, cpuLi
|
|||
}
|
||||
|
||||
func TestSortingPrinter(t *testing.T) {
|
||||
intPtr := func(val int32) *int32 { return &val }
|
||||
|
||||
a := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a",
|
||||
|
@ -272,17 +271,17 @@ func TestSortingPrinter(t *testing.T) {
|
|||
Items: []corev1.ReplicationController{
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(5),
|
||||
Replicas: ptr.To[int32](5),
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(1),
|
||||
Replicas: ptr.To[int32](1),
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(9),
|
||||
Replicas: ptr.To[int32](9),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -291,17 +290,17 @@ func TestSortingPrinter(t *testing.T) {
|
|||
Items: []corev1.ReplicationController{
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(1),
|
||||
Replicas: ptr.To[int32](1),
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(5),
|
||||
Replicas: ptr.To[int32](5),
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: corev1.ReplicationControllerSpec{
|
||||
Replicas: intPtr(9),
|
||||
Replicas: ptr.To[int32](9),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,8 +21,8 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/spf13/cobra"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/interrupt"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
|
@ -57,15 +58,33 @@ var (
|
|||
# Return snapshot logs from pod nginx with only one container
|
||||
kubectl logs nginx
|
||||
|
||||
# Return snapshot logs from pod nginx, prefixing each line with the source pod and container name
|
||||
kubectl logs nginx --prefix
|
||||
|
||||
# Return snapshot logs from pod nginx, limiting output to 500 bytes
|
||||
kubectl logs nginx --limit-bytes=500
|
||||
|
||||
# Return snapshot logs from pod nginx, waiting up to 20 seconds for it to start running.
|
||||
kubectl logs nginx --pod-running-timeout=20s
|
||||
|
||||
# Return snapshot logs from pod nginx with multi containers
|
||||
kubectl logs nginx --all-containers=true
|
||||
|
||||
# Return snapshot logs from all pods in the deployment nginx
|
||||
kubectl logs deployment/nginx --all-pods=true
|
||||
|
||||
# Return snapshot logs from all containers in pods defined by label app=nginx
|
||||
kubectl logs -l app=nginx --all-containers=true
|
||||
|
||||
# Return snapshot logs from all pods defined by label app=nginx, limiting concurrent log requests to 10 pods
|
||||
kubectl logs -l app=nginx --max-log-requests=10
|
||||
|
||||
# Return snapshot of previous terminated ruby container logs from pod web-1
|
||||
kubectl logs -p -c ruby web-1
|
||||
|
||||
# Begin streaming the logs from pod nginx, continuing even if errors occur
|
||||
kubectl logs nginx -f --ignore-errors=true
|
||||
|
||||
# Begin streaming the logs of the ruby container in pod web-1
|
||||
kubectl logs -f -c ruby web-1
|
||||
|
||||
|
@ -77,6 +96,9 @@ var (
|
|||
|
||||
# Show all logs from pod nginx written in the last hour
|
||||
kubectl logs --since=1h nginx
|
||||
|
||||
# Show all logs with timestamps from pod nginx starting from August 30, 2024, at 06:00:00 UTC
|
||||
kubectl logs nginx --since-time=2024-08-30T06:00:00Z --timestamps=true
|
||||
|
||||
# Show logs from a kubelet with an expired serving certificate
|
||||
kubectl logs --insecure-skip-tls-verify-backend nginx
|
||||
|
@ -99,10 +121,11 @@ type LogsOptions struct {
|
|||
Namespace string
|
||||
ResourceArg string
|
||||
AllContainers bool
|
||||
AllPods bool
|
||||
Options runtime.Object
|
||||
Resources []string
|
||||
|
||||
ConsumeRequestFn func(rest.ResponseWrapper, io.Writer) error
|
||||
ConsumeRequestFn func(context.Context, rest.ResponseWrapper, io.Writer) error
|
||||
|
||||
// PodLogOptions
|
||||
SinceTime string
|
||||
|
@ -122,10 +145,11 @@ type LogsOptions struct {
|
|||
MaxFollowConcurrency int
|
||||
Prefix bool
|
||||
|
||||
Object runtime.Object
|
||||
GetPodTimeout time.Duration
|
||||
RESTClientGetter genericclioptions.RESTClientGetter
|
||||
LogsForObject polymorphichelpers.LogsForObjectFunc
|
||||
Object runtime.Object
|
||||
GetPodTimeout time.Duration
|
||||
RESTClientGetter genericclioptions.RESTClientGetter
|
||||
LogsForObject polymorphichelpers.LogsForObjectFunc
|
||||
AllPodLogsForObject polymorphichelpers.AllPodLogsForObjectFunc
|
||||
|
||||
genericiooptions.IOStreams
|
||||
|
||||
|
@ -134,10 +158,9 @@ type LogsOptions struct {
|
|||
containerNameFromRefSpecRegexp *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewLogsOptions(streams genericiooptions.IOStreams, allContainers bool) *LogsOptions {
|
||||
func NewLogsOptions(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
return &LogsOptions{
|
||||
IOStreams: streams,
|
||||
AllContainers: allContainers,
|
||||
Tail: -1,
|
||||
MaxFollowConcurrency: 5,
|
||||
|
||||
|
@ -147,7 +170,7 @@ func NewLogsOptions(streams genericiooptions.IOStreams, allContainers bool) *Log
|
|||
|
||||
// NewCmdLogs creates a new pod logs command
|
||||
func NewCmdLogs(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: logsUsageStr,
|
||||
|
@ -167,6 +190,7 @@ func NewCmdLogs(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co
|
|||
}
|
||||
|
||||
func (o *LogsOptions) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVar(&o.AllPods, "all-pods", o.AllPods, "Get logs from all pod(s). Sets prefix to true.")
|
||||
cmd.Flags().BoolVar(&o.AllContainers, "all-containers", o.AllContainers, "Get all containers' logs in the pod(s).")
|
||||
cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.")
|
||||
cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output")
|
||||
|
@ -243,6 +267,11 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
|
|||
default:
|
||||
return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
|
||||
}
|
||||
|
||||
if o.AllPods {
|
||||
o.Prefix = true
|
||||
}
|
||||
|
||||
var err error
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
|
@ -263,6 +292,7 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
|
|||
|
||||
o.RESTClientGetter = f
|
||||
o.LogsForObject = polymorphichelpers.LogsForObjectFn
|
||||
o.AllPodLogsForObject = polymorphichelpers.AllPodLogsForObjectFn
|
||||
|
||||
if o.Object == nil {
|
||||
builder := f.NewBuilder().
|
||||
|
@ -328,7 +358,13 @@ func (o LogsOptions) Validate() error {
|
|||
|
||||
// RunLogs retrieves a pod log
|
||||
func (o LogsOptions) RunLogs() error {
|
||||
requests, err := o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
|
||||
var requests map[corev1.ObjectReference]rest.ResponseWrapper
|
||||
var err error
|
||||
if o.AllPods {
|
||||
requests, err = o.AllPodLogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
|
||||
} else {
|
||||
requests, err = o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -340,14 +376,21 @@ func (o LogsOptions) RunLogs() error {
|
|||
len(requests), o.MaxFollowConcurrency,
|
||||
)
|
||||
}
|
||||
|
||||
return o.parallelConsumeRequest(requests)
|
||||
}
|
||||
|
||||
return o.sequentialConsumeRequest(requests)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
intr := interrupt.New(nil, cancel)
|
||||
return intr.Run(func() error {
|
||||
if o.Follow && len(requests) > 1 {
|
||||
return o.parallelConsumeRequest(ctx, requests)
|
||||
}
|
||||
|
||||
return o.sequentialConsumeRequest(ctx, requests)
|
||||
})
|
||||
}
|
||||
|
||||
func (o LogsOptions) parallelConsumeRequest(requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
|
||||
func (o LogsOptions) parallelConsumeRequest(ctx context.Context, requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
|
||||
reader, writer := io.Pipe()
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(requests))
|
||||
|
@ -355,7 +398,7 @@ func (o LogsOptions) parallelConsumeRequest(requests map[corev1.ObjectReference]
|
|||
go func(objRef corev1.ObjectReference, request rest.ResponseWrapper) {
|
||||
defer wg.Done()
|
||||
out := o.addPrefixIfNeeded(objRef, writer)
|
||||
if err := o.ConsumeRequestFn(request, out); err != nil {
|
||||
if err := o.ConsumeRequestFn(ctx, request, out); err != nil {
|
||||
if !o.IgnoreLogErrors {
|
||||
writer.CloseWithError(err)
|
||||
|
||||
|
@ -378,10 +421,10 @@ func (o LogsOptions) parallelConsumeRequest(requests map[corev1.ObjectReference]
|
|||
return err
|
||||
}
|
||||
|
||||
func (o LogsOptions) sequentialConsumeRequest(requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
|
||||
func (o LogsOptions) sequentialConsumeRequest(ctx context.Context, requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
|
||||
for objRef, request := range requests {
|
||||
out := o.addPrefixIfNeeded(objRef, o.Out)
|
||||
if err := o.ConsumeRequestFn(request, out); err != nil {
|
||||
if err := o.ConsumeRequestFn(ctx, request, out); err != nil {
|
||||
if !o.IgnoreLogErrors {
|
||||
return err
|
||||
}
|
||||
|
@ -422,8 +465,8 @@ func (o LogsOptions) addPrefixIfNeeded(ref corev1.ObjectReference, writer io.Wri
|
|||
// A successful read returns err == nil, not err == io.EOF.
|
||||
// Because the function is defined to read from request until io.EOF, it does
|
||||
// not treat an io.EOF as an error to be reported.
|
||||
func DefaultConsumeRequest(request rest.ResponseWrapper, out io.Writer) error {
|
||||
readCloser, err := request.Stream(context.TODO())
|
||||
func DefaultConsumeRequest(ctx context.Context, request rest.ResponseWrapper, out io.Writer) error {
|
||||
readCloser, err := request.Stream(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"testing/iotest"
|
||||
"time"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -62,7 +63,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
|
||||
|
@ -83,7 +84,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Prefix = true
|
||||
|
@ -92,6 +93,35 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
expectedOutSubstrings: []string{"[pod/test-pod/test-container] test log content\n"},
|
||||
},
|
||||
{
|
||||
name: "stateful set logs with all pods",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
mock := &logTestMock{
|
||||
logsForObjectRequests: map[corev1.ObjectReference]restclient.ResponseWrapper{
|
||||
{
|
||||
Kind: "Pod",
|
||||
Name: "test-sts-0",
|
||||
FieldPath: "spec.containers{test-container}",
|
||||
}: &responseWrapperMock{data: strings.NewReader("test log content for pod test-sts-0\n")},
|
||||
{
|
||||
Kind: "Pod",
|
||||
Name: "test-sts-1",
|
||||
FieldPath: "spec.containers{test-container}",
|
||||
}: &responseWrapperMock{data: strings.NewReader("test log content for pod test-sts-1\n")},
|
||||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Prefix = true
|
||||
return o
|
||||
},
|
||||
expectedOutSubstrings: []string{
|
||||
"[pod/test-sts-0/test-container] test log content for pod test-sts-0\n",
|
||||
"[pod/test-sts-1/test-container] test log content for pod test-sts-1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pod logs with prefix: init container",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
|
@ -105,7 +135,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Prefix = true
|
||||
|
@ -127,7 +157,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Prefix = true
|
||||
|
@ -159,7 +189,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
return o
|
||||
|
@ -196,7 +226,7 @@ func TestLog(t *testing.T) {
|
|||
}
|
||||
wg.Add(3)
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Follow = true
|
||||
|
@ -234,7 +264,7 @@ func TestLog(t *testing.T) {
|
|||
}
|
||||
wg.Add(3)
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.MaxFollowConcurrency = 2
|
||||
|
@ -246,7 +276,7 @@ func TestLog(t *testing.T) {
|
|||
{
|
||||
name: "fail if LogsForObject fails",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]restclient.ResponseWrapper, error) {
|
||||
return nil, errors.New("Error from the LogsForObject")
|
||||
}
|
||||
|
@ -272,9 +302,9 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
|
||||
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error {
|
||||
return errors.New("Error from the ConsumeRequestFn")
|
||||
}
|
||||
return o
|
||||
|
@ -307,7 +337,7 @@ func TestLog(t *testing.T) {
|
|||
}
|
||||
wg.Add(3)
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Follow = true
|
||||
|
@ -346,9 +376,9 @@ func TestLog(t *testing.T) {
|
|||
}
|
||||
wg.Add(3)
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
|
||||
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error {
|
||||
return errors.New("Error from the ConsumeRequestFn")
|
||||
}
|
||||
o.Follow = true
|
||||
|
@ -369,9 +399,9 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
|
||||
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error {
|
||||
return errors.New("Error from the ConsumeRequestFn")
|
||||
}
|
||||
o.Follow = true
|
||||
|
@ -402,7 +432,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.IgnoreLogErrors = true
|
||||
|
@ -432,7 +462,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
return o
|
||||
|
@ -462,7 +492,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.IgnoreLogErrors = true
|
||||
|
@ -493,7 +523,7 @@ func TestLog(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LogsForObject = mock.mockLogsForObject
|
||||
o.ConsumeRequestFn = mock.mockConsumeRequest
|
||||
o.Follow = true
|
||||
|
@ -564,7 +594,7 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "since & since-time",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.SinceSeconds = time.Hour
|
||||
o.SinceTime = "2006-01-02T15:04:05Z"
|
||||
|
||||
|
@ -582,7 +612,7 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "negative since-time",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.SinceSeconds = -1 * time.Second
|
||||
|
||||
var err error
|
||||
|
@ -599,7 +629,7 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "negative limit-bytes",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.LimitBytes = -100
|
||||
|
||||
var err error
|
||||
|
@ -616,7 +646,7 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "negative tail",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.Tail = -100
|
||||
|
||||
var err error
|
||||
|
@ -633,7 +663,8 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "container name combined with --all-containers",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, true)
|
||||
o := NewLogsOptions(streams)
|
||||
o.AllContainers = true
|
||||
o.Container = "my-container"
|
||||
|
||||
var err error
|
||||
|
@ -650,7 +681,7 @@ func TestValidateLogOptions(t *testing.T) {
|
|||
{
|
||||
name: "container name combined with second argument",
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.Container = "my-container"
|
||||
o.ContainerNameSpecified = true
|
||||
|
||||
|
@ -697,7 +728,7 @@ func TestLogComplete(t *testing.T) {
|
|||
name: "One args case",
|
||||
args: []string{"foo"},
|
||||
opts: func(streams genericiooptions.IOStreams) *LogsOptions {
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.Selector = "foo"
|
||||
return o
|
||||
},
|
||||
|
@ -777,7 +808,7 @@ func TestDefaultConsumeRequest(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := DefaultConsumeRequest(test.request, buf)
|
||||
err := DefaultConsumeRequest(context.TODO(), test.request, buf)
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
|
||||
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expectedErr, err.Error())
|
||||
|
@ -816,7 +847,7 @@ func TestNoResourceFoundMessage(t *testing.T) {
|
|||
|
||||
streams, _, buf, errbuf := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdLogs(tf, streams)
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
o.Selector = "foo"
|
||||
err := o.Complete(tf, cmd, []string{})
|
||||
|
||||
|
@ -864,7 +895,7 @@ func TestNoPodInNamespaceFoundMessage(t *testing.T) {
|
|||
|
||||
streams, _, _, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdLogs(tf, streams)
|
||||
o := NewLogsOptions(streams, false)
|
||||
o := NewLogsOptions(streams)
|
||||
err := o.Complete(tf, cmd, []string{podName})
|
||||
|
||||
if err == nil {
|
||||
|
@ -901,8 +932,8 @@ type logTestMock struct {
|
|||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
func (l *logTestMock) mockConsumeRequest(request restclient.ResponseWrapper, out io.Writer) error {
|
||||
readCloser, err := request.Stream(context.Background())
|
||||
func (l *logTestMock) mockConsumeRequest(ctx context.Context, request restclient.ResponseWrapper, out io.Writer) error {
|
||||
readCloser, err := request.Stream(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -919,6 +950,13 @@ func (l *logTestMock) mockConsumeRequest(request restclient.ResponseWrapper, out
|
|||
|
||||
func (l *logTestMock) mockLogsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]restclient.ResponseWrapper, error) {
|
||||
switch object.(type) {
|
||||
case *appsv1.Deployment:
|
||||
_, ok := options.(*corev1.PodLogOptions)
|
||||
if !ok {
|
||||
return nil, errors.New("provided options object is not a PodLogOptions")
|
||||
}
|
||||
|
||||
return l.logsForObjectRequests, nil
|
||||
case *corev1.Pod:
|
||||
_, ok := options.(*corev1.PodLogOptions)
|
||||
if !ok {
|
||||
|
|
|
@ -22,9 +22,8 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -45,7 +44,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/slice"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
|
@ -107,8 +105,6 @@ var (
|
|||
kubectl patch deployment nginx-deployment --subresource='scale' --type='merge' -p '{"spec":{"replicas":2}}'`))
|
||||
)
|
||||
|
||||
var supportedSubresources = []string{"status", "scale"}
|
||||
|
||||
func NewPatchOptions(ioStreams genericiooptions.IOStreams) *PatchOptions {
|
||||
return &PatchOptions{
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
|
@ -140,12 +136,12 @@ func NewCmdPatch(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra
|
|||
|
||||
cmd.Flags().StringVarP(&o.Patch, "patch", "p", "", "The patch to be applied to the resource JSON file.")
|
||||
cmd.Flags().StringVar(&o.PatchFile, "patch-file", "", "A file containing a patch to be applied to the resource.")
|
||||
cmd.Flags().StringVar(&o.PatchType, "type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
|
||||
cmd.Flags().StringVar(&o.PatchType, "type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.List(sets.KeySet(patchTypes))))
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")
|
||||
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, patch will operate on the content of the file, not the server-side resource.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-patch")
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, patch will operate on the subresource of the requested object.", supportedSubresources...)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, patch will operate on the subresource of the requested object.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -197,12 +193,9 @@ func (o *PatchOptions) Validate() error {
|
|||
}
|
||||
if len(o.PatchType) != 0 {
|
||||
if _, ok := patchTypes[strings.ToLower(o.PatchType)]; !ok {
|
||||
return fmt.Errorf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), o.PatchType)
|
||||
return fmt.Errorf("--type must be one of %v, not %q", sets.List(sets.KeySet(patchTypes)), o.PatchType)
|
||||
}
|
||||
}
|
||||
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
|
||||
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -266,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
|
||||
}
|
||||
|
|
|
@ -40,15 +40,19 @@ var (
|
|||
Plugins provide extended functionality that is not part of the major command-line distribution.
|
||||
Please refer to the documentation and examples for more information about how write your own plugins.
|
||||
|
||||
The easiest way to discover and install plugins is via the kubernetes sub-project krew.
|
||||
To install krew, visit [krew.sigs.k8s.io](https://krew.sigs.k8s.io/docs/user-guide/setup/install/)`))
|
||||
The easiest way to discover and install plugins is via the kubernetes sub-project krew: [krew.sigs.k8s.io].
|
||||
To install krew, visit https://krew.sigs.k8s.io/docs/user-guide/setup/install`))
|
||||
|
||||
pluginExample = templates.Examples(i18n.T(`
|
||||
# List all available plugins
|
||||
kubectl plugin list`))
|
||||
kubectl plugin list
|
||||
|
||||
# List only binary names of available plugins without paths
|
||||
kubectl plugin list --name-only`))
|
||||
|
||||
pluginListLong = templates.LongDesc(i18n.T(`
|
||||
List all available plugin files on a user's PATH.
|
||||
To see plugins binary names without the full path use --name-only flag.
|
||||
|
||||
Available plugin files are those that are:
|
||||
- executable
|
||||
|
@ -65,6 +69,7 @@ func NewCmdPlugin(streams genericiooptions.IOStreams) *cobra.Command {
|
|||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Provides utilities for interacting with plugins"),
|
||||
Long: pluginLong,
|
||||
Example: pluginExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.DefaultSubCommandRun(streams.ErrOut)(cmd, args)
|
||||
},
|
||||
|
|
|
@ -107,7 +107,7 @@ func NewCmdPortForward(f cmdutil.Factory, streams genericiooptions.IOStreams) *c
|
|||
Short: i18n.T("Forward one or more local ports to a pod"),
|
||||
Long: portforwardLong,
|
||||
Example: portforwardExample,
|
||||
ValidArgsFunction: completion.PodResourceNameCompletionFunc(f),
|
||||
ValidArgsFunction: completion.ResourceAndPortCompletionFunc(f),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(opts.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(opts.Validate())
|
||||
|
@ -136,19 +136,29 @@ type defaultPortForwarder struct {
|
|||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
|
||||
func createDialer(method string, url *url.URL, opts PortForwardOptions) (httpstream.Dialer, error) {
|
||||
transport, upgrader, err := spdy.RoundTripperFor(opts.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
|
||||
if cmdutil.PortForwardWebsockets.IsEnabled() {
|
||||
if !cmdutil.PortForwardWebsockets.IsDisabled() {
|
||||
tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(url, opts.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// First attempt tunneling (websocket) dialer, then fallback to spdy dialer.
|
||||
dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, httpstream.IsUpgradeFailure)
|
||||
dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool {
|
||||
return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)
|
||||
})
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
|
||||
dialer, err := createDialer(method, url, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fw, err := portforward.NewOnAddresses(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut)
|
||||
if err != nil {
|
||||
|
@ -213,7 +223,9 @@ func convertPodNamedPortToNumber(ports []string, pod corev1.Pod) ([]string, erro
|
|||
var converted []string
|
||||
for _, port := range ports {
|
||||
localPort, remotePort := splitPort(port)
|
||||
|
||||
if remotePort == "" {
|
||||
return nil, fmt.Errorf("remote port cannot be empty")
|
||||
}
|
||||
containerPortStr := remotePort
|
||||
_, err := strconv.Atoi(remotePort)
|
||||
if err != nil {
|
||||
|
@ -235,7 +247,7 @@ func convertPodNamedPortToNumber(ports []string, pod corev1.Pod) ([]string, erro
|
|||
return converted, nil
|
||||
}
|
||||
|
||||
func checkUDPPorts(udpOnlyPorts sets.Int, ports []string, obj metav1.Object) error {
|
||||
func checkUDPPorts(udpOnlyPorts sets.Set[int], ports []string, obj metav1.Object) error {
|
||||
for _, port := range ports {
|
||||
_, remotePort := splitPort(port)
|
||||
portNum, err := strconv.Atoi(remotePort)
|
||||
|
@ -269,8 +281,8 @@ func checkUDPPorts(udpOnlyPorts sets.Int, ports []string, obj metav1.Object) err
|
|||
// checkUDPPortInService returns an error if remote port in Service is a UDP port
|
||||
// TODO: remove this check after #47862 is solved
|
||||
func checkUDPPortInService(ports []string, svc *corev1.Service) error {
|
||||
udpPorts := sets.NewInt()
|
||||
tcpPorts := sets.NewInt()
|
||||
udpPorts := sets.New[int]()
|
||||
tcpPorts := sets.New[int]()
|
||||
for _, port := range svc.Spec.Ports {
|
||||
portNum := int(port.Port)
|
||||
switch port.Protocol {
|
||||
|
@ -286,8 +298,8 @@ func checkUDPPortInService(ports []string, svc *corev1.Service) error {
|
|||
// checkUDPPortInPod returns an error if remote port in Pod is a UDP port
|
||||
// TODO: remove this check after #47862 is solved
|
||||
func checkUDPPortInPod(ports []string, pod *corev1.Pod) error {
|
||||
udpPorts := sets.NewInt()
|
||||
tcpPorts := sets.NewInt()
|
||||
udpPorts := sets.New[int]()
|
||||
tcpPorts := sets.New[int]()
|
||||
for _, ct := range pod.Spec.Containers {
|
||||
for _, ctPort := range ct.Ports {
|
||||
portNum := int(ctPort.ContainerPort)
|
||||
|
@ -321,7 +333,7 @@ func (o *PortForwardOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
|
|||
|
||||
getPodTimeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)
|
||||
if err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, err.Error())
|
||||
return cmdutil.UsageErrorf(cmd, "%s", err.Error())
|
||||
}
|
||||
|
||||
resourceName := args[0]
|
||||
|
|
|
@ -32,7 +32,9 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
|
@ -818,6 +820,24 @@ func TestConvertPodNamedPortToNumber(t *testing.T) {
|
|||
ports: []string{"https", "http"},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "empty port name",
|
||||
pod: corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Ports: []corev1.ContainerPort{
|
||||
{
|
||||
ContainerPort: int32(27017)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ports: []string{"28015:"},
|
||||
converted: nil,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -983,3 +1003,38 @@ func TestCheckUDPPort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDialer(t *testing.T) {
|
||||
url, err := url.Parse("http://localhost:8080/index.html")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse test url: %v", err)
|
||||
}
|
||||
config := cmdtesting.DefaultClientConfig()
|
||||
opts := PortForwardOptions{Config: config}
|
||||
// First, ensure that no environment variable creates the fallback dialer.
|
||||
dialer, err := createDialer("GET", url, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create dialer: %v", err)
|
||||
}
|
||||
if _, isFallback := dialer.(*portforward.FallbackDialer); !isFallback {
|
||||
t.Errorf("expected fallback dialer, got %#v", dialer)
|
||||
}
|
||||
// Next, check turning on feature flag explicitly also creates fallback dialer.
|
||||
t.Setenv(string(cmdutil.PortForwardWebsockets), "true")
|
||||
dialer, err = createDialer("GET", url, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create dialer: %v", err)
|
||||
}
|
||||
if _, isFallback := dialer.(*portforward.FallbackDialer); !isFallback {
|
||||
t.Errorf("expected fallback dialer, got %#v", dialer)
|
||||
}
|
||||
// Finally, check explicit disabling does NOT create the fallback dialer.
|
||||
t.Setenv(string(cmdutil.PortForwardWebsockets), "false")
|
||||
dialer, err = createDialer("GET", url, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create dialer: %v", err)
|
||||
}
|
||||
if _, isFallback := dialer.(*portforward.FallbackDialer); isFallback {
|
||||
t.Errorf("expected fallback dialer, got %#v", dialer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package replace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -40,7 +41,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/slice"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/kubectl/pkg/validation"
|
||||
)
|
||||
|
@ -68,8 +68,6 @@ var (
|
|||
kubectl replace --force -f ./pod.json`))
|
||||
)
|
||||
|
||||
var supportedSubresources = []string{"status", "scale"}
|
||||
|
||||
type ReplaceOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
|
@ -136,7 +134,7 @@ func NewCmdReplace(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra
|
|||
|
||||
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to PUT to the server. Uses the transport specified by the kubeconfig file.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-replace")
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, replace will operate on the subresource of the requested object.", supportedSubresources...)
|
||||
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, replace will operate on the subresource of the requested object.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -249,10 +247,6 @@ func (o *ReplaceOptions) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
|
||||
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -365,8 +359,7 @@ func (o *ReplaceOptions) forceReplace() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
|
||||
return wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
if err := info.Get(); !errors.IsNotFound(err) {
|
||||
return false, 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,
|
||||
|
|
|
@ -63,7 +63,7 @@ var (
|
|||
Resource rollout will be restarted.`))
|
||||
|
||||
restartExample = templates.Examples(`
|
||||
# Restart all deployments in test-namespace namespace
|
||||
# Restart all deployments in the test-namespace namespace
|
||||
kubectl rollout restart deployment -n test-namespace
|
||||
|
||||
# Restart a deployment
|
||||
|
|
|
@ -167,22 +167,6 @@ func NewCmdRun(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Com
|
|||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
|
||||
|
||||
// Deprecate the cascade flag. If set, it has no practical effect since the created pod has no dependents.
|
||||
// TODO: Remove the cascade flag from the run command in kubectl 1.29
|
||||
cmd.Flags().MarkDeprecated("cascade", "because it is not relevant for this command. It will be removed in version 1.29.")
|
||||
|
||||
// Deprecate and hide unused flags.
|
||||
// These flags are being added to the run command by DeleteFlags to support pod deletion after attach,
|
||||
// but they are not used if set, so they effectively do nothing.
|
||||
// TODO: Remove these flags from the run command in kubectl 1.29
|
||||
cmd.Flags().MarkDeprecated("filename", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("force", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("grace-period", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("kustomize", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("recursive", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("timeout", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
cmd.Flags().MarkDeprecated("wait", "because it is not used by this command. It will be removed in version 1.29.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -520,7 +504,7 @@ func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Po
|
|||
return err
|
||||
}
|
||||
for _, request := range requests {
|
||||
if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
|
||||
if err := logs.DefaultConsumeRequest(context.Background(), request, opts.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue