Compare commits

...

152 Commits

Author SHA1 Message Date
Kubernetes Publisher 2a6e583a42 Merge pull request #133430 from liggitt/json-patch-v4-errors
bump gopkg.in/evanphx/json-patch.v4

Kubernetes-commit: d5065bdf192b2ebb723657b3432c66ce3ea15b23
2025-08-30 17:41:45 +00:00
Kubernetes Publisher 9411a867c0 Merge pull request #133771 from marckhouzam/marck/fixResourceCompletion
Fix completion of resource names

Kubernetes-commit: c9cc8c1ea89c3f2d00722f7c7cb4ff419ba70ba7
2025-08-29 09:25:29 +00:00
Marc Khouzam aa186336f3 Fix linter
Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>

Kubernetes-commit: d194619979252d33c525e972cfaf0eea9dc64b43
2025-08-28 23:04:35 -07:00
Marc Khouzam 4dec168032 Fix completion of resource names
The output format is now used by the `Complete()` function, so it must
be set before invoking said function.

The commit also adds a unit tests for this scenario.

Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>

Kubernetes-commit: f3d278e75d1137f1c91dde7415bc577af3c3be82
2025-08-28 21:06:02 -07:00
Kubernetes Publisher 48317edcc3 Merge pull request #132128 from skitt/cadvisor-0.53
Bump cadvisor to 0.53

Kubernetes-commit: aead71c1c2e1846f554d21a257ff577b65f8493a
2025-08-28 17:44:45 +00:00
Jordan Liggitt 9f67d52cb6 bump gopkg.in/evanphx/json-patch.v4
Kubernetes-commit: 9f8d9432e2067bc8b0e0ea5362b00559846ca54b
2025-08-07 16:02:03 -04:00
Stephen Kitt 33f48403eb Bump cadvisor to 0.53
This brings a few fixes, drops github.com/pkg/errors (as a direct
dependency), and bumps many transitive dependencies. The
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp bump to
v0.61.0 breaks "k8s.io/kubernetes/test/integration/apiserver: tracing"
consistently, so it's held back for now.

github.com/containerd/containerd/api pulls in gopkg.in/yaml.v3 so that
needs to be added to the exceptions in unwanted-dependencies.json.

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

Kubernetes-commit: 684473af6232a5d68a5585837837d1a96f009414
2025-06-05 17:17:56 +02:00
Kubernetes Publisher ceae52f082 Merge pull request #133327 from thockin/kyaml
Change KYAML gate to on-by-default

Kubernetes-commit: d9c4a092f5325ab0a4832b72111f1f0fa5966518
2025-08-28 08:44:28 +00:00
Kubernetes Publisher 74f9adbfa6 Merge pull request #133731 from ardaguclu/wait-cmd-stable
Drop experimental prefix from kubectl wait command

Kubernetes-commit: 9096fcefe52d347827ac358bd8e520367c5e8fe5
2025-08-28 04:54:41 +00:00
Kubernetes Publisher a628f84f45 Merge pull request #133598 from vishnumohanan404/cli-usage-update
kubectl expose --help: change --selector help text for clarity

Kubernetes-commit: 05b66760e4c0f42e6e9305fb3a12e345bd731de2
2025-08-28 04:54:40 +00:00
Kubernetes Publisher e98b7e433d Merge pull request #133582 from BobyMCbobs/update-cronjob-casing-for-kubectl-create-job-help
Update CronJob casing for create job help

Kubernetes-commit: 63337a8b413b9b5db63b2d201de07557fc6189da
2025-08-28 04:54:39 +00:00
Kubernetes Publisher cce124a8d8 Merge pull request #133571 from BenTheElder/deprecated-missing-oh-no
fix many incorrect deprecation warnings in godoc, enable deprecatedComment linter

Kubernetes-commit: 22a567ac31df68929f4cfb6e87a18d2399e37efe
2025-08-28 04:54:35 +00:00
Lorain 9e0a26615e fix using stale pod when evict failed and retry (#133461)
* fix using stale pod when evict failed and retry

* simplify pod refresh process

* use activePod at getPodFn

* fix lint check

* add ut

* introduce EvictErrorRetryDelay

Kubernetes-commit: 66fdbe105831e08b588dd01039a7e3130fd2d36f
2025-08-28 04:54:34 +00:00
Kubernetes Publisher 4ce6135c24 Merge pull request #133367 from ash2k/decouple-term-from-client
Decouple `term` and `remotecommand` packages

Kubernetes-commit: e2af824293a02edcc0797ac53382797a502d5668
2025-08-28 04:54:33 +00:00
Kubernetes Publisher 22d999d2fc Merge pull request #133323 from liggitt/reconcile-retry-on-conflict
Make kubectl auth reconcile retry on conflict

Kubernetes-commit: 817534d78188e57e9e71618bb17d3e19a48d88fa
2025-08-28 04:54:32 +00:00
Kubernetes Publisher 0889899212 Merge pull request #117160 from liggitt/sa-describe
Clean up service account print and describe

Kubernetes-commit: 6958161aa94617357d14fc9b24637f4587296ff1
2025-08-28 04:54:30 +00:00
Kubernetes Publisher f4a1e23b35 Merge pull request #132770 from tom1299/kubect-set-selector-fix-help
Fix help for set selector

Kubernetes-commit: 03ea6eb2c3e464fae13975fd8d7aed30999f5cac
2025-08-28 01:00:39 +00:00
Arda Güçlü 3c19b8deb3 Drop experimental prefix from kubectl wait command
Kubernetes-commit: ff37d8c33945b5c369f40baa1884632cfd0d32c5
2025-08-27 18:36:35 +03:00
Vishnu Mohan cbe60c952e kubectl: fix expose --selector help text
The current help text wordings incorrectly suggests inference only works for RC/RS when it actually works for Pods and Deployments too

Update help text to accurately reflect that selector inference works
for any resource being exposed, as documented in main expose help.

Kubernetes-commit: 5c8f26f48032899031760e5b75ad259d23d312b2
2025-08-19 01:19:07 +05:30
Caleb Woodbine 12dc58ebf7 chore: update CronJob casing for create job help
use the resource Kind casing for `CronJob` in the note for the `from` argument help

Kubernetes-commit: 76e5929d1a140678f94964142e4cc1566dd1a90c
2025-08-18 11:42:40 +12:00
Kubernetes Publisher 7b4e1d1699 Merge pull request #133570 from BenTheElder/pr131351
Clarify staging repository READMEs

Kubernetes-commit: 8082e9ab157b280c740a623ae9d73679b9ee95a4
2025-08-16 07:31:46 +00:00
Benjamin Elder 4d28f5cea7 clarify that staging repos are automatically published
Kubernetes-commit: e49f6116f9eec5d48f2c8913e598fef496644d01
2025-08-15 14:33:07 -07:00
Benjamin Elder 4b52eef334 fix deprecation formatting for k8s.io/kubectl/pkg/cmd/config.NewCmdConfigSetAuthInfo
Kubernetes-commit: 1dd428d4c0140fd3957c30cb716f710718486aab
2025-08-15 10:55:45 -07:00
Benjamin Elder 3962f4011e add pointer to CONTRIBUTING.md for more details on contributing, clarify read-only
Kubernetes-commit: ada2ed8487708056ff11bd2413da1073558e6d7c
2025-08-15 10:13:18 -07:00
Benjamin Elder af99d1e41c special case that kubectl staging repo is currently used to track issues, and only pull requests should be redirected back to the main repo.
Kubernetes-commit: 8919ac43c815f5814f610f1e9f41cc8499ed357c
2025-08-15 10:10:58 -07:00
Benjamin Elder 4f56b5ec5b link to what a staging repository is
Kubernetes-commit: 011d50019758a7405c6ff37f0fdbd44dacb607b5
2025-08-15 10:10:26 -07:00
Mikhail Mazurskiy ce4d90902a Decouple term and remotecommand packages
This allows consumers of term to not pull in dependencies on
github.com/gorilla/websocket and github.com/moby/spdystream.

Kubernetes-commit: 640dabd58b04b72f646ed85947cb8b407b36dc08
2025-08-03 20:35:40 +10:00
Tim Hockin 21b32eea57 Change KYAML gate to on-by-default
Kubernetes-commit: 5af2b732beeae755c5a87b423659ab46968f4a14
2025-07-30 17:49:33 -07:00
tom1299 403b4a41e8 Fix help for set selector
Kubernetes-commit: 925bce297e4f39a4ab6295416621327773bca5e5
2025-07-07 08:15:52 +02:00
sAchin-680 9fb3eee5e8 docs: clarify that this is a staging repository and not for direct contributions
Signed-off-by: sAchin-680 <mrmister680@gmail.com>

Kubernetes-commit: 7c43e6d2fdbf88f8d92559dac3cabb23da2a2957
2025-04-17 17:51:45 +05:30
Kubernetes Publisher 8185d35b7a Merge pull request #132942 from thockin/kyaml
Add KYAML support to kubectl

Kubernetes-commit: 1451dd1b0873e801e082f3a06a52685bcd68dcac
2025-07-25 03:41:49 +00:00
Kubernetes Publisher 80ffc392a2 Merge pull request #131549 from carlory/KEP-3751-GA
[Kep-3751] Promote VolumeAttributesClass to GA

Kubernetes-commit: 7912e5fd677226e4ab18a1940aa8c91bac816997
2025-07-25 03:41:47 +00:00
Kubernetes Publisher 3f6dbadba7 Merge pull request #132935 from benluddy/cbor-bump-custom-marshalers
KEP-4222: Adopt text and JSON transcoding support for CBOR.

Kubernetes-commit: dfc0998baa4d6c2cd630aa3c5b8def4e9b1fcd8e
2025-07-24 23:40:50 +00:00
Kubernetes Publisher 55aec96de2 Merge pull request #132756 from ylink-lfs/ci/redis_removal
ci: redis removal for e2e test dependency simplicity

Kubernetes-commit: b3d00a026decbf3c3d632064f32a71973fb09dfb
2025-07-24 19:32:04 +00:00
Kubernetes Publisher 8f1bcdea60 Merge pull request #132593 from koba1t/update/kubectl-in-kustomize_to_v5.7.0
Update kubectl kustomize to kustomize/v5.7.1

Kubernetes-commit: 65d00aaa9d38d3b67dfc14495758f5fcf4c36fc1
2025-07-24 19:32:03 +00:00
Mayuka Channankaiah 98b4b33964 client-go, kubectl: Replace deprecated ErrWaitTimeout with recommended method (#132718)
* client-go: Replace depracted ErrWaitTimeout with recommended method

* Fix UT and Integration tests

* IT test

Kubernetes-commit: ffe306d67958297202e9492ea644b42c0e7e694d
2025-07-24 19:32:01 +00:00
Dharmit Shah 3cb662b4be JSON & YAML output for kubectl api-resources (#132604)
* Add JSON & YAML output support for kubectl api-resources

Create a separate `PrintFlags` struct within the apiresources.go file
that handles printing only for `kubetl api-resources` because existing
output formats, i.e., wide and name, are already implemented
independently from HumanReadableFlags and NamePrintFlags.

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Use separate printer type for all options

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Unit tests for JSON & YAML outputs

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Separate file for print types

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Move JSON-YAML tests to separate function

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Fix broken unit test

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Unifying JSON & YAML unit test functions

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* Fix linter errors

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

* PR feedback and linter again

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

---------

Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>

Kubernetes-commit: cb33accc8fc4d44e902da4926eee7b828c5e51ec
2025-07-24 15:35:50 +00:00
koba1t fb7d414ec2 Update kubectl kustomize to kyaml/v0.20.1, cmd/config/v0.20.1, api/v0.20.1, kustomize/v5.7.1
Kubernetes-commit: ee8ef383bec15dd63c5d22ffe6a6e0703fc1ba4a
2025-07-23 22:30:05 +09:00
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
Tim Hockin 02042ef887 Add KYAML support to kubectl
KYAML is a strict subset of YAML, which is sort of halfway between YAML
and JSON.  It has the following properties:
* Does not depend on whitespace (easier to text-patch and template).
* Always quotes value strings (no ambiguity aroud things like "no").
* Allows quoted keys, but does not require them, and only quotes them if
  they are not obviously safe (e.g. "no" would always be quoted).
* Always uses {} for structs and maps (no more obscure errors about
  mapping values).
* Always uses [] for lists (no more trying to figure out if a dash
  changes the meaning).
* When printing, it includes a header which makes it clear this is YAML
  and not ill-formed JSON.
* Allows trailing commas
* Allows comments,
* Tries to economize on vertical space by "cuddling" some kinds of
  brackets together.
* Retains comments.

Examples:

A struct:

```yaml
metadata: {
  creationTimestamp: "2024-12-11T00:10:11Z",
  labels: {
    app: "hostnames",
  },
  name: "hostnames",
  namespace: "default",
  resourceVersion: "15231643",
  uid: "f64dbcba-9c58-40b0-bbe7-70495efb5202",
}
```

A list of primitves:

```yaml
ipFamilies: [
  "IPv4",
  "IPv6",
]
```

A list of structs:

```yaml
ports: [{
  port: 80,
  protocol: "TCP",
  targetPort: 80,
}, {
  port: 443,
  protocol: "TCP",
  targetPort: 443,
}]
```

A multi-document stream:

```yaml
---
{
  foo: "bar",
}
---
{
  qux: "zrb",
}
```

Kubernetes-commit: 2cb955d8ccae30167b9610bfe51c2f86e83a1958
2025-07-14 09:24:18 -07:00
Tim Hockin 8ed5bb5f0a Re-vendor sigs.k8s.io/yaml @ v1.6.0
Kubernetes-commit: 8182a27f3b0769cefe1bcebfb938a7bafd51c88e
2025-07-24 11:46:03 -07: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
ylink-lfs 1b3f4fd0f4 ci: redis removal for e2e test dependency simplicity
Kubernetes-commit: 075abb07a9122d9a2f550b25a32199d971107d6e
2025-07-02 09:15:43 +08: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
Ben Luddy 9c02ed6a6e Bump to github.com/fxamacker/cbor/v2 v2.9.0.
Kubernetes-commit: 917659269af60f8ca960deeb0991df93e5ad1635
2025-06-24 14:25:43 -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
carlory cf54a4ea54 Promoted API `VolumeAttributesClass` and `VolumeAttributesClassList` to `storage.k8s.io/v1`.
Promoted feature-gate `VolumeAttributesClass` to GA (on by default)

Signed-off-by: carlory <baofa.fan@daocloud.io>

Kubernetes-commit: 94bf8fc8a9d1d6c989eddad07996be0ca4dd3448
2025-04-30 17:35:21 +08: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
Jordan Liggitt a7bf48f663 Clean up service account print and describe
Kubernetes-commit: 0c91e28360dd9f4caae4ed9d1aea4ceeca4aedcb
2023-04-07 09:54:24 -04:00
Jordan Liggitt 5c587a03ba Make kubectl auth reconcile retry on conflict
Kubernetes-commit: 3b0a85170a76f419be05c40a866e10c3b760195b
2022-05-28 11:04:14 -04:00
123 changed files with 4388 additions and 1072 deletions

View File

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

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

79
go.mod
View File

@ -13,54 +13,54 @@ 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
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
go.yaml.in/yaml/v2 v2.4.2
golang.org/x/sys v0.33.0
gopkg.in/evanphx/json-patch.v4 v4.13.0
k8s.io/api v0.0.0-20250830163657-b903cd06836a
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-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
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
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
sigs.k8s.io/kustomize/kyaml v0.20.1
sigs.k8s.io/randfill v1.0.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/yaml v1.6.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-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.9.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
@ -70,7 +70,7 @@ 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
@ -79,27 +79,16 @@ require (
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
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/kustomize/api v0.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
sigs.k8s.io/kustomize/api v0.20.1 // indirect
)

158
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.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
@ -50,31 +41,25 @@ 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=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/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 +73,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 +88,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=
@ -120,24 +105,18 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_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,53 +136,42 @@ 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=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -216,41 +184,49 @@ 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=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.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-20250830163657-b903cd06836a h1:qS+abmAu2zbGFkbN1vA7LKS07jsXBN1BvTFXFvaGOLI=
k8s.io/api v0.0.0-20250830163657-b903cd06836a/go.mod h1:/IpJMZ4ur2JBuX+kkBc115bnq09sFfUnbuFNrdEe5yc=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4 h1:ObQoOWhkcPbMnU7PIHT2pkO2wK66CcBn6vD+77CidHM=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4/go.mod h1:fDax9lidUgmNSmBlzUrSISURQmHpeyamBbKX9jGbJ3g=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e h1:dovWIA2PM0UrJrZdUBV8uy5pExliSBXSFeL0bI6IX6E=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e/go.mod h1:/yp9r2rD6AV7MYM/gmb55/6LttRuURzjhgqbfiFQ0Rg=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97 h1:6ks7Y8CNm05xZ6eyE0db5IDP54PIyRM3aZhpflG55hI=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97/go.mod h1:xTpANYjBhGsmpO7Gdw8kMt3yQfciVwyRhbqcq77qwyI=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7 h1:JQ/dO+EIXNQ+y3Vlez6PC6r7T+0JvNQWrBx6CD3jKls=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7/go.mod h1:ZZMk2BFRSF/kI9Y5qKmvTk4SJM654XsQ5eJ9cP7mrhw=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc h1:hEo6RVciD0e0QvtMBgwG9a7fFWb/vkx0Jvw/iQ5i+lQ=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc/go.mod h1:TmnJ20kJrkgbEqHgHoUxoTksgIUJNCWtv/QM+yBqCF0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-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=
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=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd h1:GNGeQ8o/xw8TwhavcXmWrGKHd0ez5TRx8qRqyncbFH4=
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd/go.mod h1:OYYW2zJf2TAQHmxQwQgSsP/i40dCZig4RQkw3o3vFPE=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/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.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

@ -19,6 +19,7 @@ package drain
import (
"errors"
"fmt"
"time"
"github.com/spf13/cobra"
@ -151,10 +152,11 @@ func NewDrainCmdOptions(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
PrintFlags: genericclioptions.NewPrintFlags("drained").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
drainer: &drain.Helper{
GracePeriodSeconds: -1,
Out: ioStreams.Out,
ErrOut: ioStreams.ErrOut,
ChunkSize: cmdutil.DefaultChunkSize,
GracePeriodSeconds: -1,
EvictErrorRetryDelay: 5 * time.Second,
Out: ioStreams.Out,
ErrOut: ioStreams.ErrOut,
ChunkSize: cmdutil.DefaultChunkSize,
},
}
o.drainer.OnPodDeletionOrEvictionFinished = o.onPodDeletionOrEvictionFinished

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

@ -366,7 +366,9 @@ func (p *ExecOptions) Run() error {
var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw {
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(t.GetSize())
sizeQueue = &terminalSizeQueueAdapter{
delegate: t.MonitorSize(t.GetSize()),
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
@ -403,3 +405,18 @@ func (p *ExecOptions) Run() error {
return nil
}
type terminalSizeQueueAdapter struct {
delegate term.TerminalSizeQueue
}
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
next := a.delegate.Next()
if next == nil {
return nil
}
return &remotecommand.TerminalSize{
Width: next.Width,
Height: next.Height,
}
}

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

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

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

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

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,42 @@ 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"
// owner: @thockin
// kep: https://kep.k8s.io/5296
//
// Support KYAML output.
KYAMLOutput FeatureGate = "KUBECTL_KYAML"
)
// 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

@ -36,7 +36,7 @@ import (
)
// TODO(knverey): remove this hardcoding once kubectl being built with module support makes BuildInfo available.
const kustomizeVersion = "v5.5.0"
const kustomizeVersion = "v5.7.1"
// Version is a struct for version information
type Version struct {
@ -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

@ -174,7 +174,7 @@ func getObjAndCheckCondition(ctx context.Context, info *resource.Info, o *WaitOp
return err
})
if err != nil {
if errors.Is(err, wait.ErrWaitTimeout) { // nolint:staticcheck // SA1019
if wait.Interrupted(err) { // nolint:staticcheck // SA1019
return result, false, errWaitTimeoutWithName
}
return result, false, err

View File

@ -113,7 +113,7 @@ func IsDeleted(ctx context.Context, info *resource.Info, o *WaitOptions) (runtim
return err
})
if err != nil {
if errors.Is(err, wait.ErrWaitTimeout) { // nolint:staticcheck // SA1019
if wait.Interrupted(err) { // nolint:staticcheck // SA1019
return gottenObj, false, errWaitTimeoutWithName
}
return gottenObj, false, err

View File

@ -46,7 +46,7 @@ import (
var (
waitLong = templates.LongDesc(i18n.T(`
Experimental: Wait for a specific condition on one or many resources.
Wait for a specific condition on one or many resources.
The command takes multiple resources and waits until the specified condition
is seen in the Status field of every given resource.

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"
@ -51,7 +53,6 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
schedulingv1 "k8s.io/api/scheduling/v1"
storagev1 "k8s.io/api/storage/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
@ -229,7 +230,7 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]ResourceDescr
{Group: certificatesv1beta1.GroupName, Kind: "CertificateSigningRequest"}: &CertificateSigningRequestDescriber{c},
{Group: storagev1.GroupName, Kind: "StorageClass"}: &StorageClassDescriber{c},
{Group: storagev1.GroupName, Kind: "CSINode"}: &CSINodeDescriber{c},
{Group: storagev1beta1.GroupName, Kind: "VolumeAttributesClass"}: &VolumeAttributesClassDescriber{c},
{Group: storagev1.GroupName, Kind: "VolumeAttributesClass"}: &VolumeAttributesClassDescriber{c},
{Group: policyv1beta1.GroupName, Kind: "PodDisruptionBudget"}: &PodDisruptionBudgetDescriber{c},
{Group: policyv1.GroupName, Kind: "PodDisruptionBudget"}: &PodDisruptionBudgetDescriber{c},
{Group: rbacv1.GroupName, Kind: "Role"}: &RoleDescriber{c},
@ -1863,7 +1864,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 +1876,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 +2589,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 +3164,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)
}
@ -3433,61 +3445,15 @@ func (d *ServiceAccountDescriber) Describe(namespace, name string, describerSett
return "", err
}
tokens := []corev1.Secret{}
// missingSecrets is the set of all secrets present in the
// serviceAccount but not present in the set of existing secrets.
missingSecrets := sets.New[string]()
secrets := corev1.SecretList{}
err = runtimeresource.FollowContinue(&metav1.ListOptions{Limit: describerSettings.ChunkSize},
func(options metav1.ListOptions) (runtime.Object, error) {
newList, err := d.CoreV1().Secrets(namespace).List(context.TODO(), options)
if err != nil {
return nil, runtimeresource.EnhanceListError(err, options, corev1.ResourceSecrets.String())
}
secrets.Items = append(secrets.Items, newList.Items...)
return newList, nil
})
// errors are tolerated here in order to describe the serviceAccount with all
// of the secrets that it references, even if those secrets cannot be fetched.
if err == nil {
// existingSecrets is the set of all secrets remaining on a
// service account that are not present in the "tokens" slice.
existingSecrets := sets.New[string]()
for _, s := range secrets.Items {
if s.Type == corev1.SecretTypeServiceAccountToken {
name := s.Annotations[corev1.ServiceAccountNameKey]
uid := s.Annotations[corev1.ServiceAccountUIDKey]
if name == serviceAccount.Name && uid == string(serviceAccount.UID) {
tokens = append(tokens, s)
}
}
existingSecrets.Insert(s.Name)
}
for _, s := range serviceAccount.Secrets {
if !existingSecrets.Has(s.Name) {
missingSecrets.Insert(s.Name)
}
}
for _, s := range serviceAccount.ImagePullSecrets {
if !existingSecrets.Has(s.Name) {
missingSecrets.Insert(s.Name)
}
}
}
var events *corev1.EventList
if describerSettings.ShowEvents {
events, _ = searchEvents(d.CoreV1(), serviceAccount, describerSettings.ChunkSize)
}
return describeServiceAccount(serviceAccount, tokens, missingSecrets, events)
return describeServiceAccount(serviceAccount, events)
}
func describeServiceAccount(serviceAccount *corev1.ServiceAccount, tokens []corev1.Secret, missingSecrets sets.Set[string], events *corev1.EventList) (string, error) {
func describeServiceAccount(serviceAccount *corev1.ServiceAccount, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", serviceAccount.Name)
@ -3498,28 +3464,16 @@ func describeServiceAccount(serviceAccount *corev1.ServiceAccount, tokens []core
var (
emptyHeader = " "
pullHeader = "Image pull secrets:"
mountHeader = "Mountable secrets: "
tokenHeader = "Tokens: "
pullSecretNames = []string{}
mountSecretNames = []string{}
tokenSecretNames = []string{}
pullSecretNames = []string{}
)
for _, s := range serviceAccount.ImagePullSecrets {
pullSecretNames = append(pullSecretNames, s.Name)
}
for _, s := range serviceAccount.Secrets {
mountSecretNames = append(mountSecretNames, s.Name)
}
for _, s := range tokens {
tokenSecretNames = append(tokenSecretNames, s.Name)
}
types := map[string][]string{
pullHeader: pullSecretNames,
mountHeader: mountSecretNames,
tokenHeader: tokenSecretNames,
pullHeader: pullSecretNames,
}
for _, header := range sets.List(sets.KeySet(types)) {
names := types[header]
@ -3528,11 +3482,7 @@ func describeServiceAccount(serviceAccount *corev1.ServiceAccount, tokens []core
} else {
prefix := header
for _, name := range names {
if missingSecrets.Has(name) {
w.Write(LEVEL_0, "%s\t%s (not found)\n", prefix, name)
} else {
w.Write(LEVEL_0, "%s\t%s\n", prefix, name)
}
w.Write(LEVEL_0, "%s\t%s\n", prefix, name)
prefix = emptyHeader
}
}
@ -4563,13 +4513,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")
@ -4809,7 +4761,7 @@ type VolumeAttributesClassDescriber struct {
}
func (d *VolumeAttributesClassDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
vac, err := d.StorageV1beta1().VolumeAttributesClasses().Get(context.TODO(), name, metav1.GetOptions{})
vac, err := d.StorageV1().VolumeAttributesClasses().Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return "", err
}
@ -4822,7 +4774,7 @@ func (d *VolumeAttributesClassDescriber) Describe(namespace, name string, descri
return describeVolumeAttributesClass(vac, events)
}
func describeVolumeAttributesClass(vac *storagev1beta1.VolumeAttributesClass, events *corev1.EventList) (string, error) {
func describeVolumeAttributesClass(vac *storagev1.VolumeAttributesClass, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", vac.Name)

View File

@ -19,6 +19,7 @@ package describe
import (
"bytes"
"fmt"
"maps"
"reflect"
"strings"
"testing"
@ -42,7 +43,6 @@ import (
policyv1beta1 "k8s.io/api/policy/v1beta1"
schedulingv1 "k8s.io/api/scheduling/v1"
storagev1 "k8s.io/api/storage/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -350,27 +350,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 +712,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 +806,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 +1165,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 +1371,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 {
@ -3836,7 +4031,7 @@ Parameters: param1=value1,param2=value2
Events: <none>
`
f := fake.NewSimpleClientset(&storagev1beta1.VolumeAttributesClass{
f := fake.NewSimpleClientset(&storagev1.VolumeAttributesClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
ResourceVersion: "4",
@ -6043,9 +6238,7 @@ func TestDescribeServiceAccount(t *testing.T) {
Namespace: foo
Labels: <none>
Annotations: <none>
Image pull secrets: test-local-ref (not found)
Mountable secrets: test-objectref (not found)
Tokens: <none>
Image pull secrets: test-local-ref
Events: <none>` + "\n"
if out != expectedOut {
t.Errorf("expected : %q\n but got output:\n %q", expectedOut, out)

View File

@ -74,6 +74,9 @@ type Helper struct {
// won't drain otherwise
SkipWaitForDeleteTimeoutSeconds int
// EvictErrorRetryDelay is used to control the retry delay after a pod eviction error
EvictErrorRetryDelay time.Duration
// AdditionalFilters are applied sequentially after base drain filters to
// exclude pods using custom logic. Any filter that returns PodDeleteStatus
// with Delete == false will immediately stop execution of further filters.
@ -278,37 +281,27 @@ func (d *Helper) evictPods(pods []corev1.Pod, evictionGroupVersion schema.GroupV
defer cancel()
for _, pod := range pods {
go func(pod corev1.Pod, returnCh chan error) {
refreshPod := false
activePod := pod
for {
switch d.DryRunStrategy {
case cmdutil.DryRunServer:
fmt.Fprintf(d.Out, "evicting pod %s/%s (server dry run)\n", pod.Namespace, pod.Name)
//nolint:errcheck
fmt.Fprintf(d.Out, "evicting pod %s/%s (server dry run)\n", activePod.Namespace, activePod.Name)
default:
if d.OnPodDeletionOrEvictionStarted != nil {
d.OnPodDeletionOrEvictionStarted(&pod, true)
d.OnPodDeletionOrEvictionStarted(&activePod, true)
}
fmt.Fprintf(d.Out, "evicting pod %s/%s\n", pod.Namespace, pod.Name)
//nolint:errcheck
fmt.Fprintf(d.Out, "evicting pod %s/%s\n", activePod.Namespace, activePod.Name)
}
select {
case <-ctx.Done():
// return here or we'll leak a goroutine.
returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: global timeout reached: %v", pod.Name, pod.Namespace, globalTimeout)
returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: global timeout reached: %v", activePod.Name, activePod.Namespace, globalTimeout)
return
default:
}
// Create a temporary pod so we don't mutate the pod in the loop.
activePod := pod
if refreshPod {
freshPod, err := getPodFn(pod.Namespace, pod.Name)
// We ignore errors and let eviction sort it out with
// the original pod.
if err == nil {
activePod = *freshPod
}
refreshPod = false
}
err := d.EvictPod(activePod, evictionGroupVersion)
if err == nil {
break
@ -316,8 +309,9 @@ func (d *Helper) evictPods(pods []corev1.Pod, evictionGroupVersion schema.GroupV
returnCh <- nil
return
} else if apierrors.IsTooManyRequests(err) {
fmt.Fprintf(d.ErrOut, "error when evicting pods/%q -n %q (will retry after 5s): %v\n", activePod.Name, activePod.Namespace, err)
time.Sleep(5 * time.Second)
//nolint:errcheck
fmt.Fprintf(d.ErrOut, "error when evicting pods/%q -n %q (will retry after %v): %v\n", activePod.Name, activePod.Namespace, d.EvictErrorRetryDelay, err)
time.Sleep(d.EvictErrorRetryDelay)
} else if !activePod.ObjectMeta.DeletionTimestamp.IsZero() && apierrors.IsForbidden(err) && apierrors.HasStatusCause(err, corev1.NamespaceTerminatingCause) {
// an eviction request in a deleting namespace will throw a forbidden error,
// if the pod is already marked deleted, we can ignore this error, an eviction
@ -326,12 +320,19 @@ func (d *Helper) evictPods(pods []corev1.Pod, evictionGroupVersion schema.GroupV
} else if apierrors.IsForbidden(err) && apierrors.HasStatusCause(err, corev1.NamespaceTerminatingCause) {
// an eviction request in a deleting namespace will throw a forbidden error,
// if the pod is not marked deleted, we retry until it is.
fmt.Fprintf(d.ErrOut, "error when evicting pod %q from terminating namespace %q (will retry after 5s): %v\n", activePod.Name, activePod.Namespace, err)
time.Sleep(5 * time.Second)
//nolint:errcheck
fmt.Fprintf(d.ErrOut, "error when evicting pod %q from terminating namespace %q (will retry after %v): %v\n", activePod.Name, activePod.Namespace, d.EvictErrorRetryDelay, err)
time.Sleep(d.EvictErrorRetryDelay)
} else {
returnCh <- fmt.Errorf("error when evicting pods/%q -n %q: %v", activePod.Name, activePod.Namespace, err)
return
}
freshPod, err := getPodFn(activePod.Namespace, activePod.Name)
// we ignore errors and let eviction sort it out with the original pod.
if err == nil {
activePod = *freshPod
}
}
if d.DryRunStrategy == cmdutil.DryRunServer {
returnCh <- nil
@ -339,7 +340,7 @@ func (d *Helper) evictPods(pods []corev1.Pod, evictionGroupVersion schema.GroupV
}
params := waitForDeleteParams{
ctx: ctx,
pods: []corev1.Pod{pod},
pods: []corev1.Pod{activePod},
interval: 1 * time.Second,
timeout: time.Duration(math.MaxInt64),
usingEviction: true,

View File

@ -529,3 +529,110 @@ func TestFilterPods(t *testing.T) {
})
}
}
func TestEvictDuringNamespaceTerminating(t *testing.T) {
testPodUID := types.UID("test-uid")
testPodName := "test-pod"
testNamespace := "default"
retryDelay := 5 * time.Millisecond
globalTimeout := 2 * retryDelay
tests := []struct {
description string
refresh bool
err error
}{
{
description: "Pod refreshed after NamespaceTerminating error",
refresh: true,
err: nil,
},
{
description: "Pod not refreshed after NamespaceTerminating error",
refresh: false,
err: fmt.Errorf("error when evicting pods/%q -n %q: global timeout reached: %v", testPodName, testNamespace, globalTimeout),
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
var retry bool
initialPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: testPodName,
Namespace: testNamespace,
UID: testPodUID,
},
}
// pod with DeletionTimestamp, indicating deletion in progress
deletedPod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: testPodName,
Namespace: testNamespace,
UID: testPodUID,
DeletionTimestamp: &metav1.Time{Time: time.Now()},
},
}
evictPods := []corev1.Pod{*initialPod}
k := fake.NewClientset(initialPod)
addEvictionSupport(t, k, "v1")
// mock eviction to return NamespaceTerminating error
k.PrependReactor("create", "pods", func(action ktest.Action) (bool, runtime.Object, error) {
if action.GetSubresource() != "eviction" {
return false, nil, nil
}
err := apierrors.NewForbidden(
schema.GroupResource{Resource: "pods"},
testPodName,
errors.New("namespace is terminating"),
)
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{
Type: corev1.NamespaceTerminatingCause,
})
return true, nil, err
})
k.PrependReactor("get", "pods", func(action ktest.Action) (bool, runtime.Object, error) {
if !test.refresh {
// for non-refresh test, always return the initial pod
return true, initialPod, nil
}
if retry {
// second call, pod is deleted
return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "pods"}, testPodName)
}
// first call, pod is being deleted
retry = true
return true, deletedPod, nil
})
h := &Helper{
Client: k,
DisableEviction: false,
Out: os.Stdout,
ErrOut: os.Stderr,
Timeout: globalTimeout,
EvictErrorRetryDelay: retryDelay,
}
err := h.DeleteOrEvictPods(evictPods)
if test.err == nil && err != nil {
t.Errorf("expected no error, got: %v", err)
} else if test.err != nil && (err == nil || err.Error() != test.err.Error()) {
t.Errorf("%s: unexpected eviction; actual %v; expected %v", test.description, err, test.err)
}
})
}
}

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 {

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