Compare commits

...

107 Commits

Author SHA1 Message Date
Kubernetes Publisher e7f17cb570 Merge pull request #133018 from rushmash91/add-port-names-to-describe-pod
kubectl: add port names to describe pod output

Kubernetes-commit: abbee0108de419a88be21ccee8e723cd260be417
2025-07-18 09:52:18 +00:00
Kubernetes Publisher 29d75ee358 Merge pull request #133020 from pohly/apimachinery-list-map-keys
support optional listMapKeys in server-side apply

Kubernetes-commit: d33af7f7efc7ff5d8813a85290189883167a5226
2025-07-17 18:05:30 +00:00
arush sharma 8067f3a1a6 kubectl: add port names to describe pod output
Kubernetes-commit: 1d0fd5928818036289a76358503778eff26e53ef
2025-07-16 22:47:06 -07:00
Jordan Liggitt 483c28b281 sigs.k8s.io/structured-merge-diff/v6 v6.3.0
Kubernetes-commit: 4d34975a46658efd90274c5fe05d46732e04ca67
2025-07-16 16:57:41 -04:00
Kubernetes Publisher 2e87981eff Merge pull request #132871 from dims/bump-k8s.io/kube-openapi-to-latest-SHA-f3f2b991d03b
Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b) and sigs.k8s.io/structured-merge-diff/{v4 => v6}

Kubernetes-commit: 48e04d0d6c43cfc4729857775252b89b68d65b87
2025-07-15 10:08:22 +00:00
Davanum Srinivas f47e5c84cc Bump k8s.io/kube-openapi to latest SHA (f3f2b991d03b)
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: ebc1ccc491c944fa0633f147698e0dc02675051d
2025-07-10 09:21:52 -04:00
Gemma Hou 76e6818d8d Improve ignore-not-found behavior (#132542)
* Improve ignore-not-found behavior

* ignore lint errcheck

Kubernetes-commit: a7e8a505c25965c074f2b10b1bf40230eca48a08
2025-07-03 01:53:24 +00:00
Kubernetes Publisher f07a946956 Merge pull request #132675 from dims/bump-sigs-k8s-io-json-no-code-changes
Bump sigs.k8s.io/json to latest - no code changes

Kubernetes-commit: e47ac3eb6faa97874658dc281c72b5623f994801
2025-07-03 02:06:09 +00:00
Kubernetes Publisher 45a8bb4283 Merge pull request #132677 from dims/update-github.com/emicklei/go-restful/v3-to-v3.12.2
Update github.com/emicklei/go-restful/v3 to v3.12.2

Kubernetes-commit: 305c0e06c99ff9df013edd804294217a04e4dd31
2025-07-02 22:03:18 +00:00
Kubernetes Publisher 0b7534c13c Merge pull request #132676 from dims/bump-go.yaml.in/yaml/v3-to-v3.0.4
Bump go.yaml.in/yaml/v3 to v3.0.4

Kubernetes-commit: 01c03ae9cf7b1371c8bc2bdf12d9244e63e83750
2025-07-02 18:08:44 +00:00
Davanum Srinivas e3a465587f Update github.com/emicklei/go-restful/v3 to v3.12.2
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: b44b0fbf1299c6821033076352b91914d2efef67
2025-07-02 08:00:43 -04:00
Davanum Srinivas e470ab29d4 Bump go.yaml.in/yaml/v3 to v3.0.4
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 58e620cc4403d30f9fb6aab245cfb47db17957de
2025-07-02 07:37:06 -04:00
Kubernetes Publisher 02bb9287e7 Merge pull request #132003 from tchap/kubectl-apply-cache-openapi-schema
kubectl: Cache Verifier.HasSupport calls

Kubernetes-commit: 95bff1b249e048b7e36ae857d7478dd2ac05346a
2025-07-02 13:52:51 +00:00
Davanum Srinivas 8aac463c8e Bump sigs.k8s.io/json to latest - no code changes
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 00f8cbae6b8fd3799a1a044abcefdbb572d35b27
2025-07-02 07:32:24 -04:00
Kubernetes Publisher 142b144574 Merge pull request #132654 from Jefftree/b-openapi
Bump kube-openapi

Kubernetes-commit: db49c25956df36c777213251c4a47d6d9ee1c5ea
2025-07-01 22:07:45 +00:00
Jefftree 7c82ae36f3 Update vendor
Kubernetes-commit: d04ee27c98ba91680ac6c6a8ade9e33d7ee44569
2025-07-01 15:23:58 +00:00
Jefftree d97446c293 pin kube-openapi to v0.0.0-20250628140032-d90c4fd18f59
Kubernetes-commit: b41d375b8881f25ff5fe7775b4dedaba1eaa3f02
2025-07-01 15:21:22 +00:00
Kubernetes Publisher a8605c1ee2 Merge pull request #131791 from win-t/master
feat: kubectl debug: add label for debugger pod for easy cleaning up the debug pods

Kubernetes-commit: ce06b1d93411e1973777566337c36c22a553f385
2025-07-01 17:51:52 +00:00
Kubernetes Publisher 1d576b52f0 Merge pull request #132567 from janetkuo/kubectl-top-help
Enhance help text for the 'top' command

Kubernetes-commit: c1e421a4136ff6491865a2d0e9146090e62d3841
2025-06-30 22:20:19 +00:00
Kubernetes Publisher a8c44498b7 Merge pull request #128779 from alexey-gavrilov-flant/fix/kubectl-drain
kubectl: drain daemonSetFilter with other APIVersion

Kubernetes-commit: 6ed5b60f71930d51bfcf8bfa6f3506b099811318
2025-06-27 06:14:01 +00:00
Janet Kuo daa78b3004 Enhance help text for the 'top' command
* Detailing the metrics source
* Connecting to HPA
* Defining its scope

Kubernetes-commit: e54e01e0abbff795a5628f2e845c815e6d43c03f
2025-06-26 16:53:50 -07:00
Kubernetes Publisher 9892d492ab Merge pull request #129373 from googs1025/feature/kubectl/autoscale
feature(kubectl): support --cpu, --memory flag to kubectl autoscale

Kubernetes-commit: 3ce7034a7f321bdc8223850cc805f01dc9126621
2025-06-25 22:13:39 +00:00
Kubernetes Publisher fe571ee1bb Merge pull request #132357 from dims/drop-usage-of-forked-copies-of-goyaml.v2-and-goyaml.v3
Drop usage of forked copies of goyaml.v2 and goyaml.v3

Kubernetes-commit: c1afec6a0b15ca1ed853c1321ac2c972488bf5b8
2025-06-25 18:27:57 +00:00
Kubernetes Publisher 4966ba7250 Merge pull request #132480 from pohly/kubectl-init-logging
kubectl: avoid logging during init

Kubernetes-commit: b832d100f6d7a165093199a05139de641329a77f
2025-06-25 18:27:55 +00:00
Kubernetes Publisher 1c1291fbf7 Merge pull request #132504 from jpbetz/name-formats
Introduce OpenAPI format support for k8s-short-name and k8s-long-name

Kubernetes-commit: 1d932bd6cc951b9182d07d701946aebaf667df94
2025-06-25 18:27:54 +00:00
Davanum Srinivas 7e06b5277c switch to latest sigs.k8s.io/yaml v1.5.0 (run update-gofmt.sh as well)
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: c5b4b133ce3252ee19b7167eb69a99d88fdefda8
2025-06-25 08:03:06 -04:00
Joe Betz 6b06b293f9 Bump to latest kube-openapi
Kubernetes-commit: dc323756cea2d1ebe32d7acb5a14a1769c14486f
2025-06-24 09:24:27 -04:00
Patrick Ohly da311e8e7e kubectl: avoid logging during init
LoadTranslations gets called during the init phase:

     0  0x0000000005926c56 in k8s.io/kubectl/pkg/util/i18n.LoadTranslations
        at ./staging/src/k8s.io/kubectl/pkg/util/i18n/i18n.go:146
     1  0x0000000005926727 in k8s.io/kubectl/pkg/util/i18n.init.func1
        at ./staging/src/k8s.io/kubectl/pkg/util/i18n/i18n.go:60
     2  0x000000000592780f in k8s.io/kubectl/pkg/util/i18n.lazyLoadTranslations.func1
        at ./staging/src/k8s.io/kubectl/pkg/util/i18n/i18n.go:191
     3  0x0000000001b876e8 in sync.(*Once).doSlow
        at /nvme/gopath/go-1.24.0/src/sync/once.go:78
     4  0x0000000001b8753e in sync.(*Once).Do
        at /nvme/gopath/go-1.24.0/src/sync/once.go:69
     5  0x0000000005927565 in k8s.io/kubectl/pkg/util/i18n.lazyLoadTranslations
        at ./staging/src/k8s.io/kubectl/pkg/util/i18n/i18n.go:187
     6  0x00000000059275cd in k8s.io/kubectl/pkg/util/i18n.T
        at ./staging/src/k8s.io/kubectl/pkg/util/i18n/i18n.go:201
     7  0x000000000599fb6d in k8s.io/kubectl/pkg/cmd/apiresources.init
        at <autogenerated>:1
     8  0x0000000001b41bf4 in runtime.doInit1
        at /nvme/gopath/go-1.24.0/src/runtime/proc.go:7350
     9  0x0000000001b6bf8a in runtime.doInit
        at /nvme/gopath/go-1.24.0/src/runtime/proc.go:7317
    10  0x0000000001b33910 in runtime.main
        at /nvme/gopath/go-1.24.0/src/runtime/proc.go:254
    11  0x0000000001b72881 in runtime.goexit
        at /nvme/gopath/go-1.24.0/src/runtime/asm_amd64.s:1700

During init, klog verbosity is either zero (making the log call redundant
because it doesn't print anything) or some other init function reconfigures
logging, in which case the output is potentially confusing because it is not
guaranteed that logging is reconfigured before the log call is invoked.

In other scenarios, flag parsing might switch from klog text format to
something else entirely, which then leads to a mixture of text and e.g. JSON
output. In general, code running during init should not log.

Kubernetes-commit: 0276769c2c85e14902f39760abce82512aa6b120
2025-06-24 11:07:47 +02:00
totegamma 2fa4a24584 show namespace on delete (#126619)
* show namespace on delete

* update kubectl delete message

* add test 'TestDeleteMessageOutput'

* update delete_test.go

Kubernetes-commit: 75862f3f461b8c83f3cbbf441ce4fd5357d3bba0
2025-06-24 02:15:09 +00:00
Davanum Srinivas a6fde79de4 Drop usage of forked copies of goyaml.v2 and goyaml.v3
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 3827d3bc4f1c17ac816b37422dbd449c276e9ff0
2025-06-17 11:00:27 -04:00
Kubernetes Publisher 2da25b2322 Merge pull request #132269 from dims/update-to-latest-github.com/modern-go/reflect2
Update to latest github.com/modern-go/reflect2

Kubernetes-commit: d55b119d34883bbad2a3436dcb6c62339d963031
2025-06-12 21:02:18 +00:00
Kubernetes Publisher 110ba5998e Merge pull request #132251 from ardaguclu/kubectl-interactive-delete-fix
kubectl delete: update interactive delete to break on new line

Kubernetes-commit: dac3c09bd16643dbac5e976e85d81825dd226ac0
2025-06-12 16:46:28 +00:00
Davanum Srinivas 716737e46a Update to latest github.com/modern-go/reflect2
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 3908550c0dc189cfa9de38a84bee508fa0659463
2025-06-12 11:20:39 -04:00
Arda Güçlü 8a11631ed9 kubectl delete: Update interactive delete to break on new line
Kubernetes-commit: fb611f4c93e6d8d8eb920f4436af748432402f50
2025-06-12 11:27:51 +03:00
Kubernetes Publisher 330f86df8e Merge pull request #132209 from dims/update-github.com/spf13/cobra-v1.9.1eksctl
update github.com/spf13/cobra v1.9.1

Kubernetes-commit: dc19f0b6b9cd14ece6b1929cb4e7ea2c9d322b95
2025-06-10 20:54:36 +00:00
Kubernetes Publisher 55101ca654 Merge pull request #131500 from HaraldNordgren/sort_configmaps
kubectl: sort configmap data alphabetically and simplify display format

Kubernetes-commit: 447efd4c0f45f13d262423265a8a960666ed3778
2025-06-10 20:54:34 +00:00
Harald Nordgren ab62ac8cf1 Revert "improve display format"
This reverts commit 28e7acf0f93f5c9edb43ab09cd181e8847409215.

Kubernetes-commit: f8b701243503fc10a037f86e77ce4df5741a07c6
2025-06-10 16:56:58 +02:00
Davanum Srinivas af5ade99d8 update github.com/spf13/cobra v1.9.1
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 449320a54a2dac04f953d5f6401d875ea9b7e8de
2025-06-10 10:50:54 -04:00
Kubernetes Publisher 4d27286e9c Merge pull request #132103 from nojnhuh/typed-ring-buffer
Replace queue.FIFOs with k8s.io/utils/buffer.Ring

Kubernetes-commit: 5090812df4fb6cf09a9181635d90c2e154eab8cc
2025-06-06 21:12:11 +00:00
Kubernetes Publisher 9130183f39 Merge pull request #132110 from jpbetz/gengo-bump
Bump gengo/v2 to latest, pick up related validation-gen fixes

Kubernetes-commit: 4fff091ce7c8b22e6a511231e400adb865a7b300
2025-06-05 21:39:08 +00:00
Joe Betz e808dbbf1f Bump gengo/v2 to latest
Kubernetes-commit: ac5cb23000f57b7b034fa98e5cc4f6e8fb6e8a9c
2025-06-04 22:39:08 -04:00
Jon Huhn 579a7a5a35 Update k8s.io/utils for new generic ring buffer
Kubernetes-commit: 8cdbbf5cdaef7e37cfd432e9044aa52f4d42adcd
2025-06-04 12:09:53 -05:00
Itamar Holder 0b4adb247f [KEP-2400] kubectl top: add a --show-swap option (#129458)
* top, refactor: turn package-exposed variables to unexpose struct fields

Signed-off-by: Itamar Holder <iholder@redhat.com>

* kubectl top node: add the --show-swap option

Example output:
> kubectl top node --show-swap
NAME     CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)   SWAP(bytes)   SWAP(%)
node01   500m         8%       2836Mi          60%         0Mi           0%
node02   260m         5%       2206Mi          47%         512Mi         50%

Signed-off-by: Itamar Holder <iholder@redhat.com>

* kubectl top pod: add the --show-swap option

Example output:
> kubectl top pod -n kube-system --show-swap
NAME                                      CPU(cores)   MEMORY(bytes)   SWAP(bytes)
coredns-58d5bc5cdb-5nbk4                  2m           19Mi            0Mi
coredns-58d5bc5cdb-jsh26                  3m           37Mi            0Mi
etcd-node01                               51m          143Mi           0Mi
kube-apiserver-node01                     98m          824Mi           0Mi
kube-controller-manager-node01            20m          135Mi           0Mi
kube-proxy-ffgs2                          1m           24Mi            0Mi
kube-proxy-fhvwx                          1m           39Mi            0Mi
kube-scheduler-node01                     13m          69Mi            0Mi
metrics-server-8598789fdb-d2kcj           5m           26Mi            0Mi

Signed-off-by: Itamar Holder <iholder@redhat.com>

* kubectl top node --show-swap: add unit tests

Signed-off-by: Itamar Holder <iholder@redhat.com>

* kubectl top pod --show-swap: Add unit tests

Signed-off-by: Itamar Holder <iholder@redhat.com>

* Explicitly mark swap as unavailable when necessary

Signed-off-by: Itamar Holder <iholder@redhat.com>

---------

Signed-off-by: Itamar Holder <iholder@redhat.com>

Kubernetes-commit: 8d3fb9ee0a51b6a6ea135d991391c35806422c19
2025-06-04 17:10:38 +03:00
Kubernetes Publisher 49afb3c466 Merge pull request #132055 from soltysh/default_prefs_tests
kuberc: add tests for DefaultGetPreferences

Kubernetes-commit: e9b3d62c9aa2c25d745fdf56a7ce5b29ff72973d
2025-06-03 20:43:23 +00:00
Maciej Szulik 17c0dde6b1 kuberc: add tests for DefaultGetPreferences
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 68efb079aabd3295ff4ee5cc56c89ff266e9224d
2025-06-02 16:47:02 +02:00
Kubernetes Publisher bc4c094b08 Merge pull request #131984 from soltysh/update_sigcli_security
Update security contacts for sig-cli owned repos

Kubernetes-commit: cefa8311bca466462734d9c1a24e549e7f94ba19
2025-05-28 18:35:03 +00:00
Kubernetes Publisher 800afb48a7 Merge pull request #131951 from dims/drop-usages-of-deprecated-otelgrpc-methods
Drop usages of deprecated otelgrpc methods (update to v0.60.0)

Kubernetes-commit: d9c1b4ec9b3df7f09dc23a0cc2b3daf2506d3688
2025-05-27 18:46:20 +00:00
Maciej Szulik 2375a3a9f6 Update security contacts for sig-cli owned repos
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 4e3634bbbfb2e416d991075a2a99ab92b0e3da19
2025-05-27 16:51:51 +02:00
Kubernetes Publisher 18f24e791d Merge pull request #131964 from skitt/kubectl-pkg-errors
kubectl: drop dependency on github.com/pkg/errors

Kubernetes-commit: 4c2a741aacc6eea0ae1b0b345034434f9473f52b
2025-05-26 23:12:30 +00:00
Kubernetes Publisher 9c13527bac Merge pull request #131962 from superbrothers/kubectl-explain-revert-output-shorthand
Revert shorthand for kubectl explain --output

Kubernetes-commit: b2dfba4151b859c31a27fe31f8703f9b2b758270
2025-05-26 18:33:18 +00:00
Kubernetes Publisher 17bb82b84d Merge pull request #131818 from soltysh/kuberc_beta
Promote kuberc to beta

Kubernetes-commit: fe5b9896ae8601b044a4e47901a844c93070df3b
2025-05-26 14:50:31 +00:00
Stephen Kitt abe43f6e92 kubectl: drop dependency on github.com/pkg/errors
The package is unmaintained, and kubectl doesn't rely on the
functionality it provides on top of Golang errors (stack traces).

Signed-off-by: Stephen Kitt <skitt@redhat.com>

Kubernetes-commit: 54b2fad0330032ae1bbac990f93a3644aa8a12af
2025-05-26 10:44:46 +02:00
Kazuki Suda bb9c5182ea Revert shorthand for kubectl explain --output
Kubernetes-commit: df2857e7777f18c482359cfb43a72a3cdfd89646
2025-05-26 12:16:24 +09:00
Davanum Srinivas 4ee16d2b51 Drop usages of deprecated otelgrpc methods
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 7c0f968ab256486b524ea37014ccf580b12c73e4
2025-05-23 19:40:36 -07:00
Ondra Kupka edad3048e9 kubectl: Cache Verifier.HasSupport calls
The underlying implementation decodes OpenAPI schema on every HasSupport
call. This causes these calls to be expensive and noticable when many
resources are being applied.

This patch wraps the verifier with a thread-safe cache so that when many
resources of the same kind are being checked, only the first call
includes schema decoding.

This solution is specifically limited to the kubectl codebase without
any changes required in client-go, for now, although that is where the
issue is actually originating.

Kubernetes-commit: 9043afae6d98585dd14d203d48807b14b433d730
2025-05-23 21:58:05 +02:00
Maciej Szulik 46d6f63709 kuberc: make update and update-vendor
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 76f95271a5d563ea31edbcd8f0e8eebf67cb3634
2025-05-15 14:15:20 +02:00
Maciej Szulik 8500d2979d kuberc: introduce fuzzing for kuberc types
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: c29accaf5819bd4b05409cecb9bbb3ba065d840b
2025-05-16 13:45:37 +02:00
Kurnia D Win 4b5ec542ad feat: kubectl debug: add label for debugger pod
Kubernetes-commit: e9fcdabcf5072de662d9c02f83df5f0ac80c2a43
2025-05-15 17:32:06 +07:00
Arda Güçlü ca5a831a47 Promote kuberc to beta
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: b0370c483af1bdbc1e2664f53445411c585c4559
2025-05-06 12:52:20 +03:00
Maciej Szulik b011cffff8 kuberc: pick the first known version when decoding + tests
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 1f355e5b44141be55da269a9183d53bbccd16c95
2025-05-15 16:24:43 +02:00
Maciej Szulik cb7efba696 kuberc: align internal and v1alpha1 go-types with v1beta1
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 0341b27c5d0dfb1d10818c9976f54af22971bedc
2025-05-21 13:32:37 +02:00
Maciej Szulik 2be4847754 kuberc: add v1beta1 types
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 39195f9a463cce6b3ce4e3d9f648c2915407596d
2025-05-15 14:14:59 +02:00
Kubernetes Publisher 6096dfa3cf Merge pull request #131741 from soltysh/deprecate_preferences
Deprecate kubeconfig's preference field in favor of kuberc

Kubernetes-commit: c40db09a0a2bf8df02be93d7da1912cfa7cbe333
2025-05-20 16:37:25 +00:00
Kubernetes Publisher 279ddf3abe Merge pull request #131838 from dims/bump-google.golang.org/grpc-to-google-v1.72.1
Bump google.golang.org/grpc to google v1.72.1

Kubernetes-commit: 444e2b4eb079727d7cdd81ad25041502656370b8
2025-05-20 16:37:21 +00:00
Omer Aplatony 5ff92a69e3 Kubectl: check version skew (#127365)
Signed-off-by: Omer Aplatony <omerap12@gmail.com>

Kubernetes-commit: 35307319740a3a52cf4632c24b7f99d675537bdf
2025-05-19 20:19:14 +03:00
Davanum Srinivas 777f5e3cd1 Bump google.golang.org/grpc v1.72.1
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 9b3830fba234bc4a4f09a1ad4417e4d18b74d6dc
2025-05-18 12:52:05 -04:00
Kubernetes Publisher 4afda566a9 Merge pull request #128419 from liggitt/etcd-3.6
etcd 3.6 client update

Kubernetes-commit: 09ca440a450e9103a8f835f598c09237dba6ecbb
2025-05-16 04:42:29 +00:00
Jordan Liggitt c37ca76b9c bump etcd client to 3.6
hack/pin-dependency.sh go.etcd.io/etcd/api/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/client/pkg/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/client/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/pkg/v3 v3.6.0
hack/pin-dependency.sh go.etcd.io/etcd/server/v3 v3.6.0

hack/pin-dependency.sh github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0

hack/update-vendor.sh

Kubernetes-commit: cf0bbf1171e918d5d7ba1d3c83b5f347fc8333b0
2025-05-15 21:19:11 -04:00
Maciej Szulik 47f13bd18b Deprecate kubeconfig's preference field in favor of kuberc
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 8cf5e8db78deb186ef362f64ab779c09e9520156
2025-05-13 13:39:13 +02:00
Kubernetes Publisher 88bb12ba04 Merge pull request #131672 from soltysh/kubectl_featuregate_cleanup
Kubectl FeatureGate cleanups

Kubernetes-commit: 8812a3dc3ed1cbd3c7dc38b8027e014672cf1fb1
2025-05-08 13:21:14 -07:00
Maciej Szulik 52ec1da081 Add comment describing the feature gate with a link to KEP
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 9e3a1b0a9081ab49bbc44c415525dce862eb6a12
2025-05-08 14:43:24 +02:00
Maciej Szulik 307936eb9d Swap KUBECTL_COMMAND_HEADERS to use the proper feature gate mechanism
Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: e3f3da5e795960508089aed08fe7fd9bec0a6db2
2025-05-08 14:09:17 +02:00
Maciej Szulik 90ee929b88 Drop KUBECTL_ENABLE_CMD_SHADOW featgure gat entirely
https://kep.k8s.io/3638 has been promoted to stable back in 1.32 so now
is the right time to drop this feature gate entirely.

Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: d1b5f268b48eda4bb8acdeef52407c27add9e076
2025-05-08 14:05:19 +02:00
Kubernetes Publisher 105c831190 Merge pull request #131668 from soltysh/command_creation_logging
Manually read verbosity before kubectl command construction

Kubernetes-commit: 4e30b51ebcdc24a65a0a5edfde0b9706c95d43d0
2025-05-08 20:24:14 +00:00
Maciej Szulik d35aa2c630 Manually read verbosity before kubectl command construction
kubectl command construction is slowly getting more functionality which
sometimes requires to log certain actions. Currently we parse the
verbosity only when actually running the command, so all of construction
code is not able to use -v=5. This commit adds the manual parsing and
loglevel setting berore we even start creating the kubectl command.

Signed-off-by: Maciej Szulik <soltysh@gmail.com>

Kubernetes-commit: 69682b75e508462e01f865a156f2171233b653d1
2025-05-08 13:29:11 +02:00
Kubernetes Publisher f4a8c5b53e Merge pull request #131620 from ardaguclu/drop-custom-profile-feature
Drop KUBECTL_DEBUG_CUSTOM_PROFILE feature gate entirely

Kubernetes-commit: 8b00a96b51e82075be9770282c9c82507d8abf38
2025-05-07 16:19:08 +00:00
Arda Güçlü 5bcd2add11 Drop KUBECTL_DEBUG_CUSTOM_PROFILE feature gate entirely
Kubernetes-commit: 94d043b149b845a4f02f12a3d318df278194d377
2025-05-06 13:00:14 +03:00
Kubernetes Publisher bb3c0d9f3a Merge pull request #131586 from ardaguclu/kuberc-completion
Continue alias creation when __completion is used to enable completion

Kubernetes-commit: 893486dfd16ff8c628c6f33bb2bea869ad86115f
2025-05-05 08:19:24 +00:00
Kubernetes Publisher 3323e167c5 Merge pull request #131595 from aojea/utils_fake_clock
update k8s.io/utils to bring fakeClock.Waiters()

Kubernetes-commit: e3e1f80c0110c847acf4381b1790c1c667395010
2025-05-03 04:20:15 +00:00
Antonio Ojea 3013d81bdf update k8s.io/utils to bring fakeClock.Waiters()
Change-Id: I7e25338df225c2c27457403fbc2f158d08638f87

Kubernetes-commit: c2c003a71fc52fa79c2fff0109afad58573d0216
2025-05-02 11:21:11 +00:00
Kubernetes Publisher 38e8d36c38 Merge pull request #130989 from liggitt/creationTimestamp-omitzero
Omit null creationTimestamp

Kubernetes-commit: 01899a7c86337b05a16a4155c9351cf947beaee9
2025-05-03 00:24:11 +00:00
Arda Güçlü b0f5f0c0aa Continue alias creation when __completion is used to enable completion
Kubernetes-commit: f6d0498017fc71c72891ad9455c4391aa63c6e71
2025-05-02 13:23:22 +03:00
Harald Nordgren 65d852d39c improve display format
Kubernetes-commit: 28e7acf0f93f5c9edb43ab09cd181e8847409215
2025-04-27 20:35:38 +02:00
Harald Nordgren 7ec7bb7cc1 kubectl: sort configmaps alphabetically to avoid random order
Kubernetes-commit: f30c23a7831a25266a56b9b2990fa1ca19b48ad5
2025-04-27 15:36:09 +02:00
Jordan Liggitt 722397942b Drop null creationTimestamp from test fixtures
Kubernetes-commit: 6bb6c9934294d8265197c9dfc4c9dd3adaca147a
2025-03-24 09:37:26 -04:00
Jordan Liggitt 0cdb311ed6 bump cbor to add omitzero support
Kubernetes-commit: bc6051717137cef288b82305588e675de4a32c0d
2025-03-25 12:27:43 -04:00
Jordan Liggitt 4d172bd365 bump structured-merge-diff to add omitzero support
Kubernetes-commit: 06b0784062f68566daa8eed83c475b738dcf620c
2025-03-24 16:34:01 -04:00
Kubernetes Publisher 285ed6ce48 Merge pull request #131491 from tchap/kubectl-service-describe-trafficdistribution
kubectl describe service: Add Traffic Distribution

Kubernetes-commit: a19c0ad5533d1503825f22a3725de07cb81fced1
2025-04-28 12:05:03 +00:00
Ondra Kupka 4627533853 kubectl describe service: Add Traffic Distribution
Kubernetes-commit: ad40bc88568bdb19fdba0b960755bd014b2ae5e5
2025-04-26 18:02:19 +02:00
Rodrey ae92d5f0bd Add more test cases to TestDescribeSecret test (#131422)
* Converted to parameterised tests.

* Added test case for sorting with casing.

* Formatted code.

* Added test case for keys that contain numbers.

Kubernetes-commit: 74e84dbf5a339a3830ddd172fe8767ca0952cbb8
2025-04-24 20:10:05 +00:00
Kubernetes Publisher 3a0b77ee9b Merge pull request #131097 from ardaguclu/increase-cp-timeout
Increase kubectl cp command timeout to 30 seconds

Kubernetes-commit: 8237bc5035c7cdec4df561f56e9e68a4c26024f0
2025-04-24 00:26:44 +00:00
Kubernetes Publisher 9301b2a1f6 Merge pull request #130995 from xigang/utils
bump k8s.io/utils for improvements

Kubernetes-commit: 43a7d3be12425cc80ca6ad3599809a19728c5566
2025-04-24 00:26:43 +00:00
Kubernetes Publisher d40094d4e4 Merge pull request #130987 from arthurbdiniz/kubectl-diff-testing-language-env
Set LANGUAGE env variable in TestDiffProgram for consistent locale be…

Kubernetes-commit: da7f3cef166af40644ed9265d7a6a9d7a7e842d3
2025-04-24 00:26:42 +00:00
Taha Farahani 46f95a7c68 Unhandled panic crash on rollout_history printer.PrintObj (#130503)
* Change: Handling nil runtime.Object

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Return only if there is error in rollout_history

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Return the unknown revision error directly in rollout_history.go

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Remove unintended newline

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Using go idiomatic way for checking if historyInfo[o.Revision] exists

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Remove 'error:' from returned error message in rollout_history.go

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Check for printer.PrintObj returned err

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Add TestRolloutHistoryErrors test

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Simple typo fix on Complete() function description

Signed-off-by: Taha Farahani <tahacodes@proton.me>

* Change: Checking for error on o.Complete in TestRolloutHistoryErrors

Signed-off-by: Taha Farahani <tahacodes@proton.me>

---------

Signed-off-by: Taha Farahani <tahacodes@proton.me>

Kubernetes-commit: 609e4a9ba044e64a6244e053d2b1b7c545a2d2ed
2025-04-24 00:01:14 +03:30
Kubernetes Publisher b2ed890887 Merge pull request #130309 from HaraldNordgren/sort_secrets
kubectl: sort secrets alphabetically to avoid random order

Kubernetes-commit: 0130ac1422cf7e4af9d1ffd6c5fae24fe3eb320b
2025-04-24 00:26:40 +00:00
Kubernetes Publisher 265eadfda5 Merge pull request #127183 from mochizuki875/add_attach_warning
Add warning message for attach

Kubernetes-commit: d63a52a007da693cb40d2cfaef4f738cee104ab1
2025-04-24 00:26:39 +00:00
Harald Nordgren 6c0aa1995f pr fix
Kubernetes-commit: 4e3026fdb81667fd5a9fe928736b6e4d67b225e5
2025-04-14 15:59:45 +02:00
Kubernetes Publisher 72b3a7e9b0 Merge pull request #131204 from dims/move-to-released-version-of-prometheus/client_golang-v1.22.0-from-rc.0
Move to released version of prometheus/client_golang v1.22.0 from rc.0

Kubernetes-commit: 92af6ab6926f192a3d4543a1d6fa39f20edad3ea
2025-04-08 23:34:14 +00:00
Davanum Srinivas 2e566591a6 Move to released version of prometheus/client_golang v1.22.0 from rc.0
Signed-off-by: Davanum Srinivas <davanum@gmail.com>

Kubernetes-commit: 2ef4a8426c2c1b6e3495de08c4686382a752f8f7
2025-04-08 08:35:18 -04:00
Kubernetes Publisher fec9d5b3d5 Merge pull request #131103 from ahrtr/etcd_sdk_20250328
Bump etcd 3.5.21 sdk

Kubernetes-commit: f4d1686120d2367dd4c00df53e93dad51c414435
2025-04-01 11:49:33 +00:00
Arda Güçlü 399c585899 Increase kubectl cp command timeout to 30 seconds
Kubernetes-commit: 9c228e81d2481150878ca3a431f0ec9a8f150b3f
2025-03-28 12:39:01 +03:00
xigang ece3c8c1d0 bump k8s.io/utils
Kubernetes-commit: fe14689f221a968806b771b226581efb834654cd
2025-03-22 10:14:01 +08:00
Arthur Diniz 5b96de1a99 Set LANGUAGE env variable in TestDiffProgram for consistent locale behavior
Signed-off-by: Arthur Diniz <arthurbdiniz@gmail.com>

Kubernetes-commit: bad6c7e4cc5aef010c2fe2abfcbe51a138a04700
2025-03-21 17:28:41 +00:00
Harald Nordgren 3a69c59961 kubectl: sort secrets alphabetically to avoid random order
Kubernetes-commit: 7d6f86594fc0a7d09710c643de63b24cdcb98e65
2025-02-20 13:28:55 +01:00
googs1025 e33a30ea7c feature(kubectl): support mem-percent,cpu-value,cpu-average-value,mem-value,mem-average-value flag to kubectl autoscale
Signed-off-by: googs1025 <googs1025@gmail.com>

Kubernetes-commit: 6795d5366f11b7bc782223beaa4f81bef04f751c
2024-12-24 10:01:25 +08:00
Aleksey Gavrilov 6323c5bc57 [kubectl] drain daemonSetFilter with other APIVersion
Signed-off-by: Aleksey Gavrilov <alexey.gavrilov@flant.com>

Kubernetes-commit: a52863827bafa64a678c1291f066d2c8b85217af
2024-11-13 10:23:54 +03:00
mochizuki875 81562142de Add warning message for attach
Kubernetes-commit: 97dd6dc284682d76fe7e0bc51df851473deee24f
2024-09-06 02:31:14 +00:00
97 changed files with 3581 additions and 808 deletions

View File

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

55
go.mod
View File

@ -13,45 +13,46 @@ require (
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f
github.com/fatih/camelcase v1.0.0
github.com/go-openapi/jsonreference v0.20.2
github.com/google/gnostic-models v0.6.9
github.com/google/gnostic-models v0.7.0
github.com/google/go-cmp v0.7.0
github.com/jonboulle/clockwork v0.4.0
github.com/jonboulle/clockwork v0.5.0
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/lithammer/dedent v1.1.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/moby/term v0.5.0
github.com/onsi/ginkgo/v2 v2.21.0
github.com/onsi/gomega v1.35.1
github.com/pkg/errors v0.9.1
github.com/russross/blackfriday/v2 v2.1.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
go.yaml.in/yaml/v2 v2.4.2
golang.org/x/sys v0.31.0
gopkg.in/evanphx/json-patch.v4 v4.12.0
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/cli-runtime v0.0.0
k8s.io/client-go v0.0.0
k8s.io/component-base v0.0.0
k8s.io/component-helpers v0.0.0
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7
k8s.io/component-base v0.0.0-20250717172125-4e07767df717
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
k8s.io/metrics v0.0.0
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0
sigs.k8s.io/kustomize/kyaml v0.19.0
sigs.k8s.io/structured-merge-diff/v4 v4.6.0
sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/randfill v1.0.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/yaml v1.5.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
@ -70,15 +71,17 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
@ -90,16 +93,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
)
replace (
k8s.io/api => ../api
k8s.io/apimachinery => ../apimachinery
k8s.io/cli-runtime => ../cli-runtime
k8s.io/client-go => ../client-go
k8s.io/code-generator => ../code-generator
k8s.io/component-base => ../component-base
k8s.io/component-helpers => ../component-helpers
k8s.io/metrics => ../metrics
)
replace k8s.io/code-generator => k8s.io/code-generator v0.0.0-20250718051115-9eb96548a40e

106
go.sum
View File

@ -1,20 +1,14 @@
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -23,21 +17,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
@ -50,12 +41,10 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -69,12 +58,10 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -88,7 +75,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
@ -104,8 +90,9 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@ -124,20 +111,16 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0-rc.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -157,27 +140,17 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -197,7 +170,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -216,9 +188,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -231,26 +200,37 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3 h1:UnuyCQyBmdFlYypApF2w6Ld0R0kAt8b+0Lt9dYAr23I=
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3/go.mod h1:K8dwhtttsRR0RHeSRF8XQ77gfMgyAj3q78/TkxEXhoc=
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139 h1:jWBClrBPuk+GEA9pJzMa9IvxncSBbw7fmvey15nVm0w=
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139/go.mod h1:v1p1Jsze3IHLy5gU17yVqR2qLO7jgYeX6mw3HZy2AEU=
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f h1:E/GB1lzzKbz3HPJ6Zu1bJYrey6oDAIAA+RMEozCpPpU=
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f/go.mod h1:SybB6wdHGt8FXxaHyNQqsUAhWcZKIDPurWPB5mfFLD0=
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7 h1:LNOJkn+3JlAEzdZzYheQM97gq6kKQfkrBN0GikI5nbc=
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7/go.mod h1:a14VvgYhux7oUSE9mWdzBuFKDZSGtperboMjQ1JtVgc=
k8s.io/component-base v0.0.0-20250717172125-4e07767df717 h1:07oqkM0FzuGUw/bJw2rJubzccG7ShpGcTJ7SBDGp5Fc=
k8s.io/component-base v0.0.0-20250717172125-4e07767df717/go.mod h1:/ehREU84M2OxVgU8WfxuUIi4/c5XsT6rIsEGQfhgxEQ=
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100 h1:XEHmjwZgMNRuVgpqaRH/RR+n4BU0evfitU0RpWGPMUM=
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100/go.mod h1:yxuY+YMknW7H9Bj7B29INyMOacJBa6oEG7gi7IKUzEQ=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1 h1:V4I6U/hfhZYYmDotL7ukG0nua1luMQSox5QtveZaSv0=
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1/go.mod h1:TkHVkU+vMKy7qppbMybraSCK8Y+LLpoqk/6Jl+M8EoU=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/cmd/config v0.19.0/go.mod h1:29Vvdl26PidPLUDi7nfjYa/I0wHBkwCZp15Nlcc4y98=
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA=
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@ -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"

View File

@ -2605,7 +2605,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set
@ -2639,7 +2638,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set
@ -2674,7 +2672,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set
@ -2709,7 +2706,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set
@ -2872,7 +2868,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: test
applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1
name: my-set
@ -3090,7 +3085,6 @@ metadata:
applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: replicationcontrollers
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set

View File

@ -23,8 +23,6 @@ import (
"io"
"time"
"github.com/pkg/errors"
"github.com/jonboulle/clockwork"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
@ -61,7 +59,7 @@ const (
// patchRetryBackOffPeriod is the period to back off when apply patch results in error.
var patchRetryBackOffPeriod = 1 * time.Second
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor: %w"
// Patcher defines options to patch OpenAPI objects.
type Patcher struct {
@ -120,13 +118,13 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
// Serialize the current configuration of the object from the server.
current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, nil, errors.Wrapf(err, "serializing current configuration from:\n%v\nfor:", obj)
return nil, nil, fmt.Errorf("serializing current configuration from:\n%v\nfor: %w", obj, err)
}
// Retrieve the original configuration of the object from the annotation.
original, err := util.GetOriginalConfiguration(obj)
if err != nil {
return nil, nil, errors.Wrapf(err, "retrieving original configuration from:\n%v\nfor:", obj)
return nil, nil, fmt.Errorf("retrieving original configuration from:\n%v\nfor: %w", obj, err)
}
var patchType types.PatchType
@ -178,17 +176,17 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
patchType = types.StrategicMergePatchType
patch, err = p.buildStrategicMergeFromBuiltins(versionedObj, original, modified, current)
if err != nil {
return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err)
}
} else {
if !runtime.IsNotRegisteredError(err) {
return nil, nil, errors.Wrapf(err, "getting instance of versioned object for %v:", p.Mapping.GroupVersionKind)
return nil, nil, fmt.Errorf("getting instance of versioned object for %v: %w", p.Mapping.GroupVersionKind, err)
}
patchType = types.MergePatchType
patch, err = p.buildMergePatch(original, modified, current)
if err != nil {
return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err)
}
}
}
@ -200,7 +198,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
if p.ResourceVersion != nil {
patch, err = addResourceVersion(patch, *p.ResourceVersion)
if err != nil {
return nil, nil, errors.Wrap(err, "Failed to insert resourceVersion in patch")
return nil, nil, fmt.Errorf("failed to insert resourceVersion in patch: %w", err)
}
}

View File

@ -307,6 +307,7 @@ func (o *AttachOptions) Run() error {
}
if !o.Quiet {
_, _ = fmt.Fprintln(o.ErrOut, "All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.")
fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.")
}
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {

View File

@ -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
}
}
}
})
}
}

View File

@ -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",

View File

@ -19,14 +19,16 @@ package autoscale
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"k8s.io/klog/v2"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
apiresource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -36,6 +38,7 @@ import (
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
autoscalingv2client "k8s.io/client-go/kubernetes/typed/autoscaling/v2"
"k8s.io/client-go/scale"
"k8s.io/klog/v2"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
@ -54,10 +57,16 @@ var (
autoscaleExample = templates.Examples(i18n.T(`
# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used
kubectl autoscale deployment foo --min=2 --max=10
kubectl autoscale deployment foo --min=2 --max=10
# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%
kubectl autoscale rc foo --max=5 --cpu-percent=80`))
kubectl autoscale rc foo --max=5 --cpu=80%
# Auto scale a deployment "bar", with the number of pods between 3 and 6, target average CPU of 500m and memory of 200Mi
kubectl autoscale deployment bar --min=3 --max=6 --cpu=500m --memory=200Mi
# Auto scale a deployment "bar", with the number of pods between 2 and 8, target CPU utilization 60% and memory utilization 70%
kubectl autoscale deployment bar --min=3 --max=6 --cpu=60% --memory=70%`))
)
// AutoscaleOptions declares the arguments accepted by the Autoscale command
@ -74,6 +83,8 @@ type AutoscaleOptions struct {
Min int32
Max int32
CPUPercent int32
CPU string
Memory string
createAnnotation bool
args []string
@ -109,7 +120,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
cmd := &cobra.Command{
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu=CPU] [--memory=MEMORY]",
DisableFlagsInUseLine: true,
Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
Long: autoscaleLong,
@ -129,7 +140,11 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
cmd.MarkFlagRequired("max")
cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")
cmd.Flags().StringVar(&o.CPU, "cpu", "", `Target CPU utilization over all the pods. When specified as a percentage (e.g."70%" for 70% of requested CPU) it will target average utilization. When specified as quantity (e.g."500m" for 500 milliCPU) it will target average value. Value without units is treated as a quantity with miliCPU being the unit (e.g."500" is "500m").`)
cmd.Flags().StringVar(&o.Memory, "memory", "", `Target memory utilization over all the pods. When specified as a percentage (e.g."60%" for 60% of requested memory) it will target average utilization. When specified as quantity (e.g."200Mi" for 200 MiB, "1Gi" for 1 GiB) it will target average value. Value without units is treated as a quantity with mebibytes being the unit (e.g."200" is "200Mi").`)
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
_ = cmd.Flags().MarkDeprecated("cpu-percent",
"Use --cpu with percentage or resource quantity format (e.g., '70%' for utilization or '500m' for milliCPU).")
cmdutil.AddDryRunFlag(cmd)
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
cmdutil.AddApplyAnnotationFlags(cmd)
@ -189,7 +204,22 @@ func (o *AutoscaleOptions) Validate() error {
if o.Max < o.Min {
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
}
// only one of the CPUPercent or CPU param is allowed
if o.CPUPercent > 0 && o.CPU != "" {
return fmt.Errorf("--cpu-percent and --cpu are mutually exclusive")
}
// validate CPU target if specified
if o.CPU != "" {
if _, _, _, err := parseResourceInput(o.CPU, corev1.ResourceCPU); err != nil {
return err
}
}
// validate Memory target if specified
if o.Memory != "" {
if _, _, _, err := parseResourceInput(o.Memory, corev1.ResourceMemory); err != nil {
return err
}
}
return nil
}
@ -214,16 +244,24 @@ func (o *AutoscaleOptions) Run() error {
mapping := info.ResourceMapping()
gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
if _, err = o.scaleKindResolver.ScaleForResource(gvr); err != nil {
return fmt.Errorf("cannot autoscale a %s: %w", mapping.GroupVersionKind.Kind, err)
}
// handles the creation of HorizontalPodAutoscaler objects for both v2 and v1 APIs.
// If v2 API fails, try to create and handle HorizontalPodAutoscaler using v1 API
hpaV2 := o.createHorizontalPodAutoscalerV2(info.Name, mapping)
if err := o.handleHPA(hpaV2); err != nil {
klog.V(1).Infof("Encountered an error with the v2 HorizontalPodAutoscaler: %v. "+
"Falling back to try the v1 HorizontalPodAutoscaler", err)
// handles the creation of HorizontalPodAutoscaler objects for both autoscaling/v2 and autoscaling/v1 APIs.
// If autoscaling/v2 API fails, try to create and handle HorizontalPodAutoscaler using autoscaling/v1 API
var hpaV2 runtime.Object
hpaV2, err = o.createHorizontalPodAutoscalerV2(info.Name, mapping)
if err != nil {
return fmt.Errorf("failed to create HorizontalPodAutoscaler using autoscaling/v2 API: %w", err)
}
if err = o.handleHPA(hpaV2); err != nil {
klog.V(1).Infof("Encountered an error with the autoscaling/v2 HorizontalPodAutoscaler: %v. "+
"Falling back to try the autoscaling/v1 HorizontalPodAutoscaler", err)
// check if the HPA can be created using v1 API.
if ok, err := o.canCreateHPAV1(); !ok {
return fmt.Errorf("failed to create autoscaling/v2 HPA and the configuration is incompatible with autoscaling/v1: %w", err)
}
hpaV1 := o.createHorizontalPodAutoscalerV1(info.Name, mapping)
if err := o.handleHPA(hpaV1); err != nil {
return err
@ -241,6 +279,18 @@ func (o *AutoscaleOptions) Run() error {
return nil
}
func (o *AutoscaleOptions) canCreateHPAV1() (bool, error) {
// Allow fallback to v1 HPA only if:
// 1. CPUPercent is set and Memory is not set.
// 2. Or, Memory is not set and the metric type is UtilizationMetricType.
_, _, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
if err != nil {
return false, err
}
return (o.CPUPercent >= 0 && o.Memory == "") ||
(o.Memory == "" && metricsType == autoscalingv2.UtilizationMetricType), nil
}
// handleHPA handles the creation and management of a single HPA object.
func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
if err := o.Recorder.Record(hpa); err != nil {
@ -288,7 +338,7 @@ func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
return printer.PrintObj(actualHPA, o.Out)
}
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) *autoscalingv2.HorizontalPodAutoscaler {
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) (*autoscalingv2.HorizontalPodAutoscaler, error) {
name := o.Name
if len(name) == 0 {
name = refName
@ -312,22 +362,83 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mappi
scaler.Spec.MinReplicas = &o.Min
}
if o.CPUPercent >= 0 {
scaler.Spec.Metrics = []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: &o.CPUPercent,
},
},
metrics := []autoscalingv2.MetricSpec{}
// add CPU metric if any of the CPU targets are specified
if o.CPUPercent > 0 {
cpuMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{},
},
}
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
cpuMetric.Resource.Target.AverageUtilization = &o.CPUPercent
metrics = append(metrics, cpuMetric)
}
return &scaler
// add Cpu metric if any of the cpu targets are specified
if o.CPU != "" {
cpuMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{},
},
}
quantity, value, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
if err != nil {
return nil, err
}
switch metricsType {
case autoscalingv2.UtilizationMetricType:
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
cpuMetric.Resource.Target.AverageUtilization = &value
case autoscalingv2.AverageValueMetricType:
cpuMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
cpuMetric.Resource.Target.AverageValue = &quantity
default:
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
}
metrics = append(metrics, cpuMetric)
}
// add Memory metric if any of the memory targets are specified
if o.Memory != "" {
memoryMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{},
},
}
quantity, value, metricsType, err := parseResourceInput(o.Memory, corev1.ResourceMemory)
if err != nil {
return nil, err
}
switch metricsType {
case autoscalingv2.UtilizationMetricType:
memoryMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
memoryMetric.Resource.Target.AverageUtilization = &value
case autoscalingv2.AverageValueMetricType:
memoryMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
memoryMetric.Resource.Target.AverageValue = &quantity
default:
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
}
metrics = append(metrics, memoryMetric)
}
// Only set Metrics if there are any defined
if len(metrics) > 0 {
scaler.Spec.Metrics = metrics
} else {
scaler.Spec.Metrics = nil
}
return &scaler, nil
}
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
@ -361,3 +472,72 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mappi
return &scaler
}
// parseResourceInput parses a resource input string into either a utilization percentage or a quantity value.
// It supports:
// - Percentage values (e.g., "70%") for UtilizationMetricType
// - Quantity values with units (e.g., "500m", "2Gi")
// - Bare numbers without units, which are interpreted as:
// - CPU: milliCPU ("500" → "500m")
// - Memory: Mebibytes ("512" → "512Mi")
func parseResourceInput(input string, resourceType corev1.ResourceName) (apiresource.Quantity, int32, autoscalingv2.MetricTargetType, error) {
input = strings.TrimSpace(input)
if input == "" {
return apiresource.Quantity{}, 0, "", fmt.Errorf("empty input")
}
// Case 1: Handle percentage-based metrics like "70%"
percentValue, isPercent, err := parsePercentage(input)
if isPercent {
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
return apiresource.Quantity{}, percentValue, autoscalingv2.UtilizationMetricType, nil
}
// Case 2: Try to interpret input as a bare number (e.g., "500"), and apply default float
valueFloat, err := strconv.ParseFloat(input, 64)
if err == nil {
unit, err := getDefaultUnitForResource(resourceType)
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
inputWithUnit := fmt.Sprintf("%g%s", valueFloat, unit)
quantity, err := apiresource.ParseQuantity(inputWithUnit)
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
}
// Case 3: Parse normally if input has a valid unit (e.g., "500m", "2Gi")
quantity, err := apiresource.ParseQuantity(input)
if err != nil {
return apiresource.Quantity{}, 0, "", fmt.Errorf("invalid resource %s value: %s", resourceType, input)
}
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
}
func getDefaultUnitForResource(resourceType corev1.ResourceName) (string, error) {
switch resourceType {
case corev1.ResourceCPU:
return "m", nil
case corev1.ResourceMemory:
return "Mi", nil
default:
return "", fmt.Errorf("unsupported resource type: %v", resourceType)
}
}
func parsePercentage(input string) (int32, bool, error) {
if !strings.HasSuffix(input, "%") {
return 0, false, nil
}
trimmed := strings.TrimSuffix(input, "%")
valueInt64, err := strconv.ParseInt(trimmed, 10, 32)
if err != nil || valueInt64 < 0 {
return 0, true, fmt.Errorf("invalid percentage value: %s", trimmed)
}
return int32(valueInt64), true, nil
}

View File

@ -26,6 +26,7 @@ import (
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
apiresource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
@ -79,11 +80,113 @@ func TestAutoscaleValidate(t *testing.T) {
},
expectedError: nil,
},
{
name: "CPUPercent appears with CPU",
options: &AutoscaleOptions{
Max: 5,
Min: 0,
CPU: "800",
CPUPercent: 20,
},
expectedError: fmt.Errorf("--cpu-percent and --cpu are mutually exclusive"),
},
{
name: "CPUPercent default (-1) with CPU",
options: &AutoscaleOptions{
Max: 5,
Min: 0,
CPU: "800",
CPUPercent: -1,
},
expectedError: nil,
},
{
name: "valid CPU percentage",
options: &AutoscaleOptions{
Max: 5,
CPU: "70%",
},
expectedError: nil,
},
{
name: "valid CPU numeric without unit",
options: &AutoscaleOptions{
Max: 5,
CPU: "500",
},
expectedError: nil,
},
{
name: "valid CPU with unit",
options: &AutoscaleOptions{
Max: 5,
CPU: "500m",
},
expectedError: nil,
},
{
name: "invalid CPU value (non-numeric)",
options: &AutoscaleOptions{
Max: 5,
CPU: "abc",
},
expectedError: fmt.Errorf("invalid resource cpu value: abc"),
},
{
name: "invalid CPU value (malformed unit)",
options: &AutoscaleOptions{
Max: 5,
CPU: "500xyz",
},
expectedError: fmt.Errorf("invalid resource cpu value: 500xyz"),
},
{
name: "valid memory percentage",
options: &AutoscaleOptions{
Max: 5,
Memory: "60%",
},
expectedError: nil,
},
{
name: "valid memory numeric without unit",
options: &AutoscaleOptions{
Max: 5,
Memory: "512",
},
expectedError: nil,
},
{
name: "valid memory with unit",
options: &AutoscaleOptions{
Max: 5,
Memory: "512Mi",
},
expectedError: nil,
},
{
name: "invalid memory value (non-numeric)",
options: &AutoscaleOptions{
Max: 5,
Memory: "xyz",
},
expectedError: fmt.Errorf("invalid resource memory value: xyz"),
},
{
name: "invalid memory value (MiB unit)",
options: &AutoscaleOptions{
Max: 5,
Memory: "512MiB",
},
expectedError: fmt.Errorf("invalid resource memory value: 512MiB"),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
errorGot := tc.options.Validate()
assert.Equal(t, tc.expectedError, errorGot)
if errorGot != nil {
assert.Equal(t, tc.expectedError.Error(), errorGot.Error())
}
})
}
}
@ -98,6 +201,10 @@ type createHorizontalPodAutoscalerTestCase struct {
}
func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
cpu500m := apiresource.MustParse("500m")
mem512Mi := apiresource.MustParse("512Mi")
cpu2000m := apiresource.MustParse("2000m")
mem3Gi := apiresource.MustParse("3Gi")
tests := []createHorizontalPodAutoscalerTestCase{
{
name: "create with all options",
@ -360,10 +467,396 @@ func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
},
},
},
{
name: "create with memory(use %) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
Memory: "50%",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptr.To(int32(50)),
},
},
},
},
},
},
},
{
name: "create with both cpu(use %) and memory(use %) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
CPU: "70%",
Memory: "50%",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptr.To(int32(70)),
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptr.To(int32(50)),
},
},
},
},
},
},
},
{
name: "create with both cpu(use m unit) and memory(use %) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
CPU: "500m",
Memory: "50%",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &cpu500m,
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptr.To(int32(50)),
},
},
},
},
},
},
},
{
name: "create with both cpu(no use unit) and memory(use %) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
CPU: "500",
Memory: "50%",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &cpu500m,
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.UtilizationMetricType,
AverageUtilization: ptr.To(int32(50)),
},
},
},
},
},
},
},
{
name: "create with memory(no use unit) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
Memory: "512",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &mem512Mi,
},
},
},
},
},
},
},
{
name: "create with cpu(no use unit) and memory(no use unit) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
CPU: "500",
Memory: "512",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &cpu500m,
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &mem512Mi,
},
},
},
},
},
},
},
{
name: "create with both cpu(use m unit) and memory(use Gi unit) options",
options: &AutoscaleOptions{
Name: "custom-name",
Max: 10,
Min: 2,
CPU: "2000m",
Memory: "3Gi",
},
refName: "deployment-1",
mapping: &meta.RESTMapping{
GroupVersionKind: schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
},
},
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-name",
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "deployment-1",
},
MinReplicas: ptr.To(int32(2)),
MaxReplicas: int32(10),
Metrics: []autoscalingv2.MetricSpec{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &cpu2000m,
},
},
},
{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
Type: autoscalingv2.AverageValueMetricType,
AverageValue: &mem3Gi,
},
},
},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
hpa := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
hpa, _ := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
assert.Equal(t, tc.expectedHPAV2, hpa)
})
}

View File

@ -83,8 +83,6 @@ import (
"k8s.io/kubectl/pkg/cmd/kustomize"
)
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
type KubectlOptions struct {
PluginHandler PluginHandler
Arguments []string
@ -143,25 +141,23 @@ func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
}
}
} else if err == nil {
if !cmdutil.CmdPluginAsSubcommand.IsDisabled() {
// Command exists(e.g. kubectl create), but it is not certain that
// subcommand also exists (e.g. kubectl create networkpolicy)
// we also have to eliminate kubectl create -f
if IsSubcommandPluginAllowed(foundCmd.Name()) && len(foundArgs) >= 1 && !strings.HasPrefix(foundArgs[0], "-") {
subcommand := foundArgs[0]
builtinSubcmdExist := false
for _, subcmd := range foundCmd.Commands() {
if subcmd.Name() == subcommand {
builtinSubcmdExist = true
break
}
// Command exists(e.g. kubectl create), but it is not certain that
// subcommand also exists (e.g. kubectl create networkpolicy)
// we also have to eliminate kubectl create -f
if IsSubcommandPluginAllowed(foundCmd.Name()) && len(foundArgs) >= 1 && !strings.HasPrefix(foundArgs[0], "-") {
subcommand := foundArgs[0]
builtinSubcmdExist := false
for _, subcmd := range foundCmd.Commands() {
if subcmd.Name() == subcommand {
builtinSubcmdExist = true
break
}
}
if !builtinSubcmdExist {
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, len(cmdPathPieces)-len(foundArgs)+1); err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
os.Exit(1)
}
if !builtinSubcmdExist {
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, len(cmdPathPieces)-len(foundArgs)+1); err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
os.Exit(1)
}
}
}
@ -363,7 +359,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
pref := kuberc.NewPreferences()
if cmdutil.KubeRC.IsEnabled() {
if !cmdutil.KubeRC.IsDisabled() {
pref.AddFlags(flags)
}
@ -499,7 +495,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
// add the klog flags later.
cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
if cmdutil.KubeRC.IsEnabled() {
if !cmdutil.KubeRC.IsDisabled() {
_, err := pref.Apply(cmds, o.Arguments, o.IOStreams.ErrOut)
if err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "error occurred while applying preferences %v\n", err)
@ -524,12 +520,9 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
//
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) {
// If the feature gate env var is set to "false", then do no add kubectl command headers.
if value, exists := os.LookupEnv(kubectlCmdHeaders); exists {
if value == "false" || value == "0" {
klog.V(5).Infoln("kubectl command headers turned off")
return
}
if cmdutil.CmdHeaders.IsDisabled() {
klog.V(5).Infoln("kubectl command headers turned off")
return
}
klog.V(5).Infoln("kubectl command headers turned on")
crt := &genericclioptions.CommandHeaderRoundTripper{}
@ -583,3 +576,29 @@ func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory)
return utilcomp.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
}))
}
// GetLogVerbosity parses the provided command-line arguments to determine
// the verbosity level for logging. Returns string representing the verbosity
// level, or 0 if no verbosity flag is specified.
func GetLogVerbosity(args []string) string {
for i, arg := range args {
if arg == "--" {
// flags after "--" does not represent any flag of
// the command. We should short cut the iteration in here.
break
}
if arg == "--v" || arg == "-v" {
if i+1 < len(args) {
return args[i+1]
}
} else if strings.Contains(arg, "--v=") || strings.Contains(arg, "-v=") {
parg := strings.Split(arg, "=")
if len(parg) > 1 && parg[1] != "" {
return parg[1]
}
}
}
return "0"
}

View File

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

View File

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

View File

@ -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:

View File

@ -280,7 +280,7 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
Executor: &exec.DefaultRemoteExecutor{},
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
done := make(chan error)

View File

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

View File

@ -76,7 +76,7 @@ var (
* Node: Create a new pod that runs in the node's host namespaces and can access
the node's filesystem.
Note: When a non-root user is configured for the entire target Pod, some capabilities granted
Note: When a non-root user is configured for the entire target Pod, some capabilities granted
by debug profile may not work.
`))
@ -727,6 +727,9 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
p := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: pn,
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{

View File

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

View File

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

View File

@ -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())
}
}

View File

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

View File

@ -30,6 +30,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/spf13/cobra"
yaml "go.yaml.in/yaml/v2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericiooptions"
@ -39,7 +40,6 @@ import (
"k8s.io/kubectl/pkg/cmd/create"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
yaml "sigs.k8s.io/yaml/goyaml.v2"
)
type EditTestCase struct {

View File

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

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -89,7 +89,7 @@ func NewExplainFlags(streams genericiooptions.IOStreams) *ExplainFlags {
func (flags *ExplainFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&flags.Recursive, "recursive", flags.Recursive, "Print the fields of fields (Currently only 1 level deep)")
cmd.Flags().StringVar(&flags.APIVersion, "api-version", flags.APIVersion, "Get different explanations for particular API version (API group/version)")
cmd.Flags().StringVar(&flags.OutputFormat, "output", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
}
// ToOptions converts from CLI inputs to runtime input

View File

@ -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

View File

@ -182,7 +182,7 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStre
cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.")
cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.")
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If set to true, suppresses NotFound error for specific objects that do not exist. Using this flag with commands that query for collections of resources has no effect when no resources are found.")
cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
addServerPrintColumnFlags(cmd, o)
@ -623,6 +623,10 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
}
infos, err := r.Infos()
if err != nil {
// Ignore "NotFound" error when ignore-not-found is set to true
if apierrors.IsNotFound(err) && o.IgnoreNotFound {
return nil
}
return err
}
if multipleGVKsRequested(infos) {

View File

@ -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"

View File

@ -22,7 +22,6 @@ import (
"reflect"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
"k8s.io/klog/v2"
@ -260,7 +259,7 @@ func (o *PatchOptions) RunPatch() error {
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil)
if err != nil {
if apierrors.IsUnsupportedMediaType(err) {
return errors.Wrap(err, fmt.Sprintf("%s is not supported by %s", patchType, mapping.GroupVersionKind))
return fmt.Errorf("%s is not supported by %s: %w", patchType, mapping.GroupVersionKind, err)
}
return err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -425,14 +425,36 @@ func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
type FeatureGate string
const (
ApplySet FeatureGate = "KUBECTL_APPLYSET"
CmdPluginAsSubcommand FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
// owner: @soltysh
// kep: https://kep.k8s.io/859
//
// HTTP headers with command name and flags used.
CmdHeaders FeatureGate = "KUBECTL_COMMAND_HEADERS"
// owner: @ardaguclu
// kep: https://kep.k8s.io/3104
//
// Separate kubectl user preferences.
KubeRC FeatureGate = "KUBECTL_KUBERC"
// owner: @soltysh
// kep: https://kep.k8s.io/3515
//
// Improved kubectl apply --prune behavior.
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
// owner: @justinb
// kep: https://kep.k8s.io/3659
//
// Improved kubectl apply --prune behavior.
ApplySet FeatureGate = "KUBECTL_APPLYSET"
// owner: @seans
// kep: https://kep.k8s.io/4006
//
// Transition to WebSockets.
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
// DebugCustomProfile should be dropped in 1.34
DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
KubeRC FeatureGate = "KUBECTL_KUBERC"
)
// IsEnabled returns true iff environment variable is set to true.

View File

@ -18,10 +18,10 @@ package version
import (
"fmt"
"io"
"math"
"k8s.io/apimachinery/pkg/util/version"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"math"
)
// supportedMinorVersionSkew is the maximum supported difference between the client and server minor versions.
@ -29,26 +29,26 @@ import (
// and server versions 1.18, 1.19, and 1.20 would be within the supported version skew for client version 1.19.
const supportedMinorVersionSkew = 1
// printVersionSkewWarning prints a warning message if the difference between the client and version is greater than
// getVersionSkewWarning returns a warning message if the difference between the client and version is greater than
// the supported version skew.
func printVersionSkewWarning(w io.Writer, clientVersion, serverVersion apimachineryversion.Info) error {
func getVersionSkewWarning(clientVersion, serverVersion apimachineryversion.Info) (string, error) {
parsedClientVersion, err := version.ParseSemantic(clientVersion.GitVersion)
if err != nil {
return err
return "", fmt.Errorf("client version error: %w", err)
}
parsedServerVersion, err := version.ParseSemantic(serverVersion.GitVersion)
if err != nil {
return err
return "", fmt.Errorf("server version error: %w", err)
}
majorVersionDifference := math.Abs(float64(parsedClientVersion.Major()) - float64(parsedServerVersion.Major()))
minorVersionDifference := math.Abs(float64(parsedClientVersion.Minor()) - float64(parsedServerVersion.Minor()))
if majorVersionDifference > 0 || minorVersionDifference > supportedMinorVersionSkew {
fmt.Fprintf(w, "WARNING: version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d\n",
warningMessage := fmt.Sprintf("version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d",
parsedClientVersion.Major(), parsedClientVersion.Minor(), parsedServerVersion.Major(), parsedServerVersion.Minor(), supportedMinorVersionSkew)
return warningMessage, nil
}
return nil
return "", nil
}

View File

@ -17,14 +17,12 @@ limitations under the License.
package version
import (
"bytes"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"testing"
apimachineryversion "k8s.io/apimachinery/pkg/version"
)
func TestPrintVersionSkewWarning(t *testing.T) {
output := &bytes.Buffer{}
testCases := []struct {
name string
clientVersion apimachineryversion.Info
@ -82,14 +80,15 @@ func TestPrintVersionSkewWarning(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output.Reset()
warningMessage, err := getVersionSkewWarning(tc.clientVersion, tc.serverVersion)
if err != nil {
t.Errorf("error: %s", err)
}
printVersionSkewWarning(output, tc.clientVersion, tc.serverVersion)
if tc.isWarningExpected && output.Len() == 0 {
t.Error("warning was expected, but not written to the output")
} else if !tc.isWarningExpected && output.Len() > 0 {
t.Errorf("warning was not expected, but was written to the output: %s", output.String())
if tc.isWarningExpected && warningMessage == "" {
t.Error("warning was expected")
} else if !tc.isWarningExpected && warningMessage != "" {
t.Errorf("warning was not expected. but got %s", warningMessage)
}
})
}

View File

@ -53,8 +53,9 @@ var (
// Options is a struct to support version command
type Options struct {
ClientOnly bool
Output string
ClientOnly bool
Output string
WarningsAsErrors bool
args []string
@ -93,6 +94,9 @@ func NewCmdVersion(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cob
// Complete completes all the required options
func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.WarningsAsErrors = cmd.Flags().Lookup("warnings-as-errors").Value.String() == "true"
if o.ClientOnly {
return nil
}
@ -162,11 +166,17 @@ func (o *Options) Run() error {
}
if versionInfo.ServerVersion != nil {
if err := printVersionSkewWarning(o.ErrOut, *versionInfo.ClientVersion, *versionInfo.ServerVersion); err != nil {
warningMessage, err := getVersionSkewWarning(*versionInfo.ClientVersion, *versionInfo.ServerVersion)
if err != nil {
return err
}
if warningMessage != "" {
if o.WarningsAsErrors {
return errors.New(warningMessage)
}
fmt.Fprintf(o.ErrOut, "Warning: %s\n", warningMessage) //nolint:errcheck
}
}
return serverErr
}

View File

@ -20,8 +20,6 @@ import (
"strings"
"testing"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
@ -32,13 +30,18 @@ func TestNewCmdVersionClientVersion(t *testing.T) {
defer tf.Cleanup()
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
o := NewOptions(streams)
if err := o.Complete(tf, &cobra.Command{}, nil); err != nil {
cmd := NewCmdVersion(tf, streams)
cmd.Flags().Bool("warnings-as-errors", false, "")
if err := o.Complete(tf, cmd, nil); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Validate(); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Complete(tf, &cobra.Command{}, []string{"extraParameter0"}); err != nil {
if err := o.Complete(tf, cmd, []string{"extraParameter0"}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Validate(); !strings.Contains(err.Error(), "extra arguments") {

View File

@ -1,5 +1,5 @@
/*
Copyright 2024 The Kubernetes Authors.
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,19 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package install installs the experimental API group, making it available as
// an option to all of the API encoding/decoding machinery.
package install
package fuzzer
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"sigs.k8s.io/randfill"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubectl/pkg/config"
"k8s.io/kubectl/pkg/config/v1alpha1"
)
// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
utilruntime.Must(config.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
// Funcs returns the fuzzer functions for the kubectl apis.
func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
func(obj *config.Preference, c randfill.Continue) {
c.FillNoCustom(obj)
},
}
}

View File

@ -0,0 +1,48 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package scheme
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubectl/pkg/config"
"k8s.io/kubectl/pkg/config/v1alpha1"
"k8s.io/kubectl/pkg/config/v1beta1"
)
var (
// Scheme defines methods for serializing and deserializing API objects.
Scheme = runtime.NewScheme()
// StrictCodecs provides methods for retrieving codecs and serializers
// for specific versions and content types.
StrictCodecs = serializer.NewCodecFactory(Scheme, serializer.EnableStrict)
// LenientCodecs provides methods for retrieving codecs and serializers
// for specific versions and content types.
LenientCodecs = serializer.NewCodecFactory(Scheme, serializer.DisableStrict)
)
func init() {
AddToScheme(Scheme)
}
// AddToScheme registers the API group and adds types to a scheme
func AddToScheme(scheme *runtime.Scheme) {
utilruntime.Must(config.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package scheme
import (
"testing"
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
"k8s.io/kubectl/pkg/config/fuzzer"
)
func TestRoundTripTypes(t *testing.T) {
roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs)
}

View File

@ -24,26 +24,26 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type Preference struct {
metav1.TypeMeta
// overrides allows changing default flag values of commands.
// Defaults allow changing default option values of commands.
// This is especially useful, when user doesn't want to explicitly
// set flags each time.
// set options each time.
// +optional
Overrides []CommandOverride
Defaults []CommandDefaults
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
// Aliases allow defining command aliases for existing kubectl commands, with optional default option values.
// If the alias name collides with a built-in command, built-in command always takes precedence.
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
// Option overrides defined in the defaults section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_OPTIONS] [USER_EXPLICIT_ARGS] expands to
// kubectl [COMMAND] # built-in command alias points to
// [KUBERC_PREPEND_ARGS]
// [USER_FLAGS]
// [KUBERC_FLAGS] # rest of the flags that are not passed by user in [USER_FLAGS]
// [USER_OPTIONS]
// [KUBERC_OPTIONS] # rest of the options that are not passed by user in [USER_OPTIONS]
// [USER_EXPLICIT_ARGS]
// [KUBERC_APPEND_ARGS]
// e.g.
// - name: runx
// command: run
// flags:
// options:
// - name: image
// default: nginx
// appendArgs:
@ -53,7 +53,7 @@ type Preference struct {
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
// - name: getn
// command: get
// flags:
// options:
// - name: output
// default: wide
// prependArgs:
@ -78,26 +78,28 @@ type AliasOverride struct {
// AppendArgs stores the arguments such as resource names, etc.
// These arguments are appended to the USER_ARGS.
AppendArgs []string
// Flag is allocated to store the flag definitions of alias
Flags []CommandOverrideFlag
// Options is allocated to store the option definitions of alias.
// Options only modify the default value of the option and if
// user explicitly passes a value, explicit one is used.
Options []CommandOptionDefault
}
// CommandOverride stores the commands and their associated flag's
// CommandDefaults stores the commands and their associated option's
// default values.
type CommandOverride struct {
type CommandDefaults struct {
// Command refers to a command whose flag's default value is changed.
Command string
// Flags is a list of flags storing different default values.
Flags []CommandOverrideFlag
// Options is a list of options storing different default values.
Options []CommandOptionDefault
}
// CommandOverrideFlag stores the name and the specified default
// value of the flag.
type CommandOverrideFlag struct {
// Flag name (long form, without dashes).
Name string `json:"name"`
// CommandOptionDefault stores the name and the specified default
// value of an option.
type CommandOptionDefault struct {
// Option name (long form, without dashes).
Name string
// In a string format of a default value. It will be parsed
// by kubectl to the compatible value of the flag.
Default string `json:"default"`
// by kubectl to the compatible value of the option.
Default string
}

View File

@ -28,9 +28,9 @@ type Preference struct {
// This is especially useful, when user doesn't want to explicitly
// set flags each time.
// +listType=atomic
Overrides []CommandOverride `json:"overrides"`
Defaults []CommandDefaults `json:"overrides"`
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
// aliases allow defining command aliases for existing kubectl commands, with optional default flag values.
// If the alias name collides with a built-in command, built-in command always takes precedence.
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
@ -66,40 +66,40 @@ type Preference struct {
// AliasOverride stores the alias definitions.
type AliasOverride struct {
// Name is the name of alias that can only include alphabetical characters
// name is the name of alias that can only include alphabetical characters
// If the alias name conflicts with the built-in command,
// built-in command will be used.
Name string `json:"name"`
// Command is the single or set of commands to execute, such as "set env" or "create"
// command is the single or set of commands to execute, such as "set env" or "create"
Command string `json:"command"`
// PrependArgs stores the arguments such as resource names, etc.
// prependArgs stores the arguments such as resource names, etc.
// These arguments are inserted after the alias name.
// +listType=atomic
PrependArgs []string `json:"prependArgs,omitempty"`
// AppendArgs stores the arguments such as resource names, etc.
// appendArgs stores the arguments such as resource names, etc.
// These arguments are appended to the USER_ARGS.
// +listType=atomic
AppendArgs []string `json:"appendArgs,omitempty"`
// Flag is allocated to store the flag definitions of alias.
// Flag only modifies the default value of the flag and if
// flags is allocated to store the flag definitions of alias.
// flags only modifies the default value of the flag and if
// user explicitly passes a value, explicit one is used.
// +listType=atomic
Flags []CommandOverrideFlag `json:"flags,omitempty"`
Options []CommandOptionDefault `json:"flags,omitempty"`
}
// CommandOverride stores the commands and their associated flag's
// CommandDefaults stores the commands and their associated option's
// default values.
type CommandOverride struct {
// Command refers to a command whose flag's default value is changed.
type CommandDefaults struct {
// command refers to a command whose flag's default value is changed.
Command string `json:"command"`
// Flags is a list of flags storing different default values.
// flags is a list of flags storing different default values.
// +listType=atomic
Flags []CommandOverrideFlag `json:"flags"`
Options []CommandOptionDefault `json:"flags"`
}
// CommandOverrideFlag stores the name and the specified default
// value of the flag.
type CommandOverrideFlag struct {
// CommandOptionDefault stores the name and the specified default
// value of an option.
type CommandOptionDefault struct {
// Flag name (long form, without dashes).
Name string `json:"name"`

View File

@ -46,23 +46,23 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandOverride)(nil), (*config.CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandOverride_To_config_CommandOverride(a.(*CommandOverride), b.(*config.CommandOverride), scope)
if err := s.AddGeneratedConversionFunc((*CommandDefaults)(nil), (*config.CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults(a.(*CommandDefaults), b.(*config.CommandDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandOverride)(nil), (*CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOverride_To_v1alpha1_CommandOverride(a.(*config.CommandOverride), b.(*CommandOverride), scope)
if err := s.AddGeneratedConversionFunc((*config.CommandDefaults)(nil), (*CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults(a.(*config.CommandDefaults), b.(*CommandDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandOverrideFlag)(nil), (*config.CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(a.(*CommandOverrideFlag), b.(*config.CommandOverrideFlag), scope)
if err := s.AddGeneratedConversionFunc((*CommandOptionDefault)(nil), (*config.CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(a.(*CommandOptionDefault), b.(*config.CommandOptionDefault), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandOverrideFlag)(nil), (*CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(a.(*config.CommandOverrideFlag), b.(*CommandOverrideFlag), scope)
if err := s.AddGeneratedConversionFunc((*config.CommandOptionDefault)(nil), (*CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(a.(*config.CommandOptionDefault), b.(*CommandOptionDefault), scope)
}); err != nil {
return err
}
@ -84,7 +84,7 @@ func autoConvert_v1alpha1_AliasOverride_To_config_AliasOverride(in *AliasOverrid
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
@ -98,7 +98,7 @@ func autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.Alias
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
@ -107,52 +107,52 @@ func Convert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.AliasOver
return autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in, out, s)
}
func autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
func autoConvert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
out.Command = in.Command
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_v1alpha1_CommandOverride_To_config_CommandOverride is an autogenerated conversion function.
func Convert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in, out, s)
// Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults is an autogenerated conversion function.
func Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in, out, s)
}
func autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
func autoConvert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
out.Command = in.Command
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_config_CommandOverride_To_v1alpha1_CommandOverride is an autogenerated conversion function.
func Convert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
return autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in, out, s)
// Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults is an autogenerated conversion function.
func Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
return autoConvert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in, out, s)
}
func autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
func autoConvert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag is an autogenerated conversion function.
func Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in, out, s)
// Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault is an autogenerated conversion function.
func Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in, out, s)
}
func autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
func autoConvert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag is an autogenerated conversion function.
func Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
return autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in, out, s)
// Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault is an autogenerated conversion function.
func Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
return autoConvert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in, out, s)
}
func autoConvert_v1alpha1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
out.Overrides = *(*[]config.CommandOverride)(unsafe.Pointer(&in.Overrides))
out.Defaults = *(*[]config.CommandDefaults)(unsafe.Pointer(&in.Defaults))
out.Aliases = *(*[]config.AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}
@ -163,7 +163,7 @@ func Convert_v1alpha1_Preference_To_config_Preference(in *Preference, out *confi
}
func autoConvert_config_Preference_To_v1alpha1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
out.Overrides = *(*[]CommandOverride)(unsafe.Pointer(&in.Overrides))
out.Defaults = *(*[]CommandDefaults)(unsafe.Pointer(&in.Defaults))
out.Aliases = *(*[]AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}

View File

@ -38,9 +38,9 @@ func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
@ -57,38 +57,38 @@ func (in *AliasOverride) DeepCopy() *AliasOverride {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
*out = *in
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
func (in *CommandOverride) DeepCopy() *CommandOverride {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
if in == nil {
return nil
}
out := new(CommandOverride)
out := new(CommandDefaults)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
if in == nil {
return nil
}
out := new(CommandOverrideFlag)
out := new(CommandOptionDefault)
in.DeepCopyInto(out)
return out
}
@ -97,9 +97,9 @@ func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
func (in *Preference) DeepCopyInto(out *Preference) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Overrides != nil {
in, out := &in.Overrides, &out.Overrides
*out = make([]CommandOverride, len(*in))
if in.Defaults != nil {
in, out := &in.Defaults, &out.Defaults
*out = make([]CommandDefaults, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}

23
pkg/config/v1beta1/doc.go Normal file
View File

@ -0,0 +1,23 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +groupName=kubectl.config.k8s.io
// +k8s:conversion-gen=k8s.io/kubectl/pkg/config
// +k8s:defaulter-gen=TypeMeta
package v1beta1 // Package v1beta1 import "k8s.io/kubectl/pkg/config/v1beta1"

View File

@ -0,0 +1,50 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name used in this package
const GroupName = "kubectl.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
var (
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Preference{},
)
return nil
}

109
pkg/config/v1beta1/types.go Normal file
View File

@ -0,0 +1,109 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Preference stores elements of KubeRC configuration file
type Preference struct {
metav1.TypeMeta `json:",inline"`
// defaults allow changing default option values of commands.
// This is especially useful, when user doesn't want to explicitly
// set options each time.
// +listType=atomic
Defaults []CommandDefaults `json:"defaults"`
// aliases allow defining command aliases for existing kubectl commands, with optional default option values.
// If the alias name collides with a built-in command, built-in command always takes precedence.
// Option overrides defined in the defaults section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_OPTIONS] [USER_EXPLICIT_ARGS] expands to
// kubectl [COMMAND] # built-in command alias points to
// [KUBERC_PREPEND_ARGS]
// [USER_OPTIONS]
// [KUBERC_OPTIONS] # rest of the options that are not passed by user in [USER_OPTIONS]
// [USER_EXPLICIT_ARGS]
// [KUBERC_APPEND_ARGS]
// e.g.
// - name: runx
// command: run
// options:
// - name: image
// default: nginx
// appendArgs:
// - --
// - custom-arg1
// For example, if user invokes "kubectl runx test-pod" command,
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
// - name: getn
// command: get
// options:
// - name: output
// default: wide
// prependArgs:
// - node
// "kubectl getn control-plane-1" expands to "kubectl get node control-plane-1 --output=wide"
// "kubectl getn control-plane-1 --output=json" expands to "kubectl get node --output=json control-plane-1"
// +listType=atomic
Aliases []AliasOverride `json:"aliases"`
}
// AliasOverride stores the alias definitions.
type AliasOverride struct {
// name is the name of alias that can only include alphabetical characters
// If the alias name conflicts with the built-in command,
// built-in command will be used.
Name string `json:"name"`
// command is the single or set of commands to execute, such as "set env" or "create"
Command string `json:"command"`
// prependArgs stores the arguments such as resource names, etc.
// These arguments are inserted after the alias name.
// +listType=atomic
PrependArgs []string `json:"prependArgs,omitempty"`
// appendArgs stores the arguments such as resource names, etc.
// These arguments are appended to the USER_ARGS.
// +listType=atomic
AppendArgs []string `json:"appendArgs,omitempty"`
// options is allocated to store the option definitions of alias.
// options only modify the default value of the option and if
// user explicitly passes a value, explicit one is used.
// +listType=atomic
Options []CommandOptionDefault `json:"options,omitempty"`
}
// CommandDefaults stores the commands and their associated option's
// default values.
type CommandDefaults struct {
// command refers to a command whose option's default value is changed.
Command string `json:"command"`
// options is a list of options storing different default values.
// +listType=atomic
Options []CommandOptionDefault `json:"options"`
}
// CommandOptionDefault stores the name and the specified default
// value of an option.
type CommandOptionDefault struct {
// Option name (long form, without dashes).
Name string `json:"name"`
// In a string format of a default value. It will be parsed
// by kubectl to the compatible value of the option.
Default string `json:"default"`
}

View File

@ -0,0 +1,174 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1beta1
import (
unsafe "unsafe"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
config "k8s.io/kubectl/pkg/config"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*AliasOverride)(nil), (*config.AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_AliasOverride_To_config_AliasOverride(a.(*AliasOverride), b.(*config.AliasOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.AliasOverride)(nil), (*AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_AliasOverride_To_v1beta1_AliasOverride(a.(*config.AliasOverride), b.(*AliasOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandDefaults)(nil), (*config.CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_CommandDefaults_To_config_CommandDefaults(a.(*CommandDefaults), b.(*config.CommandDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandDefaults)(nil), (*CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandDefaults_To_v1beta1_CommandDefaults(a.(*config.CommandDefaults), b.(*CommandDefaults), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandOptionDefault)(nil), (*config.CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(a.(*CommandOptionDefault), b.(*config.CommandOptionDefault), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandOptionDefault)(nil), (*CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(a.(*config.CommandOptionDefault), b.(*CommandOptionDefault), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Preference)(nil), (*config.Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_Preference_To_config_Preference(a.(*Preference), b.(*config.Preference), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.Preference)(nil), (*Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_Preference_To_v1beta1_Preference(a.(*config.Preference), b.(*Preference), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1beta1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
out.Name = in.Name
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_v1beta1_AliasOverride_To_config_AliasOverride is an autogenerated conversion function.
func Convert_v1beta1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
return autoConvert_v1beta1_AliasOverride_To_config_AliasOverride(in, out, s)
}
func autoConvert_config_AliasOverride_To_v1beta1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
out.Name = in.Name
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_config_AliasOverride_To_v1beta1_AliasOverride is an autogenerated conversion function.
func Convert_config_AliasOverride_To_v1beta1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
return autoConvert_config_AliasOverride_To_v1beta1_AliasOverride(in, out, s)
}
func autoConvert_v1beta1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
out.Command = in.Command
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_v1beta1_CommandDefaults_To_config_CommandDefaults is an autogenerated conversion function.
func Convert_v1beta1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
return autoConvert_v1beta1_CommandDefaults_To_config_CommandDefaults(in, out, s)
}
func autoConvert_config_CommandDefaults_To_v1beta1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
out.Command = in.Command
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
return nil
}
// Convert_config_CommandDefaults_To_v1beta1_CommandDefaults is an autogenerated conversion function.
func Convert_config_CommandDefaults_To_v1beta1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
return autoConvert_config_CommandDefaults_To_v1beta1_CommandDefaults(in, out, s)
}
func autoConvert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault is an autogenerated conversion function.
func Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
return autoConvert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in, out, s)
}
func autoConvert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault is an autogenerated conversion function.
func Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
return autoConvert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in, out, s)
}
func autoConvert_v1beta1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
out.Defaults = *(*[]config.CommandDefaults)(unsafe.Pointer(&in.Defaults))
out.Aliases = *(*[]config.AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}
// Convert_v1beta1_Preference_To_config_Preference is an autogenerated conversion function.
func Convert_v1beta1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
return autoConvert_v1beta1_Preference_To_config_Preference(in, out, s)
}
func autoConvert_config_Preference_To_v1beta1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
out.Defaults = *(*[]CommandDefaults)(unsafe.Pointer(&in.Defaults))
out.Aliases = *(*[]AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}
// Convert_config_Preference_To_v1beta1_Preference is an autogenerated conversion function.
func Convert_config_Preference_To_v1beta1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
return autoConvert_config_Preference_To_v1beta1_Preference(in, out, s)
}

View File

@ -0,0 +1,133 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
*out = *in
if in.PrependArgs != nil {
in, out := &in.PrependArgs, &out.PrependArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AppendArgs != nil {
in, out := &in.AppendArgs, &out.AppendArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasOverride.
func (in *AliasOverride) DeepCopy() *AliasOverride {
if in == nil {
return nil
}
out := new(AliasOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
*out = *in
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
if in == nil {
return nil
}
out := new(CommandDefaults)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
if in == nil {
return nil
}
out := new(CommandOptionDefault)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Preference) DeepCopyInto(out *Preference) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Defaults != nil {
in, out := &in.Defaults, &out.Defaults
*out = make([]CommandDefaults, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Aliases != nil {
in, out := &in.Aliases, &out.Aliases
*out = make([]AliasOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preference.
func (in *Preference) DeepCopy() *Preference {
if in == nil {
return nil
}
out := new(Preference)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Preference) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,33 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -38,9 +38,9 @@ func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
@ -57,38 +57,38 @@ func (in *AliasOverride) DeepCopy() *AliasOverride {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
*out = *in
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]CommandOptionDefault, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
func (in *CommandOverride) DeepCopy() *CommandOverride {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
if in == nil {
return nil
}
out := new(CommandOverride)
out := new(CommandDefaults)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
if in == nil {
return nil
}
out := new(CommandOverrideFlag)
out := new(CommandOptionDefault)
in.DeepCopyInto(out)
return out
}
@ -97,9 +97,9 @@ func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
func (in *Preference) DeepCopyInto(out *Preference) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Overrides != nil {
in, out := &in.Overrides, &out.Overrides
*out = make([]CommandOverride, len(*in))
if in.Defaults != nil {
in, out := &in.Defaults, &out.Defaults
*out = make([]CommandDefaults, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}

View File

@ -22,9 +22,11 @@ import (
"crypto/x509"
"fmt"
"io"
"maps"
"net"
"net/url"
"reflect"
"slices"
"sort"
"strconv"
"strings"
@ -1863,7 +1865,11 @@ func describeContainerBasicInfo(container corev1.Container, status corev1.Contai
func describeContainerPorts(cPorts []corev1.ContainerPort) string {
ports := make([]string, 0, len(cPorts))
for _, cPort := range cPorts {
ports = append(ports, fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol))
portStr := fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol)
if cPort.Name != "" {
portStr = fmt.Sprintf("%s (%s)", portStr, cPort.Name)
}
ports = append(ports, portStr)
}
return strings.Join(ports, ", ")
}
@ -1871,7 +1877,11 @@ func describeContainerPorts(cPorts []corev1.ContainerPort) string {
func describeContainerHostPorts(cPorts []corev1.ContainerPort) string {
ports := make([]string, 0, len(cPorts))
for _, cPort := range cPorts {
ports = append(ports, fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol))
portStr := fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol)
if cPort.Name != "" {
portStr = fmt.Sprintf("%s (%s)", portStr, cPort.Name)
}
ports = append(ports, portStr)
}
return strings.Join(ports, ", ")
}
@ -2580,12 +2590,12 @@ func describeSecret(secret *corev1.Secret) (string, error) {
w.Write(LEVEL_0, "\nType:\t%s\n", secret.Type)
w.Write(LEVEL_0, "\nData\n====\n")
for k, v := range secret.Data {
for _, k := range slices.Sorted(maps.Keys(secret.Data)) {
switch {
case k == corev1.ServiceAccountTokenKey && secret.Type == corev1.SecretTypeServiceAccountToken:
w.Write(LEVEL_0, "%s:\t%s\n", k, string(v))
w.Write(LEVEL_0, "%s:\t%s\n", k, string(secret.Data[k]))
default:
w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(v))
w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(secret.Data[k]))
}
}
@ -3155,6 +3165,9 @@ func describeService(service *corev1.Service, endpointSlices []discoveryv1.Endpo
if len(service.Spec.LoadBalancerSourceRanges) > 0 {
w.Write(LEVEL_0, "LoadBalancer Source Ranges:\t%v\n", strings.Join(service.Spec.LoadBalancerSourceRanges, ","))
}
if service.Spec.TrafficDistribution != nil {
w.Write(LEVEL_0, "Traffic Distribution:\t%s\n", *service.Spec.TrafficDistribution)
}
if events != nil {
DescribeEvents(events, w)
}
@ -4563,13 +4576,15 @@ func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings
printAnnotationsMultiline(w, "Annotations", configMap.Annotations)
w.Write(LEVEL_0, "\nData\n====\n")
for k, v := range configMap.Data {
for _, k := range slices.Sorted(maps.Keys(configMap.Data)) {
v := configMap.Data[k]
w.Write(LEVEL_0, "%s:\n----\n", k)
w.Write(LEVEL_0, "%s\n", string(v))
w.Write(LEVEL_0, "\n")
}
w.Write(LEVEL_0, "\nBinaryData\n====\n")
for k, v := range configMap.BinaryData {
for _, k := range slices.Sorted(maps.Keys(configMap.BinaryData)) {
v := configMap.BinaryData[k]
w.Write(LEVEL_0, "%s: %s bytes\n", k, strconv.Itoa(len(v)))
}
w.Write(LEVEL_0, "\n")

View File

@ -19,6 +19,7 @@ package describe
import (
"bytes"
"fmt"
"maps"
"reflect"
"strings"
"testing"
@ -350,27 +351,79 @@ func TestDescribeTopologySpreadConstraints(t *testing.T) {
}
func TestDescribeSecret(t *testing.T) {
fake := fake.NewSimpleClientset(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
testCases := []struct {
description string
data map[string][]byte // secret key -> secret in bytes
expected []string
}{
{
description: "alphabetical ordering",
data: map[string][]byte{
"username": []byte("YWRtaW4="),
"password": []byte("MWYyZDFlMmU2N2Rm"),
},
expected: []string{"password", "username"},
},
Data: map[string][]byte{
"username": []byte("YWRtaW4="),
"password": []byte("MWYyZDFlMmU2N2Rm"),
{
description: "uppercase takes precedence",
data: map[string][]byte{
"text": []byte("a3ViZXJuZXRlcwo="),
"Text": []byte("dGhpcyBpcyBhIHRlc3QK"),
"tExt": []byte("d2VpcmQgY2FzaW5nCg=="),
},
expected: []string{"Text", "tExt", "text"},
},
{
description: "numbers take precedence",
data: map[string][]byte{
"key_1": []byte("c29tZV9zZWNyZXQK"),
"1_key": []byte("c29tZV90ZXh0Cg=="),
},
expected: []string{"1_key", "key_1"},
},
})
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := SecretDescriber{c}
out, err := d.Describe("foo", "bar", DescriberSettings{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !strings.Contains(out, "bar") || !strings.Contains(out, "foo") || !strings.Contains(out, "username") || !strings.Contains(out, "8 bytes") || !strings.Contains(out, "password") || !strings.Contains(out, "16 bytes") {
t.Errorf("unexpected out: %s", out)
}
if strings.Contains(out, "YWRtaW4=") || strings.Contains(out, "MWYyZDFlMmU2N2Rm") {
t.Errorf("sensitive data should not be shown, unexpected out: %s", out)
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Data: testCase.data,
}
fake := fake.NewSimpleClientset(secret)
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
d := SecretDescriber{c}
out, err := d.Describe("foo", "bar", DescriberSettings{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
for value := range maps.Values(testCase.data) {
if strings.Contains(out, string(value)) {
t.Errorf("sensitive data should not be shown, unexpected out: %s", out)
}
}
expectedOut := `Name: bar
Namespace: foo
Labels: <none>
Annotations: <none>
Type:
Data
====`
for _, expectedKey := range testCase.expected {
expectedOut = fmt.Sprintf("%s\n%s: %d bytes", expectedOut, expectedKey, len(testCase.data[expectedKey]))
}
expectedOut = fmt.Sprintf("%s\n", expectedOut)
assert.Equal(t, expectedOut, out)
})
}
}
@ -660,6 +713,32 @@ func TestDescribeConfigMap(t *testing.T) {
if !strings.Contains(out, "binarykey1") || !strings.Contains(out, "5 bytes") || !strings.Contains(out, "binarykey2") || !strings.Contains(out, "6 bytes") {
t.Errorf("unexpected out: %s", out)
}
expectedOut := `Name: mycm
Namespace: foo
Labels: <none>
Annotations: <none>
Data
====
key1:
----
value1
key2:
----
value2
BinaryData
====
binarykey1: 5 bytes
binarykey2: 6 bytes
Events: <none>
`
assert.Equal(t, expectedOut, out)
}
func TestDescribeLimitRange(t *testing.T) {
@ -728,6 +807,7 @@ func getResourceList(cpu, memory string) corev1.ResourceList {
func TestDescribeService(t *testing.T) {
singleStack := corev1.IPFamilyPolicySingleStack
preferClose := corev1.ServiceTrafficDistributionPreferClose
testCases := []struct {
name string
service *corev1.Service
@ -1086,6 +1166,54 @@ func TestDescribeService(t *testing.T) {
Events: <none>
`)[1:],
},
{
name: "test-TrafficDistribution",
service: &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Ports: []corev1.ServicePort{{
Name: "port-tcp",
Port: 8080,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromString("targetPort"),
NodePort: 31111,
}},
Selector: map[string]string{"blah": "heh"},
ClusterIP: "1.2.3.4",
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
LoadBalancerIP: "5.6.7.8",
SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
TrafficDistribution: &preferClose,
HealthCheckNodePort: 32222,
},
},
expected: dedent.Dedent(`
Name: bar
Namespace: foo
Labels: <none>
Annotations: <none>
Selector: blah=heh
Type: LoadBalancer
IP Families: IPv4
IP: 1.2.3.4
IPs: <none>
Desired LoadBalancer IP: 5.6.7.8
Port: port-tcp 8080/TCP
TargetPort: targetPort/TCP
NodePort: port-tcp 31111/TCP
Endpoints: <none>
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 32222
Traffic Distribution: PreferClose
Events: <none>
`)[1:],
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@ -1244,6 +1372,74 @@ func TestDescribeResources(t *testing.T) {
}
}
func TestDescribeContainerPorts(t *testing.T) {
testCases := []struct {
name string
ports []corev1.ContainerPort
expectedContainer string
expectedHost string
}{
{
name: "no ports",
ports: []corev1.ContainerPort{},
expectedContainer: "",
expectedHost: "",
},
{
name: "container and host port, with name",
ports: []corev1.ContainerPort{
{Name: "web", ContainerPort: 8080, HostPort: 8080, Protocol: corev1.ProtocolTCP},
},
expectedContainer: "8080/TCP (web)",
expectedHost: "8080/TCP (web)",
},
{
name: "container and host port, no name",
ports: []corev1.ContainerPort{
{ContainerPort: 8080, HostPort: 8080, Protocol: corev1.ProtocolTCP},
},
expectedContainer: "8080/TCP",
expectedHost: "8080/TCP",
},
{
name: "multiple ports with mixed configuration",
ports: []corev1.ContainerPort{
{Name: "controller", ContainerPort: 9093, HostPort: 9093, Protocol: corev1.ProtocolTCP},
{ContainerPort: 9092, Protocol: corev1.ProtocolTCP},
{Name: "interbroker", ContainerPort: 9094, HostPort: 9094, Protocol: corev1.ProtocolTCP},
},
expectedContainer: "9093/TCP (controller), 9092/TCP, 9094/TCP (interbroker)",
expectedHost: "9093/TCP (controller), 0/TCP, 9094/TCP (interbroker)",
},
{
name: "all ports with mixed configuration",
ports: []corev1.ContainerPort{
{Name: "controller", ContainerPort: 9093, HostPort: 9093, Protocol: corev1.ProtocolTCP},
{Name: "client", ContainerPort: 9092, HostPort: 9092, Protocol: corev1.ProtocolTCP},
{Name: "interbroker", ContainerPort: 9094, HostPort: 9094, Protocol: corev1.ProtocolTCP},
},
expectedContainer: "9093/TCP (controller), 9092/TCP (client), 9094/TCP (interbroker)",
expectedHost: "9093/TCP (controller), 9092/TCP (client), 9094/TCP (interbroker)",
},
}
for _, tc := range testCases {
t.Run(tc.name+" - container ports", func(t *testing.T) {
result := describeContainerPorts(tc.ports)
if result != tc.expectedContainer {
t.Errorf("describeContainerPorts: expected %q, got %q", tc.expectedContainer, result)
}
})
t.Run(tc.name+" - host ports", func(t *testing.T) {
result := describeContainerHostPorts(tc.ports)
if result != tc.expectedHost {
t.Errorf("describeContainerHostPorts: expected %q, got %q", tc.expectedHost, result)
}
})
}
}
func TestDescribeContainers(t *testing.T) {
trueVal := true
testCases := []struct {

View File

@ -171,6 +171,12 @@ func hasLocalStorage(pod corev1.Pod) bool {
return false
}
func isControllerRefDaemonSet(workloadRef *metav1.OwnerReference) bool {
// find if workloadRef is daemonSet
daemonSetAPIVersion, daemonSetKind := appsv1.SchemeGroupVersion.WithKind("DaemonSet").ToAPIVersionAndKind()
return workloadRef.Kind == daemonSetKind && workloadRef.APIVersion == daemonSetAPIVersion
}
func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
// Note that we return false in cases where the pod is DaemonSet managed,
// regardless of flags.
@ -179,7 +185,7 @@ func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
// management resource - including DaemonSet - is not found).
// Such pods will be deleted if --force is used.
controllerRef := metav1.GetControllerOf(&pod)
if controllerRef == nil || controllerRef.Kind != appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
if controllerRef == nil || !isControllerRefDaemonSet(controllerRef) {
return MakePodDeleteStatusOkay()
}
// Any finished pod can be removed.

View File

@ -24,16 +24,13 @@ import (
"regexp"
"strings"
"k8s.io/kubectl/pkg/config"
kuberc "k8s.io/kubectl/pkg/config/install"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/kubectl/pkg/config"
)
const RecommendedKubeRCFileName = "kuberc"
@ -44,16 +41,8 @@ var (
aliasNameRegex = regexp.MustCompile("^[a-zA-Z]+$")
shortHandRegex = regexp.MustCompile("^-[a-zA-Z]+$")
scheme = runtime.NewScheme()
strictCodecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict)
lenientCodecs = serializer.NewCodecFactory(scheme, serializer.DisableStrict)
)
func init() {
kuberc.Install(scheme)
}
// PreferencesHandler is responsible for setting default flags
// arguments based on user's kuberc configuration.
type PreferencesHandler interface {
@ -80,13 +69,13 @@ func NewPreferences() PreferencesHandler {
type aliasing struct {
appendArgs []string
prependArgs []string
flags []config.CommandOverrideFlag
flags []config.CommandOptionDefault
command *cobra.Command
}
// AddFlags adds kuberc related flags into the command.
func (p *Preferences) AddFlags(flags *pflag.FlagSet) {
flags.String("kuberc", "", "Path to the kuberc file to use for preferences. This can be disabled by exporting KUBECTL_KUBERC=false.")
flags.String("kuberc", "", "Path to the kuberc file to use for preferences. This can be disabled by exporting KUBECTL_KUBERC=false feature gate or turning off the feature KUBERC=off.")
}
// Apply firstly applies the aliases in the preferences file and secondly overrides
@ -133,7 +122,7 @@ func (p *Preferences) applyOverrides(rootCmd *cobra.Command, kuberc *config.Pref
return nil
}
for _, c := range kuberc.Overrides {
for _, c := range kuberc.Defaults {
parsedCmds := strings.Fields(c.Command)
overrideCmd, _, err := rootCmd.Find(parsedCmds)
if err != nil {
@ -158,7 +147,7 @@ func (p *Preferences) applyOverrides(rootCmd *cobra.Command, kuberc *config.Pref
}
})
for _, fl := range c.Flags {
for _, fl := range c.Options {
existingFlag := cmd.Flag(fl.Name)
if existingFlag == nil {
return fmt.Errorf("invalid flag %s for command %s", fl.Name, c.Command)
@ -194,7 +183,7 @@ func (p *Preferences) applyAliases(rootCmd *cobra.Command, kuberc *config.Prefer
var commandName string // first "non-flag" arguments
var commandIndex int
for index, arg := range args[1:] {
if !strings.HasPrefix(arg, "-") {
if !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, cobra.ShellCompRequestCmd) {
commandName = arg
commandIndex = index + 1
break
@ -227,7 +216,7 @@ func (p *Preferences) applyAliases(rootCmd *cobra.Command, kuberc *config.Prefer
aliasArgs = &aliasing{
prependArgs: alias.PrependArgs,
appendArgs: alias.AppendArgs,
flags: alias.Flags,
flags: alias.Options,
command: aliasCmd,
}
break
@ -318,9 +307,9 @@ func DefaultGetPreferences(kuberc string, errOut io.Writer) (*config.Preference,
preference, err := decodePreference(kubeRCFile)
switch {
case explicitly && preference != nil && runtime.IsStrictDecodingError(err):
// if explicitly requested, just warn about strict decoding errors if we got a usable Preference object back
fmt.Fprintf(errOut, "kuberc: ignoring strict decoding error in %s: %v", kubeRCFile, err)
case preference != nil && runtime.IsStrictDecodingError(err):
// just warn about strict decoding errors if we got a usable Preference object back
fmt.Fprintf(errOut, "kuberc: ignoring strict decoding error in %s: %v", kubeRCFile, err) //nolint:errcheck
return preference, nil
case explicitly && err != nil:
@ -333,7 +322,7 @@ func DefaultGetPreferences(kuberc string, errOut io.Writer) (*config.Preference,
case !explicitly && err != nil:
// if not explicitly requested, only warn on any other error
fmt.Fprintf(errOut, "kuberc: no preferences loaded from %s: %v", kubeRCFile, err)
fmt.Fprintf(errOut, "kuberc: no preferences loaded from %s: %v", kubeRCFile, err) //nolint:errcheck
return nil, nil
default:
@ -424,7 +413,7 @@ func searchInArgs(flagName string, shorthand string, allShorthands map[string]st
}
func validate(plugin *config.Preference) error {
validateFlag := func(flags []config.CommandOverrideFlag) error {
validateFlag := func(flags []config.CommandOptionDefault) error {
for _, flag := range flags {
if strings.HasPrefix(flag.Name, "-") {
return fmt.Errorf("flag name %s should be in long form without dashes", flag.Name)
@ -438,7 +427,7 @@ func validate(plugin *config.Preference) error {
return fmt.Errorf("invalid alias name, can only include alphabetical characters")
}
if err := validateFlag(alias.Flags); err != nil {
if err := validateFlag(alias.Options); err != nil {
return err
}
@ -448,8 +437,8 @@ func validate(plugin *config.Preference) error {
aliases[alias.Name] = struct{}{}
}
for _, override := range plugin.Overrides {
if err := validateFlag(override.Flags); err != nil {
for _, override := range plugin.Defaults {
if err := validateFlag(override.Options); err != nil {
return err
}
}

File diff suppressed because it is too large Load Diff

View File

@ -24,12 +24,11 @@ import (
"io"
"os"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/runtime/schema"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/klog/v2"
"k8s.io/kubectl/pkg/config"
"k8s.io/kubectl/pkg/config/scheme"
)
// decodePreference iterates over the yamls in kuberc file to find the supported kuberc version.
@ -58,10 +57,10 @@ func decodePreference(kubercFile string) (*config.Preference, error) {
}
// remember we attempted
attemptedItems++
pref, gvk, strictDecodeErr := strictCodecs.UniversalDecoder().Decode(doc, nil, nil)
pref, gvk, strictDecodeErr := scheme.StrictCodecs.UniversalDecoder().Decode(doc, nil, nil)
if strictDecodeErr != nil {
var lenientDecodeErr error
pref, gvk, lenientDecodeErr = lenientCodecs.UniversalDecoder().Decode(doc, nil, nil)
pref, gvk, lenientDecodeErr = scheme.LenientCodecs.UniversalDecoder().Decode(doc, nil, nil)
if lenientDecodeErr != nil {
// both strict and lenient failed
// verbose log the error with the most information about this item and continue
@ -88,13 +87,14 @@ func decodePreference(kubercFile string) (*config.Preference, error) {
}
// we have a usable preferences to return
klog.V(5).Infof("kuberc: successfully decoded entry %d in %s", attemptedItems, kubercFile)
klog.V(5).Infof("kuberc: using entry %d (%s) in %s", attemptedItems, gvk.GroupVersion(), kubercFile)
return preferences, strictDecodeErr
}
if attemptedItems > 0 {
return nil, fmt.Errorf("no valid preferences found in %s, use --v=5 to see details", kubercFile)
}
// empty doc
klog.V(5).Infof("kuberc: no preferences found in %s", kubercFile)
return nil, nil

103
pkg/kuberc/marshal_test.go Normal file
View File

@ -0,0 +1,103 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kuberc
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestDecodePreference(t *testing.T) {
testCases := map[string]struct {
kuberc string
expectedAliases []string
expectedDefaults []string
expectedError string
}{
"v1alpha1": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "v1alpha1.kuberc"),
expectedDefaults: []string{"v1alpha1-apply", "v1alpha1-delete"},
},
"v1beta1": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "v1beta1.kuberc"),
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
},
"first known version (v1beta1) with all versions": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "allversions.kuberc"),
expectedAliases: []string{"getn", "runx"},
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
},
"first known (v1beta1) with multiple versions (unknown, v1beta1, v1alpha1)": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple1.kuberc"),
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
},
"first known (v1beta1) with multiple versions (unknown, v1beta1, v1beta1, v1alpha1)": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple2.kuberc"),
expectedDefaults: []string{"v1beta1-apply-first", "v1beta1-delete-first"},
},
"first known older (v1alpha1) with multiple versions (unknown, v1alpha1)": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple3.kuberc"),
expectedDefaults: []string{"v1alpha1-apply-first", "v1alpha1-delete-first"},
},
"first v1alpha1 with multiple versions (unknown, v1alpha1, v1beta1)": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple4.kuberc"),
expectedDefaults: []string{"v1alpha1-apply-first", "v1alpha1-delete-first"},
},
"single unknown version": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "unknown.kuberc"),
expectedError: "no valid preferences found",
},
"multiple unknown version": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "unknown.kuberc"),
expectedError: "no valid preferences found",
},
"non-existent file": {
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "non-existent"),
expectedError: "no such file or directory",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual, err := decodePreference(tc.kuberc)
if len(tc.expectedError) != 0 {
require.ErrorContains(t, err, tc.expectedError, "wrong expected error")
return
}
require.NoError(t, err, "unexpected error")
require.NotNil(t, actual, "missing preferences when decoding")
defaults := []string{}
for _, o := range actual.Defaults {
defaults = append(defaults, o.Command)
}
require.ElementsMatch(t, defaults, tc.expectedDefaults, "defaults mismatch")
aliases := []string{}
for _, o := range actual.Aliases {
aliases = append(aliases, o.Name)
}
require.ElementsMatch(t, aliases, tc.expectedAliases, "aliases mismatch")
})
}
}
func TestDecodeEmptyPreference(t *testing.T) {
actual, err := decodePreference(filepath.Join("..", "..", "testdata", "kuberc", "empty.kuberc"))
require.NoError(t, err, "unexpected error")
require.Nil(t, actual, "unexpected preferences")
}

View File

@ -19,6 +19,7 @@ package metricsutil
import (
"fmt"
"io"
"slices"
"sort"
"k8s.io/api/core/v1"
@ -28,16 +29,12 @@ import (
)
var (
MeasuredResources = []v1.ResourceName{
v1.ResourceCPU,
v1.ResourceMemory,
}
NodeColumns = []string{"NAME", "CPU(cores)", "CPU(%)", "MEMORY(bytes)", "MEMORY(%)"}
PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"}
NamespaceColumn = "NAMESPACE"
PodColumn = "POD"
)
const ResourceSwap = "swap"
type ResourceMetricsInfo struct {
Name string
Metrics v1.ResourceList
@ -45,11 +42,33 @@ type ResourceMetricsInfo struct {
}
type TopCmdPrinter struct {
out io.Writer
out io.Writer
measuredResources []v1.ResourceName
nodeColumns []string
podColumns []string
// a map from a node name to its missing resources
nodesMissingResources map[string][]string
}
func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
return &TopCmdPrinter{out: out}
func NewTopCmdPrinter(out io.Writer, showSwap bool) *TopCmdPrinter {
printer := &TopCmdPrinter{
out: out,
measuredResources: []v1.ResourceName{
v1.ResourceCPU,
v1.ResourceMemory,
},
nodeColumns: []string{"NAME", "CPU(cores)", "CPU(%)", "MEMORY(bytes)", "MEMORY(%)"},
podColumns: []string{"NAME", "CPU(cores)", "MEMORY(bytes)"},
nodesMissingResources: make(map[string][]string),
}
if showSwap {
printer.measuredResources = append(printer.measuredResources, ResourceSwap)
printer.nodeColumns = append(printer.nodeColumns, "SWAP(bytes)", "SWAP(%)")
printer.podColumns = append(printer.podColumns, "SWAP(bytes)")
}
return printer
}
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
@ -59,25 +78,26 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics,
w := printers.GetNewTabWriter(printer.out)
defer w.Flush()
measuredResources := printer.measuredResources
sort.Sort(NewNodeMetricsSorter(metrics, sortBy))
if !noHeaders {
printColumnNames(w, NodeColumns)
printColumnNames(w, printer.nodeColumns)
}
var usage v1.ResourceList
for _, m := range metrics {
m.Usage.DeepCopyInto(&usage)
printMetricsLine(w, &ResourceMetricsInfo{
printer.printMetricsLine(w, &ResourceMetricsInfo{
Name: m.Name,
Metrics: usage,
Available: availableResources[m.Name],
})
}, measuredResources)
delete(availableResources, m.Name)
}
// print lines for nodes of which the metrics is unreachable.
for nodeName := range availableResources {
printMissingMetricsNodeLine(w, nodeName)
printer.printMissingMetricsNodeLine(w, nodeName, measuredResources)
}
return nil
}
@ -89,7 +109,7 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
w := printers.GetNewTabWriter(printer.out)
defer w.Flush()
columnWidth := len(PodColumns)
columnWidth := len(printer.podColumns)
if !noHeaders {
if withNamespace {
printValue(w, NamespaceColumn)
@ -99,32 +119,39 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
printValue(w, PodColumn)
columnWidth++
}
printColumnNames(w, PodColumns)
printColumnNames(w, printer.podColumns)
}
sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy))
sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy, printer.measuredResources))
for _, m := range metrics {
if printContainers {
sort.Sort(NewContainerMetricsSorter(m.Containers, sortBy))
printSinglePodContainerMetrics(w, &m, withNamespace)
printer.printSinglePodContainerMetrics(w, &m, withNamespace, printer.measuredResources)
} else {
printSinglePodMetrics(w, &m, withNamespace)
printer.printSinglePodMetrics(w, &m, withNamespace, printer.measuredResources)
}
}
if sum {
adder := NewResourceAdder(MeasuredResources)
adder := NewResourceAdder(printer.measuredResources)
for _, m := range metrics {
adder.AddPodMetrics(&m)
}
printPodResourcesSum(w, adder.total, columnWidth)
printer.printPodResourcesSum(w, adder.total, columnWidth, printer.measuredResources)
}
return nil
}
func (printer *TopCmdPrinter) RegisterMissingResource(nodeName, resourceName string) {
if slices.Contains(printer.nodesMissingResources[nodeName], resourceName) {
return
}
printer.nodesMissingResources[nodeName] = append(printer.nodesMissingResources[nodeName], resourceName)
}
func printColumnNames(out io.Writer, names []string) {
for _, name := range names {
printValue(out, name)
@ -132,40 +159,40 @@ func printColumnNames(out io.Writer, names []string) {
fmt.Fprint(out, "\n")
}
func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
podMetrics := getPodMetrics(m)
func (printer *TopCmdPrinter) printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool, measuredResources []v1.ResourceName) {
podMetrics := getPodMetrics(m, measuredResources)
if withNamespace {
printValue(out, m.Namespace)
}
printMetricsLine(out, &ResourceMetricsInfo{
printer.printMetricsLine(out, &ResourceMetricsInfo{
Name: m.Name,
Metrics: podMetrics,
Available: v1.ResourceList{},
})
}, measuredResources)
}
func printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
func (printer *TopCmdPrinter) printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool, measuredResources []v1.ResourceName) {
for _, c := range m.Containers {
if withNamespace {
printValue(out, m.Namespace)
}
printValue(out, m.Name)
printMetricsLine(out, &ResourceMetricsInfo{
printer.printMetricsLine(out, &ResourceMetricsInfo{
Name: c.Name,
Metrics: c.Usage,
Available: v1.ResourceList{},
})
}, measuredResources)
}
}
func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
func getPodMetrics(m *metricsapi.PodMetrics, measuredResources []v1.ResourceName) v1.ResourceList {
podMetrics := make(v1.ResourceList)
for _, res := range MeasuredResources {
for _, res := range measuredResources {
podMetrics[res], _ = resource.ParseQuantity("0")
}
for _, c := range m.Containers {
for _, res := range MeasuredResources {
for _, res := range measuredResources {
quantity := podMetrics[res]
quantity.Add(c.Usage[res])
podMetrics[res] = quantity
@ -174,16 +201,16 @@ func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
return podMetrics
}
func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
func (printer *TopCmdPrinter) printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo, measuredResources []v1.ResourceName) {
printValue(out, metrics.Name)
printAllResourceUsages(out, metrics)
printer.printAllResourceUsages(out, metrics, measuredResources)
fmt.Fprint(out, "\n")
}
func printMissingMetricsNodeLine(out io.Writer, nodeName string) {
func (printer *TopCmdPrinter) printMissingMetricsNodeLine(out io.Writer, nodeName string, measuredResources []v1.ResourceName) {
printValue(out, nodeName)
unknownMetricsStatus := "<unknown>"
for i := 0; i < len(MeasuredResources); i++ {
for i := 0; i < len(measuredResources); i++ {
printValue(out, unknownMetricsStatus)
printValue(out, unknownMetricsStatus)
}
@ -194,13 +221,21 @@ func printValue(out io.Writer, value interface{}) {
fmt.Fprintf(out, "%v\t", value)
}
func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
for _, res := range MeasuredResources {
func (printer *TopCmdPrinter) printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo, measuredResources []v1.ResourceName) {
for _, res := range measuredResources {
if missingResources, found := printer.nodesMissingResources[metrics.Name]; found && slices.Contains(missingResources, string(res)) {
printSingleMissingResource(out)
continue
}
quantity := metrics.Metrics[res]
printSingleResourceUsage(out, res, quantity)
fmt.Fprint(out, "\t")
if available, found := metrics.Available[res]; found {
fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
fraction := 0.0
if !available.IsZero() {
fraction = float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
}
fmt.Fprintf(out, "%d%%\t", int64(fraction))
}
}
@ -210,14 +245,19 @@ func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quant
switch resourceType {
case v1.ResourceCPU:
fmt.Fprintf(out, "%vm", quantity.MilliValue())
case v1.ResourceMemory:
case v1.ResourceMemory, ResourceSwap:
fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
default:
fmt.Fprintf(out, "%v", quantity.Value())
}
}
func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int) {
func printSingleMissingResource(out io.Writer) {
const unavailableStr = "<unknown>"
_, _ = fmt.Fprintf(out, "%s\t%s\t", unavailableStr, unavailableStr)
}
func (printer *TopCmdPrinter) printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int, measuredResources []v1.ResourceName) {
for i := 0; i < columnWidth-2; i++ {
printValue(out, "")
}
@ -227,10 +267,10 @@ func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int)
for i := 0; i < columnWidth-3; i++ {
printValue(out, "")
}
printMetricsLine(out, &ResourceMetricsInfo{
printer.printMetricsLine(out, &ResourceMetricsInfo{
Name: "",
Metrics: total,
Available: v1.ResourceList{},
})
}, measuredResources)
}

View File

@ -130,7 +130,7 @@ node1 1000m 10% 1024Mi 10%
for _, n := range test.nodes {
availableResources[n.Name] = n.Status.Capacity
}
top := NewTopCmdPrinter(out)
top := NewTopCmdPrinter(out, false)
err := top.PrintNodeMetrics(test.nodeMetric, availableResources, test.noHeader, test.sortBy)
assert.Equal(t, test.expectedErr, err)
assert.Equal(t, test.expectedOutput, out.String())
@ -374,7 +374,7 @@ test-1 400m 5120Mi
// Create a new TopCmdPrinter with a test writer.
_, _, out, _ := genericiooptions.NewTestIOStreams()
top := NewTopCmdPrinter(out)
top := NewTopCmdPrinter(out, false)
err := top.PrintPodMetrics(test.podMetric, test.printContainers,
test.withNamespace, test.noHeader, test.sortBy, test.sum)
assert.Equal(t, test.expectedErr, err)

View File

@ -82,11 +82,11 @@ func (p *PodMetricsSorter) Less(i, j int) bool {
}
}
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string, measuredResources []v1.ResourceName) *PodMetricsSorter {
var podMetrics = make([]v1.ResourceList, len(metrics))
if len(sortBy) > 0 {
for i, v := range metrics {
podMetrics[i] = getPodMetrics(&v)
podMetrics[i] = getPodMetrics(&v, measuredResources)
}
}

View File

@ -62,7 +62,7 @@ func TestGetDeploymentPatch(t *testing.T) {
t.Errorf("expected strategic merge patch, got %v", patchType)
}
expectedPatch := `[` +
`{"op":"replace","path":"/spec/template","value":{"metadata":{"creationTimestamp":null},"spec":{"containers":[{"name":"","image":"foo","resources":{}}]}}},` +
`{"op":"replace","path":"/spec/template","value":{"metadata":{},"spec":{"containers":[{"name":"","image":"foo","resources":{}}]}}},` +
`{"op":"replace","path":"/metadata/annotations","value":{"a":"true"}}` +
`]`
if string(patchBytes) != expectedPatch {

View File

@ -154,7 +154,6 @@ func LoadTranslations(root string, getLanguageFn func() string) error {
fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr),
}
klog.V(3).Infof("Setting language to %s", langStr)
// TODO: list the directory and load all files.
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)

View File

@ -3,7 +3,7 @@ kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx-deployment","creationTimestamp":null,"labels":{"name":"nginx"}},"spec":{"selector":{"matchLabels":{"name":"nginx"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"name":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx","resources":{}}]}},"strategy":{}},"status":{}}'
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx-deployment","labels":{"name":"nginx"}},"spec":{"selector":{"matchLabels":{"name":"nginx"}},"template":{"metadata":{"labels":{"name":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx","resources":{}}]}},"strategy":{}},"status":{}}'
creationTimestamp: "2016-10-24T22:15:06Z"
generation: 6
labels:
@ -25,7 +25,6 @@ spec:
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
name: nginx
spec:

View File

@ -77,7 +77,6 @@ spec:
name: test-rc
template:
metadata:
creationTimestamp: null
labels:
name: test-rc
spec:

34
testdata/kuberc/allversions.kuberc vendored Normal file
View File

@ -0,0 +1,34 @@
---
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
aliases:
- name: getn
command: get
prependArgs:
- namespace
- name: runx
command: run
appendArgs:
- --
- custom-arg
defaults:
- command: v1beta1-apply
options:
- name: server-side
default: "true"
- command: v1beta1-delete
options:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete
flags:
- name: interactive
default: "true"

0
testdata/kuberc/empty.kuberc vendored Normal file
View File

36
testdata/kuberc/multiple1.kuberc vendored Normal file
View File

@ -0,0 +1,36 @@
---
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
defaults:
- command: v1beta1-apply
options:
- name: server-side
default: "true"
- command: v1beta1-delete
options:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete
flags:
- name: interactive
default: "true"

42
testdata/kuberc/multiple2.kuberc vendored Normal file
View File

@ -0,0 +1,42 @@
---
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
defaults:
- command: v1beta1-apply-first
options:
- name: server-side
default: "true"
- command: v1beta1-delete-first
---
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
defaults:
- command: v1beta1-apply-second
options:
- name: server-side
default: "true"
- command: v1beta1-delete-second
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete
flags:
- name: interactive
default: "true"

36
testdata/kuberc/multiple3.kuberc vendored Normal file
View File

@ -0,0 +1,36 @@
---
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply-first
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete-first
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply-second
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete-second
flags:
- name: interactive
default: "true"

45
testdata/kuberc/multiple4.kuberc vendored Normal file
View File

@ -0,0 +1,45 @@
---
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply-first
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete-first
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply-second
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete-second
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
defaults:
- command: v1beta1-apply-first
options:
- name: server-side
default: "true"
- command: v1beta1-delete-first

11
testdata/kuberc/unknown.kuberc vendored Normal file
View File

@ -0,0 +1,11 @@
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"

24
testdata/kuberc/unknown2.kuberc vendored Normal file
View File

@ -0,0 +1,24 @@
---
apiVersion: kubectl.config.k8s.io/v1unknown1
kind: Preference
overrides:
- command: v1unknown1-apply
flags:
- name: server-side
default: "true"
- command: v1unknown1-delete
flags:
- name: interactive
default: "true"
---
apiVersion: kubectl.config.k8s.io/v1unknown2
kind: Preference
overrides:
- command: v1unknown2-apply
flags:
- name: server-side
default: "true"
- command: v1unknown2-delete
flags:
- name: interactive
default: "true"

11
testdata/kuberc/v1alpha1.kuberc vendored Normal file
View File

@ -0,0 +1,11 @@
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: v1alpha1-apply
flags:
- name: server-side
default: "true"
- command: v1alpha1-delete
flags:
- name: interactive
default: "true"

11
testdata/kuberc/v1beta1.kuberc vendored Normal file
View File

@ -0,0 +1,11 @@
apiVersion: kubectl.config.k8s.io/v1beta1
kind: Preference
defaults:
- command: v1beta1-apply
options:
- name: server-side
default: "true"
- command: v1beta1-delete
options:
- name: interactive
default: "true"