Compare commits
489 Commits
kubernetes
...
master
Author | SHA1 | Date |
---|---|---|
|
2a6e583a42 | |
|
9411a867c0 | |
|
aa186336f3 | |
|
4dec168032 | |
|
48317edcc3 | |
|
9f67d52cb6 | |
|
33f48403eb | |
|
ceae52f082 | |
|
74f9adbfa6 | |
|
a628f84f45 | |
|
e98b7e433d | |
|
cce124a8d8 | |
|
9e0a26615e | |
|
4ce6135c24 | |
|
22d999d2fc | |
|
0889899212 | |
|
f4a1e23b35 | |
|
3c19b8deb3 | |
|
cbe60c952e | |
|
12dc58ebf7 | |
|
7b4e1d1699 | |
|
4d28f5cea7 | |
|
4b52eef334 | |
|
3962f4011e | |
|
af99d1e41c | |
|
4f56b5ec5b | |
|
ce4d90902a | |
|
21b32eea57 | |
|
403b4a41e8 | |
|
9fb3eee5e8 | |
|
8185d35b7a | |
|
80ffc392a2 | |
|
3f6dbadba7 | |
|
55aec96de2 | |
|
8f1bcdea60 | |
|
98b4b33964 | |
|
3cb662b4be | |
|
fb7d414ec2 | |
|
e7f17cb570 | |
|
29d75ee358 | |
|
8067f3a1a6 | |
|
483c28b281 | |
|
2e87981eff | |
|
02042ef887 | |
|
8ed5bb5f0a | |
|
f47e5c84cc | |
|
76e6818d8d | |
|
f07a946956 | |
|
45a8bb4283 | |
|
0b7534c13c | |
|
e3a465587f | |
|
e470ab29d4 | |
|
02bb9287e7 | |
|
8aac463c8e | |
|
1b3f4fd0f4 | |
|
142b144574 | |
|
7c82ae36f3 | |
|
d97446c293 | |
|
a8605c1ee2 | |
|
1d576b52f0 | |
|
a8c44498b7 | |
|
daa78b3004 | |
|
9892d492ab | |
|
fe571ee1bb | |
|
4966ba7250 | |
|
1c1291fbf7 | |
|
7e06b5277c | |
|
9c02ed6a6e | |
|
6b06b293f9 | |
|
da311e8e7e | |
|
2fa4a24584 | |
|
a6fde79de4 | |
|
2da25b2322 | |
|
110ba5998e | |
|
716737e46a | |
|
8a11631ed9 | |
|
330f86df8e | |
|
55101ca654 | |
|
ab62ac8cf1 | |
|
af5ade99d8 | |
|
4d27286e9c | |
|
9130183f39 | |
|
e808dbbf1f | |
|
579a7a5a35 | |
|
0b4adb247f | |
|
49afb3c466 | |
|
17c0dde6b1 | |
|
bc4c094b08 | |
|
800afb48a7 | |
|
2375a3a9f6 | |
|
18f24e791d | |
|
9c13527bac | |
|
17bb82b84d | |
|
abe43f6e92 | |
|
bb9c5182ea | |
|
4ee16d2b51 | |
|
edad3048e9 | |
|
46d6f63709 | |
|
8500d2979d | |
|
4b5ec542ad | |
|
ca5a831a47 | |
|
b011cffff8 | |
|
cb7efba696 | |
|
2be4847754 | |
|
6096dfa3cf | |
|
279ddf3abe | |
|
5ff92a69e3 | |
|
777f5e3cd1 | |
|
4afda566a9 | |
|
c37ca76b9c | |
|
47f13bd18b | |
|
88bb12ba04 | |
|
52ec1da081 | |
|
307936eb9d | |
|
90ee929b88 | |
|
105c831190 | |
|
d35aa2c630 | |
|
f4a8c5b53e | |
|
5bcd2add11 | |
|
bb3c0d9f3a | |
|
3323e167c5 | |
|
3013d81bdf | |
|
38e8d36c38 | |
|
b0f5f0c0aa | |
|
cf54a4ea54 | |
|
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 | |
|
e33a30ea7c | |
|
79fb20cb8b | |
|
316ed014ab | |
|
d74f0e1af1 | |
|
1ab40ed1c8 | |
|
6323c5bc57 | |
|
81562142de | |
|
a7bf48f663 | |
|
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 | |
|
5c587a03ba |
|
@ -1,3 +1,8 @@
|
|||
> ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**.
|
||||
> Pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes).
|
||||
> This repository is read-only for importing, and not used for direct contributions.
|
||||
> See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
|
||||
|
||||
# Kubectl
|
||||
|
||||

|
||||
|
|
|
@ -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
|
||||
|
|
131
go.mod
131
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.7.0
|
||||
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
|
||||
go.yaml.in/yaml/v2 v2.4.2
|
||||
golang.org/x/sys v0.33.0
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0
|
||||
k8s.io/api v0.0.0-20250830163657-b903cd06836a
|
||||
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4
|
||||
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e
|
||||
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97
|
||||
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7
|
||||
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
|
||||
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
|
||||
sigs.k8s.io/yaml v1.6.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/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.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.3 // 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/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/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // 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/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
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // 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.20.1 // indirect
|
||||
)
|
||||
|
|
324
go.sum
324
go.sum
|
@ -1,109 +1,65 @@
|
|||
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/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/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.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/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.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/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/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/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.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/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/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
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,190 +81,152 @@ 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=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
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=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.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-20250830163657-b903cd06836a h1:qS+abmAu2zbGFkbN1vA7LKS07jsXBN1BvTFXFvaGOLI=
|
||||
k8s.io/api v0.0.0-20250830163657-b903cd06836a/go.mod h1:/IpJMZ4ur2JBuX+kkBc115bnq09sFfUnbuFNrdEe5yc=
|
||||
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4 h1:ObQoOWhkcPbMnU7PIHT2pkO2wK66CcBn6vD+77CidHM=
|
||||
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4/go.mod h1:fDax9lidUgmNSmBlzUrSISURQmHpeyamBbKX9jGbJ3g=
|
||||
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e h1:dovWIA2PM0UrJrZdUBV8uy5pExliSBXSFeL0bI6IX6E=
|
||||
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e/go.mod h1:/yp9r2rD6AV7MYM/gmb55/6LttRuURzjhgqbfiFQ0Rg=
|
||||
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97 h1:6ks7Y8CNm05xZ6eyE0db5IDP54PIyRM3aZhpflG55hI=
|
||||
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97/go.mod h1:xTpANYjBhGsmpO7Gdw8kMt3yQfciVwyRhbqcq77qwyI=
|
||||
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7 h1:JQ/dO+EIXNQ+y3Vlez6PC6r7T+0JvNQWrBx6CD3jKls=
|
||||
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7/go.mod h1:ZZMk2BFRSF/kI9Y5qKmvTk4SJM654XsQ5eJ9cP7mrhw=
|
||||
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc h1:hEo6RVciD0e0QvtMBgwG9a7fFWb/vkx0Jvw/iQ5i+lQ=
|
||||
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc/go.mod h1:TmnJ20kJrkgbEqHgHoUxoTksgIUJNCWtv/QM+yBqCF0=
|
||||
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-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd h1:GNGeQ8o/xw8TwhavcXmWrGKHd0ez5TRx8qRqyncbFH4=
|
||||
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd/go.mod h1:OYYW2zJf2TAQHmxQwQgSsP/i40dCZig4RQkw3o3vFPE=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
|
||||
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU=
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
|
||||
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/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -23,10 +23,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -61,12 +62,10 @@ var (
|
|||
// APIResourceOptions is the start of the data required to perform the operation.
|
||||
// As new fields are added, add them here instead of referencing the cmd.Flags()
|
||||
type APIResourceOptions struct {
|
||||
Output string
|
||||
SortBy string
|
||||
APIGroup string
|
||||
Namespaced bool
|
||||
Verbs []string
|
||||
NoHeaders bool
|
||||
Cached bool
|
||||
Categories []string
|
||||
|
||||
|
@ -76,13 +75,8 @@ type APIResourceOptions struct {
|
|||
discoveryClient discovery.CachedDiscoveryInterface
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// groupResource contains the APIGroup and APIResource
|
||||
type groupResource struct {
|
||||
APIGroup string
|
||||
APIGroupVersion string
|
||||
APIResource metav1.APIResource
|
||||
PrintFlags *PrintFlags
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
}
|
||||
|
||||
// NewAPIResourceOptions creates the options for APIResource
|
||||
|
@ -90,6 +84,7 @@ func NewAPIResourceOptions(ioStreams genericiooptions.IOStreams) *APIResourceOpt
|
|||
return &APIResourceOptions{
|
||||
IOStreams: ioStreams,
|
||||
Namespaced: true,
|
||||
PrintFlags: NewPrintFlags(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,8 +104,7 @@ func NewCmdAPIResources(restClientGetter genericclioptions.RESTClientGetter, ioS
|
|||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`)
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
|
||||
cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
|
||||
|
@ -123,11 +117,7 @@ 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")
|
||||
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")
|
||||
|
@ -151,6 +141,28 @@ func (o *APIResourceOptions) Complete(restClientGetter genericclioptions.RESTCli
|
|||
o.groupChanged = cmd.Flags().Changed("api-group")
|
||||
o.nsChanged = cmd.Flags().Changed("namespaced")
|
||||
|
||||
var printer printers.ResourcePrinter
|
||||
if o.PrintFlags.OutputFormat != nil {
|
||||
printer, err = o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(object runtime.Object, out io.Writer) error {
|
||||
errs := []error{}
|
||||
if !*o.PrintFlags.NoHeaders &&
|
||||
(o.PrintFlags.OutputFormat == nil || *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {
|
||||
if err = printContextHeaders(out, *o.PrintFlags.OutputFormat); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if err := printer.PrintObj(object, out); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -170,7 +182,7 @@ func (o *APIResourceOptions) RunAPIResources() error {
|
|||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
resources := []groupResource{}
|
||||
var allResources []*metav1.APIResourceList
|
||||
|
||||
for _, list := range lists {
|
||||
if len(list.APIResources) == 0 {
|
||||
|
@ -180,6 +192,14 @@ func (o *APIResourceOptions) RunAPIResources() error {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
apiList := &metav1.APIResourceList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "APIResourceList",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
GroupVersion: gv.String(),
|
||||
}
|
||||
var apiResources []metav1.APIResource
|
||||
for _, resource := range list.APIResources {
|
||||
if len(resource.Verbs) == 0 {
|
||||
continue
|
||||
|
@ -193,65 +213,39 @@ 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{
|
||||
APIGroup: gv.Group,
|
||||
APIGroupVersion: gv.String(),
|
||||
APIResource: resource,
|
||||
})
|
||||
// set these because we display a concatenation of these two values under APIVERSION column of human-readable output
|
||||
resource.Group = gv.Group
|
||||
resource.Version = gv.Version
|
||||
apiResources = append(apiResources, resource)
|
||||
}
|
||||
apiList.APIResources = apiResources
|
||||
allResources = append(allResources, apiList)
|
||||
}
|
||||
|
||||
if o.NoHeaders == false && o.Output != "name" {
|
||||
if err = printContextHeaders(w, o.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
flatList := &metav1.APIResourceList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: allResources[0].APIVersion,
|
||||
Kind: allResources[0].Kind,
|
||||
},
|
||||
}
|
||||
for _, resource := range allResources {
|
||||
flatList.APIResources = append(flatList.APIResources, resource.APIResources...)
|
||||
}
|
||||
|
||||
sort.Stable(sortableResource{resources, o.SortBy})
|
||||
for _, r := range resources {
|
||||
switch o.Output {
|
||||
case "name":
|
||||
name := r.APIResource.Name
|
||||
if len(r.APIGroup) > 0 {
|
||||
name += "." + r.APIGroup
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
case "wide":
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
|
||||
r.APIResource.Name,
|
||||
strings.Join(r.APIResource.ShortNames, ","),
|
||||
r.APIGroupVersion,
|
||||
r.APIResource.Namespaced,
|
||||
r.APIResource.Kind,
|
||||
strings.Join(r.APIResource.Verbs, ","),
|
||||
strings.Join(r.APIResource.Categories, ",")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
case "":
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
|
||||
r.APIResource.Name,
|
||||
strings.Join(r.APIResource.ShortNames, ","),
|
||||
r.APIGroupVersion,
|
||||
r.APIResource.Namespaced,
|
||||
r.APIResource.Kind); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Stable(sortableResource{flatList.APIResources, o.SortBy})
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.NewAggregate(errs)
|
||||
err = o.PrintObj(flatList, w)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func printContextHeaders(out io.Writer, output string) error {
|
||||
|
@ -264,7 +258,7 @@ func printContextHeaders(out io.Writer, output string) error {
|
|||
}
|
||||
|
||||
type sortableResource struct {
|
||||
resources []groupResource
|
||||
resources []metav1.APIResource
|
||||
sortBy string
|
||||
}
|
||||
|
||||
|
@ -277,7 +271,7 @@ func (s sortableResource) Less(i, j int) bool {
|
|||
if ret > 0 {
|
||||
return false
|
||||
} else if ret == 0 {
|
||||
return strings.Compare(s.resources[i].APIResource.Name, s.resources[j].APIResource.Name) < 0
|
||||
return strings.Compare(s.resources[i].Name, s.resources[j].Name) < 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -285,9 +279,9 @@ func (s sortableResource) Less(i, j int) bool {
|
|||
func (s sortableResource) compareValues(i, j int) (string, string) {
|
||||
switch s.sortBy {
|
||||
case "name":
|
||||
return s.resources[i].APIResource.Name, s.resources[j].APIResource.Name
|
||||
return s.resources[i].Name, s.resources[j].Name
|
||||
case "kind":
|
||||
return s.resources[i].APIResource.Kind, s.resources[j].APIResource.Kind
|
||||
return s.resources[i].Kind, s.resources[j].Kind
|
||||
}
|
||||
return s.resources[i].APIGroup, s.resources[j].APIGroup
|
||||
return s.resources[i].Group, s.resources[j].Group
|
||||
}
|
||||
|
|
|
@ -17,13 +17,17 @@ limitations under the License.
|
|||
package apiresources
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestAPIResourcesComplete(t *testing.T) {
|
||||
|
@ -48,6 +52,16 @@ See 'kubectl api-resources -h' for help and examples`
|
|||
if err.Error() != expectedError {
|
||||
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
|
||||
}
|
||||
|
||||
*o.PrintFlags.OutputFormat = "foo"
|
||||
err = o.Complete(tf, cmd, []string{})
|
||||
if err == nil {
|
||||
t.Fatalf("An error was expected but not returned")
|
||||
}
|
||||
expectedError = `unable to match a printer suitable for the output format "foo", allowed formats are:`
|
||||
if !strings.HasPrefix(err.Error(), expectedError) {
|
||||
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIResourcesValidate(t *testing.T) {
|
||||
|
@ -61,13 +75,6 @@ func TestAPIResourcesValidate(t *testing.T) {
|
|||
optionSetupFn: func(o *APIResourceOptions) {},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "invalid output",
|
||||
optionSetupFn: func(o *APIResourceOptions) {
|
||||
o.Output = "foo"
|
||||
},
|
||||
expectedError: "--output foo is not available",
|
||||
},
|
||||
{
|
||||
name: "invalid sort by",
|
||||
optionSetupFn: func(o *APIResourceOptions) {
|
||||
|
@ -322,3 +329,92 @@ bazzes b somegroup/v1 true Baz
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAPIResourcesRunJsonYaml is doing same thing as TestAPIResourcesRun but for JSON and YAML outputs
|
||||
// A separate test function is created because we are using apieqaulity.Semantic.DeepEqual
|
||||
// to check equality between input and output
|
||||
func TestAPIResourcesRunJsonYaml(t *testing.T) {
|
||||
dc := cmdtesting.NewFakeCachedDiscoveryClient()
|
||||
tf := cmdtesting.NewTestFactory().WithDiscoveryClient(dc)
|
||||
defer tf.Cleanup()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedInvalidations int
|
||||
preferredResources []*v1.APIResourceList
|
||||
}{
|
||||
{
|
||||
name: "one",
|
||||
preferredResources: []*v1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []v1.APIResource{
|
||||
{
|
||||
Name: "foos",
|
||||
Namespaced: false,
|
||||
Kind: "Foo",
|
||||
Verbs: []string{"get", "list"},
|
||||
ShortNames: []string{"f", "fo"},
|
||||
Categories: []string{"some-category"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two",
|
||||
preferredResources: []*v1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "somegroup/v1",
|
||||
APIResources: []v1.APIResource{
|
||||
{
|
||||
Name: "bazzes",
|
||||
Namespaced: true,
|
||||
Kind: "Baz",
|
||||
Verbs: []string{"get", "list", "create", "delete"},
|
||||
ShortNames: []string{"b"},
|
||||
Categories: []string{"some-category", "another-category"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
dc.PreferredResources = tc.preferredResources
|
||||
ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
|
||||
|
||||
for _, v := range []string{"json", "yaml"} {
|
||||
cmd := NewCmdAPIResources(tf, ioStreams)
|
||||
err := cmd.Flags().Set("output", v)
|
||||
require.NoError(tt, err)
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if errOut.Len() > 0 {
|
||||
t.Fatalf("unexpected error output: %s", errOut.String())
|
||||
}
|
||||
apiResourceList := v1.APIResourceList{}
|
||||
switch v {
|
||||
case "json":
|
||||
err = json.Unmarshal(out.Bytes(), &apiResourceList)
|
||||
case "yaml":
|
||||
err = yaml.Unmarshal(out.Bytes(), &apiResourceList)
|
||||
}
|
||||
require.NoError(tt, err)
|
||||
|
||||
// this will undo custom value we add in RunAPIResources in the lines:
|
||||
// resource.Group = gv.Group
|
||||
// resource.Version = gv.Version
|
||||
apiResourceList.GroupVersion = apiResourceList.APIResources[0].Group + "/" + apiResourceList.APIResources[0].Version
|
||||
apiResourceList.APIResources[0].Version = ""
|
||||
apiResourceList.APIResources[0].Group = ""
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0]) {
|
||||
tt.Fatalf("expected output: [%v]\n, but got [%v]", tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiresources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
)
|
||||
|
||||
type PrintFlags struct {
|
||||
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
|
||||
NamePrintFlags NamePrintFlags
|
||||
HumanReadableFlags HumanPrintFlags
|
||||
|
||||
NoHeaders *bool
|
||||
OutputFormat *string
|
||||
}
|
||||
|
||||
func NewPrintFlags() *PrintFlags {
|
||||
outputFormat := ""
|
||||
noHeaders := false
|
||||
|
||||
return &PrintFlags{
|
||||
OutputFormat: &outputFormat,
|
||||
NoHeaders: &noHeaders,
|
||||
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
|
||||
NamePrintFlags: APIResourcesNewNamePrintFlags(),
|
||||
HumanReadableFlags: APIResourcesHumanReadableFlags(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.JSONYamlPrintFlags.AddFlags(cmd)
|
||||
f.HumanReadableFlags.AddFlags(cmd)
|
||||
f.NamePrintFlags.AddFlags(cmd)
|
||||
|
||||
if f.OutputFormat != nil {
|
||||
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: (%s).", strings.Join(f.AllowedFormats(), ", ")))
|
||||
}
|
||||
if f.NoHeaders != nil {
|
||||
cmd.Flags().BoolVar(f.NoHeaders, "no-headers", *f.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintOptions struct defines a struct for various print options
|
||||
type PrintOptions struct {
|
||||
SortBy *string
|
||||
NoHeaders bool
|
||||
Wide bool
|
||||
}
|
||||
|
||||
type HumanPrintFlags struct {
|
||||
SortBy *string
|
||||
NoHeaders bool
|
||||
}
|
||||
|
||||
func (f *HumanPrintFlags) AllowedFormats() []string {
|
||||
return []string{"wide"}
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to human-readable printing to it
|
||||
func (f *HumanPrintFlags) AddFlags(c *cobra.Command) {
|
||||
if f.SortBy != nil {
|
||||
c.Flags().StringVar(f.SortBy, "sort-by", *f.SortBy, "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.")
|
||||
}
|
||||
}
|
||||
|
||||
// ToPrinter receives an outputFormat and returns a printer capable of
|
||||
// handling human-readable output.
|
||||
func (f *HumanPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
if len(outputFormat) > 0 && outputFormat != "wide" {
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
p := HumanReadablePrinter{
|
||||
options: PrintOptions{
|
||||
NoHeaders: f.NoHeaders,
|
||||
Wide: outputFormat == "wide",
|
||||
},
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type HumanReadablePrinter struct {
|
||||
options PrintOptions
|
||||
}
|
||||
|
||||
func (f HumanReadablePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
flatList, ok := obj.(*metav1.APIResourceList)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not a APIResourceList")
|
||||
}
|
||||
var errs []error
|
||||
for _, r := range flatList.APIResources {
|
||||
gv, err := schema.ParseGroupVersion(strings.Join([]string{r.Group, r.Version}, "/"))
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if f.options.Wide {
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
|
||||
r.Name,
|
||||
strings.Join(r.ShortNames, ","),
|
||||
gv.String(),
|
||||
r.Namespaced,
|
||||
r.Kind,
|
||||
strings.Join(r.Verbs, ","),
|
||||
strings.Join(r.Categories, ",")); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
|
||||
r.Name,
|
||||
strings.Join(r.ShortNames, ","),
|
||||
gv.String(),
|
||||
r.Namespaced,
|
||||
r.Kind); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
type NamePrintFlags struct{}
|
||||
|
||||
func APIResourcesNewNamePrintFlags() NamePrintFlags {
|
||||
return NamePrintFlags{}
|
||||
}
|
||||
|
||||
func (f *NamePrintFlags) AllowedFormats() []string {
|
||||
return []string{"name"}
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to name printing to it
|
||||
func (f *NamePrintFlags) AddFlags(_ *cobra.Command) {}
|
||||
|
||||
// ToPrinter receives an outputFormat and returns a printer capable of
|
||||
// handling human-readable output.
|
||||
func (f *NamePrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
if outputFormat == "name" {
|
||||
return NamePrinter{}, nil
|
||||
}
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
type NamePrinter struct{}
|
||||
|
||||
func (f NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||
flatList, ok := obj.(*metav1.APIResourceList)
|
||||
if !ok {
|
||||
return fmt.Errorf("object is not a APIResourceList")
|
||||
}
|
||||
var errs []error
|
||||
for _, r := range flatList.APIResources {
|
||||
name := r.Name
|
||||
if len(r.Group) > 0 {
|
||||
name += "." + r.Group
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func APIResourcesHumanReadableFlags() HumanPrintFlags {
|
||||
return HumanPrintFlags{
|
||||
SortBy: nil,
|
||||
NoHeaders: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *PrintFlags) AllowedFormats() []string {
|
||||
ret := []string{}
|
||||
ret = append(ret, f.JSONYamlPrintFlags.AllowedFormats()...)
|
||||
ret = append(ret, f.NamePrintFlags.AllowedFormats()...)
|
||||
ret = append(ret, f.HumanReadableFlags.AllowedFormats()...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
|
||||
outputFormat := ""
|
||||
if f.OutputFormat != nil {
|
||||
outputFormat = *f.OutputFormat
|
||||
}
|
||||
|
||||
noHeaders := false
|
||||
if f.NoHeaders != nil {
|
||||
noHeaders = *f.NoHeaders
|
||||
}
|
||||
f.HumanReadableFlags.NoHeaders = noHeaders
|
||||
|
||||
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if p, err := f.HumanReadableFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
|
@ -23,7 +23,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import (
|
|||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -184,7 +185,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 +207,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
|
||||
|
@ -298,13 +301,16 @@ func (o *AttachOptions) Run() error {
|
|||
sizePlusOne.Height++
|
||||
|
||||
// this call spawns a goroutine to monitor/update the terminal size
|
||||
sizeQueue = t.MonitorSize(&sizePlusOne, size)
|
||||
sizeQueue = &terminalSizeQueueAdapter{
|
||||
delegate: t.MonitorSize(&sizePlusOne, size),
|
||||
}
|
||||
}
|
||||
|
||||
o.DisableStderr = true
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -354,3 +360,18 @@ func (o *AttachOptions) reattachMessage(containerName string, rawTTY bool) strin
|
|||
}
|
||||
return fmt.Sprintf("Session ended, resume using '%s %s -c %s -i -t' command when the pod is running", o.CommandName, o.Pod.Name, containerName)
|
||||
}
|
||||
|
||||
type terminalSizeQueueAdapter struct {
|
||||
delegate term.TerminalSizeQueue
|
||||
}
|
||||
|
||||
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
|
||||
next := a.delegate.Next()
|
||||
if next == nil {
|
||||
return nil
|
||||
}
|
||||
return &remotecommand.TerminalSize{
|
||||
Width: next.Width,
|
||||
Height: next.Height,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/resource"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/component-helpers/auth/rbac/reconciliation"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
|
@ -197,6 +199,9 @@ func (o *ReconcileOptions) Validate() error {
|
|||
|
||||
// RunReconcile performs the execution
|
||||
func (o *ReconcileOptions) RunReconcile() error {
|
||||
// conflictBackoff retries up to 3 times on conflict, with no delay
|
||||
conflictBackoff := wait.Backoff{Steps: 3}
|
||||
|
||||
return o.Visitor.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -213,7 +218,14 @@ func (o *ReconcileOptions) RunReconcile() error {
|
|||
Client: o.RBACClient,
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
var (
|
||||
result *reconciliation.ReconcileClusterRoleResult
|
||||
err error
|
||||
)
|
||||
retry.RetryOnConflict(conflictBackoff, func() error {
|
||||
result, err = reconcileOptions.Run()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -228,7 +240,14 @@ func (o *ReconcileOptions) RunReconcile() error {
|
|||
Client: o.RBACClient.ClusterRoles(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
var (
|
||||
result *reconciliation.ReconcileClusterRoleResult
|
||||
err error
|
||||
)
|
||||
retry.RetryOnConflict(conflictBackoff, func() error {
|
||||
result, err = reconcileOptions.Run()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -244,10 +263,14 @@ func (o *ReconcileOptions) RunReconcile() error {
|
|||
NamespaceClient: o.NamespaceClient.Namespaces(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
var (
|
||||
result *reconciliation.ReconcileClusterRoleBindingResult
|
||||
err error
|
||||
)
|
||||
retry.RetryOnConflict(conflictBackoff, func() error {
|
||||
result, err = reconcileOptions.Run()
|
||||
return err
|
||||
}
|
||||
})
|
||||
o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected)
|
||||
|
||||
case *rbacv1.ClusterRoleBinding:
|
||||
|
@ -259,10 +282,14 @@ func (o *ReconcileOptions) RunReconcile() error {
|
|||
Client: o.RBACClient.ClusterRoleBindings(),
|
||||
},
|
||||
}
|
||||
result, err := reconcileOptions.Run()
|
||||
if err != nil {
|
||||
var (
|
||||
result *reconciliation.ReconcileClusterRoleBindingResult
|
||||
err error
|
||||
)
|
||||
retry.RetryOnConflict(conflictBackoff, func() error {
|
||||
result, err = reconcileOptions.Run()
|
||||
return err
|
||||
}
|
||||
})
|
||||
o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected)
|
||||
|
||||
case *rbacv1beta1.Role,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -19,19 +19,26 @@ package autoscale
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"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"
|
||||
"k8s.io/klog/v2"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
@ -43,16 +50,23 @@ 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.`))
|
||||
|
||||
autoscaleExample = templates.Examples(i18n.T(`
|
||||
# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used
|
||||
kubectl autoscale deployment foo --min=2 --max=10
|
||||
kubectl autoscale deployment foo --min=2 --max=10
|
||||
|
||||
# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%
|
||||
kubectl autoscale rc foo --max=5 --cpu-percent=80`))
|
||||
kubectl autoscale rc foo --max=5 --cpu=80%
|
||||
|
||||
# Auto scale a deployment "bar", with the number of pods between 3 and 6, target average CPU of 500m and memory of 200Mi
|
||||
kubectl autoscale deployment bar --min=3 --max=6 --cpu=500m --memory=200Mi
|
||||
|
||||
# Auto scale a deployment "bar", with the number of pods between 2 and 8, target CPU utilization 60% and memory utilization 70%
|
||||
kubectl autoscale deployment bar --min=3 --max=6 --cpu=60% --memory=70%`))
|
||||
)
|
||||
|
||||
// AutoscaleOptions declares the arguments accepted by the Autoscale command
|
||||
|
@ -69,6 +83,8 @@ type AutoscaleOptions struct {
|
|||
Min int32
|
||||
Max int32
|
||||
CPUPercent int32
|
||||
CPU string
|
||||
Memory string
|
||||
|
||||
createAnnotation bool
|
||||
args []string
|
||||
|
@ -78,7 +94,8 @@ type AutoscaleOptions struct {
|
|||
builder *resource.Builder
|
||||
fieldManager string
|
||||
|
||||
HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
|
||||
HPAClientV1 autoscalingv1client.HorizontalPodAutoscalersGetter
|
||||
HPAClientV2 autoscalingv2client.HorizontalPodAutoscalersGetter
|
||||
scaleKindResolver scale.ScaleKindResolver
|
||||
|
||||
genericiooptions.IOStreams
|
||||
|
@ -103,7 +120,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu=CPU] [--memory=MEMORY]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
|
||||
Long: autoscaleLong,
|
||||
|
@ -123,7 +140,11 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
|
||||
cmd.MarkFlagRequired("max")
|
||||
cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")
|
||||
cmd.Flags().StringVar(&o.CPU, "cpu", "", `Target CPU utilization over all the pods. When specified as a percentage (e.g."70%" for 70% of requested CPU) it will target average utilization. When specified as quantity (e.g."500m" for 500 milliCPU) it will target average value. Value without units is treated as a quantity with miliCPU being the unit (e.g."500" is "500m").`)
|
||||
cmd.Flags().StringVar(&o.Memory, "memory", "", `Target memory utilization over all the pods. When specified as a percentage (e.g."60%" for 60% of requested memory) it will target average utilization. When specified as quantity (e.g."200Mi" for 200 MiB, "1Gi" for 1 GiB) it will target average value. Value without units is treated as a quantity with mebibytes being the unit (e.g."200" is "200Mi").`)
|
||||
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
|
||||
_ = cmd.Flags().MarkDeprecated("cpu-percent",
|
||||
"Use --cpu with percentage or resource quantity format (e.g., '70%' for utilization or '500m' for milliCPU).")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
|
@ -157,7 +178,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 {
|
||||
|
@ -182,11 +204,25 @@ func (o *AutoscaleOptions) Validate() error {
|
|||
if o.Max < o.Min {
|
||||
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
|
||||
}
|
||||
|
||||
// only one of the CPUPercent or CPU param is allowed
|
||||
if o.CPUPercent > 0 && o.CPU != "" {
|
||||
return fmt.Errorf("--cpu-percent and --cpu are mutually exclusive")
|
||||
}
|
||||
// validate CPU target if specified
|
||||
if o.CPU != "" {
|
||||
if _, _, _, err := parseResourceInput(o.CPU, corev1.ResourceCPU); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// validate Memory target if specified
|
||||
if o.Memory != "" {
|
||||
if _, _, _, err := parseResourceInput(o.Memory, corev1.ResourceMemory); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution
|
||||
func (o *AutoscaleOptions) Run() error {
|
||||
r := o.builder.
|
||||
Unstructured().
|
||||
|
@ -208,48 +244,31 @@ func (o *AutoscaleOptions) Run() error {
|
|||
|
||||
mapping := info.ResourceMapping()
|
||||
gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
|
||||
if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
|
||||
if _, err = o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %s: %w", mapping.GroupVersionKind.Kind, err)
|
||||
}
|
||||
|
||||
hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)
|
||||
|
||||
if err := o.Recorder.Record(hpa); err != nil {
|
||||
klog.V(4).Infof("error recording current command: %v", err)
|
||||
// handles the creation of HorizontalPodAutoscaler objects for both autoscaling/v2 and autoscaling/v1 APIs.
|
||||
// If autoscaling/v2 API fails, try to create and handle HorizontalPodAutoscaler using autoscaling/v1 API
|
||||
var hpaV2 runtime.Object
|
||||
hpaV2, err = o.createHorizontalPodAutoscalerV2(info.Name, mapping)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HorizontalPodAutoscaler using autoscaling/v2 API: %w", err)
|
||||
}
|
||||
|
||||
if o.dryRunStrategy == cmdutil.DryRunClient {
|
||||
count++
|
||||
|
||||
printer, err := o.ToPrinter("created")
|
||||
if err != nil {
|
||||
if err = o.handleHPA(hpaV2); err != nil {
|
||||
klog.V(1).Infof("Encountered an error with the autoscaling/v2 HorizontalPodAutoscaler: %v. "+
|
||||
"Falling back to try the autoscaling/v1 HorizontalPodAutoscaler", err)
|
||||
// check if the HPA can be created using v1 API.
|
||||
if ok, err := o.canCreateHPAV1(); !ok {
|
||||
return fmt.Errorf("failed to create autoscaling/v2 HPA and the configuration is incompatible with autoscaling/v1: %w", err)
|
||||
}
|
||||
hpaV1 := o.createHorizontalPodAutoscalerV1(info.Name, mapping)
|
||||
if err := o.handleHPA(hpaV1); err != nil {
|
||||
return err
|
||||
}
|
||||
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 +279,169 @@ func (o *AutoscaleOptions) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
|
||||
func (o *AutoscaleOptions) canCreateHPAV1() (bool, error) {
|
||||
// Allow fallback to v1 HPA only if:
|
||||
// 1. CPUPercent is set and Memory is not set.
|
||||
// 2. Or, Memory is not set and the metric type is UtilizationMetricType.
|
||||
_, _, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return (o.CPUPercent >= 0 && o.Memory == "") ||
|
||||
(o.Memory == "" && metricsType == autoscalingv2.UtilizationMetricType), nil
|
||||
}
|
||||
|
||||
// handleHPA handles the creation and management of a single HPA object.
|
||||
func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
|
||||
if err := o.Recorder.Record(hpa); err != nil {
|
||||
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, error) {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
}
|
||||
|
||||
scaler := autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
|
||||
Kind: mapping.GroupVersionKind.Kind,
|
||||
Name: refName,
|
||||
},
|
||||
MaxReplicas: o.Max,
|
||||
},
|
||||
}
|
||||
|
||||
if o.Min > 0 {
|
||||
scaler.Spec.MinReplicas = &o.Min
|
||||
}
|
||||
|
||||
metrics := []autoscalingv2.MetricSpec{}
|
||||
|
||||
// add CPU metric if any of the CPU targets are specified
|
||||
if o.CPUPercent > 0 {
|
||||
cpuMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
cpuMetric.Resource.Target.AverageUtilization = &o.CPUPercent
|
||||
metrics = append(metrics, cpuMetric)
|
||||
}
|
||||
|
||||
// add Cpu metric if any of the cpu targets are specified
|
||||
if o.CPU != "" {
|
||||
cpuMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
|
||||
quantity, value, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch metricsType {
|
||||
case autoscalingv2.UtilizationMetricType:
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
cpuMetric.Resource.Target.AverageUtilization = &value
|
||||
case autoscalingv2.AverageValueMetricType:
|
||||
cpuMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
|
||||
cpuMetric.Resource.Target.AverageValue = &quantity
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
|
||||
}
|
||||
metrics = append(metrics, cpuMetric)
|
||||
}
|
||||
|
||||
// add Memory metric if any of the memory targets are specified
|
||||
if o.Memory != "" {
|
||||
memoryMetric := autoscalingv2.MetricSpec{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{},
|
||||
},
|
||||
}
|
||||
quantity, value, metricsType, err := parseResourceInput(o.Memory, corev1.ResourceMemory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch metricsType {
|
||||
case autoscalingv2.UtilizationMetricType:
|
||||
memoryMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
|
||||
memoryMetric.Resource.Target.AverageUtilization = &value
|
||||
case autoscalingv2.AverageValueMetricType:
|
||||
memoryMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
|
||||
memoryMetric.Resource.Target.AverageValue = &quantity
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
|
||||
}
|
||||
metrics = append(metrics, memoryMetric)
|
||||
}
|
||||
|
||||
// Only set Metrics if there are any defined
|
||||
if len(metrics) > 0 {
|
||||
scaler.Spec.Metrics = metrics
|
||||
} else {
|
||||
scaler.Spec.Metrics = nil
|
||||
}
|
||||
|
||||
return &scaler, nil
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
|
@ -291,3 +472,72 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping
|
|||
|
||||
return &scaler
|
||||
}
|
||||
|
||||
// parseResourceInput parses a resource input string into either a utilization percentage or a quantity value.
|
||||
// It supports:
|
||||
// - Percentage values (e.g., "70%") for UtilizationMetricType
|
||||
// - Quantity values with units (e.g., "500m", "2Gi")
|
||||
// - Bare numbers without units, which are interpreted as:
|
||||
// - CPU: milliCPU ("500" → "500m")
|
||||
// - Memory: Mebibytes ("512" → "512Mi")
|
||||
func parseResourceInput(input string, resourceType corev1.ResourceName) (apiresource.Quantity, int32, autoscalingv2.MetricTargetType, error) {
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
return apiresource.Quantity{}, 0, "", fmt.Errorf("empty input")
|
||||
}
|
||||
|
||||
// Case 1: Handle percentage-based metrics like "70%"
|
||||
percentValue, isPercent, err := parsePercentage(input)
|
||||
if isPercent {
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
return apiresource.Quantity{}, percentValue, autoscalingv2.UtilizationMetricType, nil
|
||||
}
|
||||
|
||||
// Case 2: Try to interpret input as a bare number (e.g., "500"), and apply default float
|
||||
valueFloat, err := strconv.ParseFloat(input, 64)
|
||||
if err == nil {
|
||||
unit, err := getDefaultUnitForResource(resourceType)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
|
||||
inputWithUnit := fmt.Sprintf("%g%s", valueFloat, unit)
|
||||
quantity, err := apiresource.ParseQuantity(inputWithUnit)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", err
|
||||
}
|
||||
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
|
||||
}
|
||||
|
||||
// Case 3: Parse normally if input has a valid unit (e.g., "500m", "2Gi")
|
||||
quantity, err := apiresource.ParseQuantity(input)
|
||||
if err != nil {
|
||||
return apiresource.Quantity{}, 0, "", fmt.Errorf("invalid resource %s value: %s", resourceType, input)
|
||||
}
|
||||
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
|
||||
}
|
||||
|
||||
func getDefaultUnitForResource(resourceType corev1.ResourceName) (string, error) {
|
||||
switch resourceType {
|
||||
case corev1.ResourceCPU:
|
||||
return "m", nil
|
||||
case corev1.ResourceMemory:
|
||||
return "Mi", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported resource type: %v", resourceType)
|
||||
}
|
||||
}
|
||||
|
||||
func parsePercentage(input string) (int32, bool, error) {
|
||||
if !strings.HasSuffix(input, "%") {
|
||||
return 0, false, nil
|
||||
}
|
||||
trimmed := strings.TrimSuffix(input, "%")
|
||||
valueInt64, err := strconv.ParseInt(trimmed, 10, 32)
|
||||
if err != nil || valueInt64 < 0 {
|
||||
return 0, true, fmt.Errorf("invalid percentage value: %s", trimmed)
|
||||
}
|
||||
return int32(valueInt64), true, nil
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 {
|
||||
|
|
|
@ -129,7 +129,8 @@ func NewCmdConfigSetCredentials(out io.Writer, configAccess clientcmd.ConfigAcce
|
|||
}
|
||||
|
||||
// NewCmdConfigSetAuthInfo returns a Command instance for 'config set-credentials' sub command
|
||||
// DEPRECATED: Use NewCmdConfigSetCredentials instead
|
||||
//
|
||||
// Deprecated: Use NewCmdConfigSetCredentials instead
|
||||
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
|
||||
return NewCmdConfigSetCredentials(out, configAccess)
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
@ -105,7 +106,7 @@ func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
|
||||
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
|
||||
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only CronJob is supported).")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -658,6 +727,9 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
|
|||
p := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: pn,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -708,6 +780,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 +788,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 +989,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 +1012,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 +1023,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
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 {
|
||||
|
@ -503,6 +506,10 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
|||
operation = "force deleted"
|
||||
}
|
||||
|
||||
if info.Namespaced() {
|
||||
operation = fmt.Sprintf("%s from %s namespace", operation, info.Namespace)
|
||||
}
|
||||
|
||||
switch o.DryRunStrategy {
|
||||
case cmdutil.DryRunClient:
|
||||
operation = fmt.Sprintf("%s (dry run)", operation)
|
||||
|
@ -521,7 +528,7 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
|||
}
|
||||
|
||||
func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
|
||||
fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos))
|
||||
fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos)) //nolint:errcheck
|
||||
for _, info := range infos {
|
||||
groupKind := info.Mapping.GroupVersionKind
|
||||
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
|
||||
|
@ -529,11 +536,11 @@ func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
|
|||
kindString = strings.ToLower(groupKind.Kind)
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
|
||||
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name) //nolint:errcheck
|
||||
}
|
||||
fmt.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" {
|
||||
|
@ -972,3 +972,39 @@ func TestResourceErrors(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMessageOutput(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
_, _, rc := cmdtesting.TestData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test-specific")
|
||||
defer tf.Cleanup()
|
||||
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test-specific/replicationcontrollers/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdDelete(tf, streams)
|
||||
err := cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if buf.String() != "replicationcontroller \"redis-master\" deleted from test-specific namespace\n" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package drain
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -151,10 +152,11 @@ func NewDrainCmdOptions(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
|
|||
PrintFlags: genericclioptions.NewPrintFlags("drained").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
drainer: &drain.Helper{
|
||||
GracePeriodSeconds: -1,
|
||||
Out: ioStreams.Out,
|
||||
ErrOut: ioStreams.ErrOut,
|
||||
ChunkSize: cmdutil.DefaultChunkSize,
|
||||
GracePeriodSeconds: -1,
|
||||
EvictErrorRetryDelay: 5 * time.Second,
|
||||
Out: ioStreams.Out,
|
||||
ErrOut: ioStreams.ErrOut,
|
||||
ChunkSize: cmdutil.DefaultChunkSize,
|
||||
},
|
||||
}
|
||||
o.drainer.OnPodDeletionOrEvictionFinished = o.onPodDeletionOrEvictionFinished
|
||||
|
@ -224,8 +226,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 +245,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 +328,7 @@ func (o *DrainCmdOptions) RunDrain() error {
|
|||
return err
|
||||
}
|
||||
|
||||
drainedNodes := sets.NewString()
|
||||
drainedNodes := sets.New[string]()
|
||||
var fatal []error
|
||||
|
||||
remainingNodes := []string{}
|
||||
|
@ -343,7 +343,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,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
yaml "go.yaml.in/yaml/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -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 "go.yaml.in/yaml/v2"
|
||||
)
|
||||
|
||||
type EditTestCase struct {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ spec:
|
|||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
|
|
@ -29,7 +29,6 @@ spec:
|
|||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -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
|
||||
|
@ -365,7 +366,9 @@ func (p *ExecOptions) Run() error {
|
|||
var sizeQueue remotecommand.TerminalSizeQueue
|
||||
if t.Raw {
|
||||
// this call spawns a goroutine to monitor/update the terminal size
|
||||
sizeQueue = t.MonitorSize(t.GetSize())
|
||||
sizeQueue = &terminalSizeQueueAdapter{
|
||||
delegate: t.MonitorSize(t.GetSize()),
|
||||
}
|
||||
|
||||
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
|
||||
// true
|
||||
|
@ -402,3 +405,18 @@ func (p *ExecOptions) Run() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type terminalSizeQueueAdapter struct {
|
||||
delegate term.TerminalSizeQueue
|
||||
}
|
||||
|
||||
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
|
||||
next := a.delegate.Next()
|
||||
if next == nil {
|
||||
return nil
|
||||
}
|
||||
return &remotecommand.TerminalSize{
|
||||
Width: next.Width,
|
||||
Height: next.Height,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -206,7 +206,7 @@ func (flags *ExposeServiceFlags) AddFlags(cmd *cobra.Command) {
|
|||
cmd.Flags().StringVar(&flags.Port, "port", flags.Port, i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
|
||||
cmd.Flags().StringVar(&flags.Type, "type", flags.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
|
||||
cmd.Flags().StringVar(&flags.LoadBalancerIP, "load-balancer-ip", flags.LoadBalancerIP, i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)."))
|
||||
cmd.Flags().StringVar(&flags.Selector, "selector", flags.Selector, i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)"))
|
||||
cmd.Flags().StringVar(&flags.Selector, "selector", flags.Selector, i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the resource being exposed."))
|
||||
cmd.Flags().StringVarP(&flags.Labels, "labels", "l", flags.Labels, "Labels to apply to the service created by this call.")
|
||||
cmd.Flags().StringVar(&flags.TargetPort, "target-port", flags.TargetPort, i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional."))
|
||||
cmd.Flags().StringVar(&flags.ExternalIP, "external-ip", flags.ExternalIP, i18n.T("Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP."))
|
||||
|
@ -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{
|
||||
|
@ -179,14 +182,14 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStre
|
|||
cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.")
|
||||
cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
|
||||
cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.")
|
||||
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
|
||||
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If set to true, suppresses NotFound error for specific objects that do not exist. Using this flag with commands that query for collections of resources has no effect when no resources are found.")
|
||||
cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
addServerPrintColumnFlags(cmd, o)
|
||||
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)
|
||||
|
@ -619,6 +623,10 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
|
|||
}
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
// Ignore "NotFound" error when ignore-not-found is set to true
|
||||
if apierrors.IsNotFound(err) && o.IgnoreNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if multipleGVKsRequested(infos) {
|
||||
|
@ -627,7 +635,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
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -711,7 +712,7 @@ func TestGetEmptyTable(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetObjectIgnoreNotFound(t *testing.T) {
|
||||
func TestGetNonExistObject(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
|
||||
ns := &corev1.NamespaceList{
|
||||
|
@ -745,6 +746,63 @@ func TestGetObjectIgnoreNotFound(t *testing.T) {
|
|||
}),
|
||||
}
|
||||
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)"
|
||||
if str != expectedErr {
|
||||
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
|
||||
}
|
||||
})
|
||||
|
||||
// Get nonexistentpod fails with above error message
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdGet("kubectl", tf, streams)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
|
||||
}
|
||||
|
||||
func TestGetNonExistObjectIgnoreNotFound(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
|
||||
ns := &corev1.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Items: []corev1.Namespace{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testns", Namespace: "test", ResourceVersion: "11"},
|
||||
Spec: corev1.NamespaceSpec{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
case p == "/api/v1/namespaces/test" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &ns.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
expectedErr := ""
|
||||
if str != expectedErr {
|
||||
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
|
||||
}
|
||||
})
|
||||
|
||||
// Get nonexistentpod passes without error when setting ignore-not-found to true
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdGet("kubectl", tf, streams)
|
||||
cmd.SetOut(buf)
|
||||
|
@ -1438,7 +1496,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "foo",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "10"
|
||||
|
@ -1457,7 +1514,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "bar",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "11"
|
||||
|
@ -1476,7 +1532,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "baz",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "12"
|
||||
|
@ -2129,6 +2184,93 @@ foo <unknown>
|
|||
}
|
||||
}
|
||||
|
||||
func TestWatchNonExistObject(t *testing.T) {
|
||||
pods, _ := watchTestData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
case p == "/api/v1/namespaces/test" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil
|
||||
default:
|
||||
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)"
|
||||
if str != expectedErr {
|
||||
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
|
||||
}
|
||||
})
|
||||
|
||||
// Get nonexistentpod fails with above error message
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdGet("kubectl", tf, streams)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.Flags().Set("watch", "true") //nolint:errcheck
|
||||
cmd.Flags().Set("output", "yaml") //nolint:errcheck
|
||||
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
|
||||
|
||||
if buf.String() != "" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchNonExistObjectIgnoreNotFound(t *testing.T) {
|
||||
pods, _ := watchTestData()
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
case p == "/api/v1/namespaces/test" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil
|
||||
default:
|
||||
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
expectedErr := ""
|
||||
if str != expectedErr {
|
||||
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
|
||||
}
|
||||
})
|
||||
|
||||
// Get nonexistentpod passes without error when setting ignore-not-found to true
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdGet("kubectl", tf, streams)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.Flags().Set("ignore-not-found", "true") //nolint:errcheck
|
||||
cmd.Flags().Set("watch", "true") //nolint:errcheck
|
||||
cmd.Flags().Set("output", "yaml") //nolint:errcheck
|
||||
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
|
||||
|
||||
if buf.String() != "" {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchStatus(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}})
|
||||
|
@ -2367,10 +2509,10 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
|
|||
},
|
||||
{
|
||||
format: "json",
|
||||
expected: `{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"bar","namespace":"test","resourceVersion":"9"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"10"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"MODIFIED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"11"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"DELETED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"12"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
expected: `{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"bar","namespace":"test","resourceVersion":"9"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"10"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"MODIFIED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"11"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
{"type":"DELETED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"12"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -2379,7 +2521,6 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: bar
|
||||
namespace: test
|
||||
resourceVersion: "9"
|
||||
|
@ -2397,7 +2538,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "10"
|
||||
|
@ -2415,7 +2555,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "11"
|
||||
|
@ -2433,7 +2572,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "12"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue