Compare commits

...

49 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
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
ylink-lfs 1b3f4fd0f4 ci: redis removal for e2e test dependency simplicity
Kubernetes-commit: 075abb07a9122d9a2f550b25a32199d971107d6e
2025-07-02 09:15:43 +08: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
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
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
33 changed files with 917 additions and 296 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)

46
go.mod
View File

@ -27,24 +27,24 @@ require (
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
go.yaml.in/yaml/v2 v2.4.2
golang.org/x/sys v0.31.0
gopkg.in/evanphx/json-patch.v4 v4.12.0
k8s.io/api v0.0.0-20250715090528-7da28ad7db85
k8s.io/apimachinery v0.0.0-20250715090235-1ebcba2516a6
k8s.io/cli-runtime v0.0.0-20250715094709-a28ba5287690
k8s.io/client-go v0.0.0-20250715090929-f78427e36774
k8s.io/component-base v0.0.0-20250715092145-ef3b5b450d5d
k8s.io/component-helpers v0.0.0-20250715092311-11b5d493a527
golang.org/x/sys v0.33.0
gopkg.in/evanphx/json-patch.v4 v4.13.0
k8s.io/api v0.0.0-20250830163657-b903cd06836a
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/metrics v0.0.0-20250715094530-e16345c4c004
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.6.0
sigs.k8s.io/kustomize/kyaml v0.19.0
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.2.0
sigs.k8s.io/yaml v1.5.0
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/yaml v1.6.0
)
require (
@ -52,16 +52,15 @@ require (
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.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // 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
@ -77,20 +76,19 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // 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/kustomize/api v0.20.1 // indirect
)

92
go.sum
View File

@ -23,12 +23,12 @@ github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2
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/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/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/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,8 +50,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
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=
@ -107,8 +105,6 @@ 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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -155,27 +151,27 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
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/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=
@ -188,49 +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/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/api v0.0.0-20250715090528-7da28ad7db85 h1:QfiiAaLAQKj+yvMS1ySLpj3UWzxJixBEaWvKNtzwnQM=
k8s.io/api v0.0.0-20250715090528-7da28ad7db85/go.mod h1:MCwhlGL+lP5brf6CuU20fWxaLh/8tUSlu4VM1cOD8Lo=
k8s.io/apimachinery v0.0.0-20250715090235-1ebcba2516a6 h1:lH8NMkqxmCWN1CvOiqbfphUcl+EAk95z+3Le9bfEbJ4=
k8s.io/apimachinery v0.0.0-20250715090235-1ebcba2516a6/go.mod h1:TP8uyOuDEOnzGpLOdffo8hPnIjVDljZCxCM/fruV+5M=
k8s.io/cli-runtime v0.0.0-20250715094709-a28ba5287690 h1:1YwSuyjcnhvaAaes2zGdbilWjLeHBxZkJ1hWKz0/IXc=
k8s.io/cli-runtime v0.0.0-20250715094709-a28ba5287690/go.mod h1:WykG0uYhov9/a+lenZsbNR4Z5AvFgUzk4bRkfB6SVdY=
k8s.io/client-go v0.0.0-20250715090929-f78427e36774 h1:OJXhumReMNIzlpFEEQvl89u+u7KmQ6fa4I3TpZQYjIg=
k8s.io/client-go v0.0.0-20250715090929-f78427e36774/go.mod h1:y02d1W5RQ3IDA7qs1unUQEkERwkgLrd7fuDANdUN31E=
k8s.io/component-base v0.0.0-20250715092145-ef3b5b450d5d h1:dwVoeQ3VARtU56EIoD/h9N5P8HertH7rBD99fESoxN4=
k8s.io/component-base v0.0.0-20250715092145-ef3b5b450d5d/go.mod h1:Cx9XNntOC5C9BXP8nozdA2se8LRKYnh2rlhglgoCxp8=
k8s.io/component-helpers v0.0.0-20250715092311-11b5d493a527 h1:KUTvlyyLHBRb9BLmlvu2EEsDUM7FATEpZx370bUZ9a8=
k8s.io/component-helpers v0.0.0-20250715092311-11b5d493a527/go.mod h1:u8G9DUkNxykMmVeOEc6Pf1Tagyjc/nKm0QXG9VQYKY8=
k8s.io/api v0.0.0-20250830163657-b903cd06836a h1:qS+abmAu2zbGFkbN1vA7LKS07jsXBN1BvTFXFvaGOLI=
k8s.io/api v0.0.0-20250830163657-b903cd06836a/go.mod h1:/IpJMZ4ur2JBuX+kkBc115bnq09sFfUnbuFNrdEe5yc=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4 h1:ObQoOWhkcPbMnU7PIHT2pkO2wK66CcBn6vD+77CidHM=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4/go.mod h1:fDax9lidUgmNSmBlzUrSISURQmHpeyamBbKX9jGbJ3g=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e h1:dovWIA2PM0UrJrZdUBV8uy5pExliSBXSFeL0bI6IX6E=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e/go.mod h1:/yp9r2rD6AV7MYM/gmb55/6LttRuURzjhgqbfiFQ0Rg=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97 h1:6ks7Y8CNm05xZ6eyE0db5IDP54PIyRM3aZhpflG55hI=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97/go.mod h1:xTpANYjBhGsmpO7Gdw8kMt3yQfciVwyRhbqcq77qwyI=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7 h1:JQ/dO+EIXNQ+y3Vlez6PC6r7T+0JvNQWrBx6CD3jKls=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7/go.mod h1:ZZMk2BFRSF/kI9Y5qKmvTk4SJM654XsQ5eJ9cP7mrhw=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc h1:hEo6RVciD0e0QvtMBgwG9a7fFWb/vkx0Jvw/iQ5i+lQ=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc/go.mod h1:TmnJ20kJrkgbEqHgHoUxoTksgIUJNCWtv/QM+yBqCF0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/metrics v0.0.0-20250715094530-e16345c4c004 h1:JMUcdZhgvBQOMwlDWU+DYuxu/msdf2vlP19crwbaF18=
k8s.io/metrics v0.0.0-20250715094530-e16345c4c004/go.mod h1:oDIgK3jknb/hium+pU4Dca0PB6o53GwvdUA576x1xb8=
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.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA=
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.2.0 h1:msyqjP8Nyd5sF3QSmJouFSzcBIdwq4ct8d1/7VSBHIQ=
sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
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

@ -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,7 +301,9 @@ 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
@ -357,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

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

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

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

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

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

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

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

@ -455,6 +455,12 @@ const (
// Transition to WebSockets.
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
// 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

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

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

@ -53,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"
@ -231,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},
@ -1865,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, ", ")
}
@ -1873,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, ", ")
}
@ -3438,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)
@ -3503,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]
@ -3533,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
}
}
@ -4816,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
}
@ -4829,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

@ -43,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"
@ -1372,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 {
@ -3964,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",
@ -6171,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

@ -25,6 +25,7 @@ import (
"time"
"github.com/spf13/cobra"
"k8s.io/utils/ptr"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -318,16 +319,18 @@ func compGetResourceList(restClientGetter genericclioptions.RESTClientGetter, cm
streams := genericiooptions.IOStreams{In: os.Stdin, Out: buf, ErrOut: io.Discard}
o := apiresources.NewAPIResourceOptions(streams)
o.Complete(restClientGetter, cmd, nil)
// Get the list of resources
o.Output = "name"
o.PrintFlags.OutputFormat = ptr.To("name")
o.Cached = true
o.Verbs = []string{"get"}
// TODO:Should set --request-timeout=5s
if err := o.Complete(restClientGetter, cmd, nil); err != nil {
return []string{}
}
// Ignore errors as the output may still be valid
o.RunAPIResources()
_ = o.RunAPIResources()
// Resources can be a comma-separated list. The last element is then
// the one we should complete. For example if toComplete=="pods,secre"

View File

@ -492,6 +492,101 @@ func TestResourceAndPortCompletionFunc(t *testing.T) {
}
}
func TestResourceTypeAndNameCompletionFuncResourceList(t *testing.T) {
// Set up a fake discovery client with some API resources
dc := cmdtesting.NewFakeCachedDiscoveryClient()
dc.PreferredResources = []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Name: "pods",
Namespaced: true,
Kind: "Pod",
Verbs: []string{"get", "list"},
},
{
Name: "services",
Namespaced: true,
Kind: "Service",
Verbs: []string{"get", "list"},
},
{
Name: "secrets",
Namespaced: true,
Kind: "Secret",
Verbs: []string{"get", "list"},
},
},
},
{
GroupVersion: "apps/v1",
APIResources: []metav1.APIResource{
{
Name: "deployments",
Namespaced: true,
Kind: "Deployment",
Verbs: []string{"get", "list"},
},
},
},
}
testCases := []struct {
name string
args []string
toComplete string
expectedComps []string
expectedDirective cobra.ShellCompDirective
}{
{
name: "complete resources starting with 's'",
args: []string{},
toComplete: "s",
expectedComps: []string{"secrets", "services"},
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
name: "complete resources starting with 'p'",
args: []string{},
toComplete: "p",
expectedComps: []string{"pods"},
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
name: "complete resources starting with 'd'",
args: []string{},
toComplete: "d",
expectedComps: []string{"deployments.apps"},
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
name: "complete all resources with empty string",
args: []string{},
toComplete: "",
expectedComps: []string{"deployments.apps", "pods", "secrets", "services"},
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
name: "no matches",
args: []string{},
toComplete: "xyz",
expectedComps: []string{},
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tf, cmd := prepareCompletionTest()
tf.WithDiscoveryClient(dc)
compFunc := ResourceTypeAndNameCompletionFunc(tf)
comps, directive := compFunc(cmd, tc.args, tc.toComplete)
checkCompletion(t, comps, tc.expectedComps, directive, tc.expectedDirective)
})
}
}
func setMockFactory(config api.Config) {
clientConfig := clientcmd.NewDefaultClientConfig(config, nil)
testFactory := cmdtesting.NewTestFactory().WithClientConfig(clientConfig)

View File

@ -21,12 +21,28 @@ import (
"github.com/moby/term"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
)
// TerminalSize represents the width and height of a terminal.
// It is the same as staging/src/k8s.io/client-go/tools/remotecommand.TerminalSize.
// Copied to decouple the packages. Terminal-related package should not depend on API client and vice versa.
type TerminalSize struct {
Width uint16
Height uint16
}
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
// It is the same as staging/src/k8s.io/client-go/tools/remotecommand.TerminalSizeQueue.
// Copied to decouple the packages. Terminal-related package should not depend on API client and vice versa.
type TerminalSizeQueue interface {
// Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped.
Next() *TerminalSize
}
// GetSize returns the current size of the user's terminal. If it isn't a terminal,
// nil is returned.
func (t TTY) GetSize() *remotecommand.TerminalSize {
func (t TTY) GetSize() *TerminalSize {
outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal {
return nil
@ -35,19 +51,19 @@ func (t TTY) GetSize() *remotecommand.TerminalSize {
}
// GetSize returns the current size of the terminal associated with fd.
func GetSize(fd uintptr) *remotecommand.TerminalSize {
func GetSize(fd uintptr) *TerminalSize {
winsize, err := term.GetWinsize(fd)
if err != nil {
runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
return nil
}
return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height}
return &TerminalSize{Width: winsize.Width, Height: winsize.Height}
}
// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with
// initialSizes, or nil if there's no TTY present.
func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue {
func (t *TTY) MonitorSize(initialSizes ...*TerminalSize) TerminalSizeQueue {
outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal {
return nil
@ -57,7 +73,7 @@ func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecom
t: *t,
// make it buffered so we can send the initial terminal sizes without blocking, prior to starting
// the streaming below
resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)),
resizeChan: make(chan TerminalSize, len(initialSizes)),
stopResizing: make(chan struct{}),
}
@ -70,16 +86,16 @@ func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecom
type sizeQueue struct {
t TTY
// resizeChan receives a Size each time the user's terminal is resized.
resizeChan chan remotecommand.TerminalSize
resizeChan chan TerminalSize
stopResizing chan struct{}
}
// make sure sizeQueue implements the resize.TerminalSizeQueue interface
var _ remotecommand.TerminalSizeQueue = &sizeQueue{}
// make sure sizeQueue implements the TerminalSizeQueue interface
var _ TerminalSizeQueue = &sizeQueue{}
// monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each
// new event, it sends the current terminal size to resizeChan.
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) {
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*TerminalSize) {
// send the initial sizes
for i := range initialSizes {
if initialSizes[i] != nil {
@ -87,7 +103,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.Te
}
}
resizeEvents := make(chan remotecommand.TerminalSize, 1)
resizeEvents := make(chan TerminalSize, 1)
monitorResizeEvents(outFd, resizeEvents, s.stopResizing)
@ -118,7 +134,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.Te
// Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped.
func (s *sizeQueue) Next() *remotecommand.TerminalSize {
func (s *sizeQueue) Next() *TerminalSize {
size, ok := <-s.resizeChan
if !ok {
return nil

View File

@ -25,13 +25,12 @@ import (
"golang.org/x/sys/unix"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
)
// monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the
// terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send
// it to the resizeEvents channel. The goroutine stops when the stop channel is closed.
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
func monitorResizeEvents(fd uintptr, resizeEvents chan<- TerminalSize, stop chan struct{}) {
go func() {
defer runtime.HandleCrash()

View File

@ -20,13 +20,12 @@ import (
"time"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
)
// monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send
// it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel
// is closed.
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
func monitorResizeEvents(fd uintptr, resizeEvents chan<- TerminalSize, stop chan struct{}) {
go func() {
defer runtime.HandleCrash()

View File

@ -23,8 +23,6 @@ import (
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/moby/term"
"k8s.io/client-go/tools/remotecommand"
)
type wordWrapWriter struct {
@ -70,7 +68,7 @@ func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
}
}
func getTerminalLimitWidth(terminalSize *remotecommand.TerminalSize) uint {
func getTerminalLimitWidth(terminalSize *TerminalSize) uint {
var limit uint
switch {
case terminalSize.Width >= 120:

View File

@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: master
image: docker.io/library/redis:5.0.5-alpine
image: registry.k8s.io/e2e-test-images/redis:5.0.5-alpine
resources:
requests:
cpu: 100m

View File

@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: master
image: docker.io/library/redis:5.0.5-alpine
image: registry.k8s.io/e2e-test-images/redis:5.0.5-alpine
resources:
requests:
cpu: 100m

View File

@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: slave
image: docker.io/library/redis:5.0.5-alpine
image: registry.k8s.io/e2e-test-images/redis:5.0.5-alpine
# We are only implementing the dns option of:
# https://github.com/kubernetes/examples/blob/97c7ed0eb6555a4b667d2877f965d392e00abc45/guestbook/redis-slave/run.sh
command: [ "redis-server", "--slaveof", "redis-master", "6379" ]

View File

@ -24,7 +24,7 @@ spec:
spec:
containers:
- name: slave
image: docker.io/library/redis:5.0.5-alpine
image: registry.k8s.io/e2e-test-images/redis:5.0.5-alpine
# We are only implementing the dns option of:
# https://github.com/kubernetes/examples/blob/97c7ed0eb6555a4b667d2877f965d392e00abc45/guestbook/redis-slave/run.sh
command: [ "redis-server", "--slaveof", "redis-master", "6379" ]