From 337ad9e402297557f29c2662d30f00f22d0f288d Mon Sep 17 00:00:00 2001 From: tyzbit <3319104+tyzbit@users.noreply.github.com> Date: Sun, 12 Nov 2023 18:22:10 -0500 Subject: [PATCH 001/169] add flux plugin image reconciliation commands (#1445) --- plugins/flux.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/flux.yml b/plugins/flux.yml index d997e387f6..dd3b98404d 100644 --- a/plugins/flux.yml +++ b/plugins/flux.yml @@ -80,6 +80,28 @@ plugin: args: - -c - "flux --context $CONTEXT reconcile kustomization -n $NAMESPACE $NAME |& less" + reconcile-ir: + shortCut: Shift-R + confirm: false + description: Flux reconcile + scopes: + - imagerepositories + command: sh + background: false + args: + - -c + - "flux --context $CONTEXT reconcile image repository -n $NAMESPACE $NAME | less" + reconcile-iua: + shortCut: Shift-R + confirm: false + description: Flux reconcile + scopes: + - imageupdateautomations + command: sh + background: false + args: + - -c + - "flux --context $CONTEXT reconcile image update -n $NAMESPACE $NAME | less" trace: shortCut: Shift-A confirm: false From c64d11250907c2c48102d73f9074b536dc60d737 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:59:15 -0700 Subject: [PATCH 002/169] Bump sigs.k8s.io/yaml from 1.3.0 to 1.4.0 (#2296) Bumps [sigs.k8s.io/yaml](https://github.com/kubernetes-sigs/yaml) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/kubernetes-sigs/yaml/releases) - [Changelog](https://github.com/kubernetes-sigs/yaml/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/yaml/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/yaml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c17dd61b42..b03cdad5e2 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( k8s.io/kubectl v0.28.3 k8s.io/kubernetes v1.28.3 k8s.io/metrics v0.28.3 - sigs.k8s.io/yaml v1.3.0 + sigs.k8s.io/yaml v1.4.0 ) require ( diff --git a/go.sum b/go.sum index 8b98445d5d..e712bc3ed9 100644 --- a/go.sum +++ b/go.sum @@ -644,5 +644,5 @@ sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From aee560d82c7ae59c1a4055de2604ff9de7878576 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:59:29 -0700 Subject: [PATCH 003/169] Bump golang.org/x/text from 0.13.0 to 0.14.0 (#2295) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b03cdad5e2..ffe0ff371b 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/sahilm/fuzzy v0.1.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.13.1 k8s.io/api v0.28.3 diff --git a/go.sum b/go.sum index e712bc3ed9..4bc48a1968 100644 --- a/go.sum +++ b/go.sum @@ -535,8 +535,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 0d6805f63fe44c3baafeae2f7be38ed05fef175f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:59:46 -0700 Subject: [PATCH 004/169] Bump k8s.io/klog/v2 from 2.100.1 to 2.110.1 (#2294) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.100.1 to 2.110.1. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.100.1...v2.110.1) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ffe0ff371b..7ae9eb40a6 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( k8s.io/apimachinery v0.28.3 k8s.io/cli-runtime v0.28.3 k8s.io/client-go v0.28.3 - k8s.io/klog/v2 v2.100.1 + k8s.io/klog/v2 v2.110.1 k8s.io/kubectl v0.28.3 k8s.io/kubernetes v1.28.3 k8s.io/metrics v0.28.3 diff --git a/go.sum b/go.sum index 4bc48a1968..e4b479dc3c 100644 --- a/go.sum +++ b/go.sum @@ -135,7 +135,6 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -622,8 +621,8 @@ k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/kubectl v0.28.3 h1:H1Peu1O3EbN9zHkJCcvhiJ4NUj6lb88sGPO5wrWIM6k= From 702f6f01b2144b973ab2c4c1a2e6faddc8aef7a0 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sun, 19 Nov 2023 08:43:39 -0700 Subject: [PATCH 005/169] Clean up items... (#2303) * Update licence header * Add linter workflow * Add pkg builds --- .github/ISSUE_TEMPLATE/bug_report.md | 9 +- .github/ISSUE_TEMPLATE/feature_request.md | 3 +- .github/workflows/lint.yml | 24 + .github/workflows/test.yml | 4 +- .golangci.yml | 818 +++------------------- .goreleaser.yml | 32 +- cmd/info.go | 3 + cmd/info_test.go | 3 + cmd/root.go | 3 + cmd/version.go | 3 + internal/client/client.go | 3 + internal/client/client_test.go | 3 + internal/client/config.go | 3 + internal/client/config_test.go | 3 + internal/client/errors.go | 3 + internal/client/gvr.go | 3 + internal/client/gvr_test.go | 3 + internal/client/helper_test.go | 3 + internal/client/helpers.go | 3 + internal/client/metrics.go | 3 + internal/client/metrics_test.go | 3 + internal/client/types.go | 3 + internal/color/colorize.go | 3 + internal/color/colorize_test.go | 3 + internal/config/alias.go | 3 + internal/config/alias_test.go | 3 + internal/config/bench.go | 3 + internal/config/bench_test.go | 3 + internal/config/cluster.go | 3 + internal/config/cluster_test.go | 3 + internal/config/config.go | 3 + internal/config/config_test.go | 3 + internal/config/feature.go | 3 + internal/config/flags.go | 3 + internal/config/helpers.go | 3 + internal/config/helpers_test.go | 3 + internal/config/hotkey.go | 3 + internal/config/hotkey_test.go | 3 + internal/config/k9s.go | 3 + internal/config/k9s_test.go | 3 + internal/config/logger.go | 3 + internal/config/logger_test.go | 3 + internal/config/mock_connection_test.go | 3 + internal/config/mock_kubesettings_test.go | 3 + internal/config/ns.go | 3 + internal/config/ns_test.go | 3 + internal/config/plugin.go | 5 +- internal/config/plugin_test.go | 3 + internal/config/shell_pod.go | 3 + internal/config/styles.go | 3 + internal/config/styles_test.go | 3 + internal/config/threshold.go | 3 + internal/config/threshold_test.go | 3 + internal/config/view.go | 3 + internal/config/view_test.go | 3 + internal/config/views.go | 3 + internal/config/views_test.go | 3 + internal/dao/alias.go | 3 + internal/dao/alias_test.go | 3 + internal/dao/benchmark.go | 6 + internal/dao/benchmark_test.go | 3 + internal/dao/cluster.go | 3 + internal/dao/container.go | 3 + internal/dao/container_test.go | 3 + internal/dao/context.go | 3 + internal/dao/crd.go | 3 + internal/dao/cronjob.go | 12 +- internal/dao/cruiser.go | 3 + internal/dao/cruiser_test.go | 3 + internal/dao/describe.go | 3 + internal/dao/dir.go | 3 + internal/dao/dir_test.go | 3 + internal/dao/dp.go | 6 + internal/dao/ds.go | 3 + internal/dao/generic.go | 6 + internal/dao/helm.go | 6 + internal/dao/helpers.go | 3 + internal/dao/helpers_test.go | 3 + internal/dao/job.go | 3 + internal/dao/log_item.go | 6 + internal/dao/log_item_test.go | 3 + internal/dao/log_items.go | 3 + internal/dao/log_items_test.go | 3 + internal/dao/log_options.go | 3 + internal/dao/log_options_test.go | 3 + internal/dao/node.go | 6 + internal/dao/non_resource.go | 3 + internal/dao/ns.go | 3 + internal/dao/ofaas.go | 3 + internal/dao/patch.go | 3 + internal/dao/patch_test.go | 3 + internal/dao/pod.go | 6 + internal/dao/pod_test.go | 3 + internal/dao/popeye.go | 3 + internal/dao/port_forward.go | 3 + internal/dao/port_forward_test.go | 3 + internal/dao/port_forwarder.go | 3 + internal/dao/pulse.go | 3 + internal/dao/rbac.go | 3 + internal/dao/rbac_policy.go | 3 + internal/dao/rbac_policy_test.go | 3 + internal/dao/rbac_subject.go | 3 + internal/dao/reference.go | 3 + internal/dao/registry.go | 3 + internal/dao/registry_test.go | 3 + internal/dao/resource.go | 3 + internal/dao/rest_mapper.go | 3 + internal/dao/rs.go | 3 + internal/dao/screen_dump.go | 3 + internal/dao/sts.go | 3 + internal/dao/svc.go | 3 + internal/dao/table.go | 3 + internal/dao/types.go | 3 + internal/health/check.go | 3 + internal/health/check_test.go | 3 + internal/health/types.go | 3 + internal/keys.go | 3 + internal/model/cluster.go | 3 + internal/model/cluster_info.go | 3 + internal/model/cluster_info_test.go | 3 + internal/model/cmd_buff.go | 3 + internal/model/cmd_buff_test.go | 3 + internal/model/describe.go | 3 + internal/model/fish_buff.go | 3 + internal/model/fish_buff_test.go | 3 + internal/model/flash.go | 3 + internal/model/flash_test.go | 3 + internal/model/helpers.go | 3 + internal/model/helpers_test.go | 3 + internal/model/hint.go | 3 + internal/model/hint_test.go | 3 + internal/model/history.go | 3 + internal/model/history_test.go | 3 + internal/model/log.go | 3 + internal/model/log_int_test.go | 3 + internal/model/log_test.go | 3 + internal/model/menu_hint.go | 3 + internal/model/menu_hint_test.go | 3 + internal/model/mock_clustermeta_test.go | 3 + internal/model/mock_connection_test.go | 3 + internal/model/mock_metricsserver_test.go | 3 + internal/model/pulse.go | 3 + internal/model/pulse_health.go | 3 + internal/model/registry.go | 3 + internal/model/semver.go | 3 + internal/model/semver_test.go | 3 + internal/model/stack.go | 3 + internal/model/stack_test.go | 3 + internal/model/table.go | 3 + internal/model/table_int_test.go | 3 + internal/model/table_test.go | 3 + internal/model/text.go | 3 + internal/model/text_test.go | 3 + internal/model/tree.go | 3 + internal/model/types.go | 3 + internal/model/values.go | 3 + internal/model/yaml.go | 3 + internal/model/yaml_test.go | 3 + internal/perf/benchmark.go | 6 +- internal/port/ann.go | 3 + internal/port/ann_test.go | 3 + internal/port/co_portspec.go | 3 + internal/port/co_portspec_test.go | 3 + internal/port/pf.go | 3 + internal/port/pf_test.go | 3 + internal/port/pfs.go | 3 + internal/port/pfs_test.go | 3 + internal/port/tunnel.go | 3 + internal/port/tunnel_test.go | 3 + internal/render/alias.go | 3 + internal/render/alias_test.go | 3 + internal/render/base.go | 3 + internal/render/benchmark.go | 3 + internal/render/benchmark_int_test.go | 3 + internal/render/color.go | 3 + internal/render/color_test.go | 3 + internal/render/container.go | 3 + internal/render/container_test.go | 3 + internal/render/context.go | 3 + internal/render/context_test.go | 3 + internal/render/cr.go | 3 + internal/render/cr_test.go | 3 + internal/render/crb.go | 3 + internal/render/crb_test.go | 3 + internal/render/crd.go | 3 + internal/render/crd_test.go | 3 + internal/render/cronjob.go | 3 + internal/render/cronjob_test.go | 3 + internal/render/delta.go | 3 + internal/render/delta_test.go | 3 + internal/render/dir.go | 3 + internal/render/dp.go | 3 + internal/render/dp_test.go | 3 + internal/render/ds.go | 3 + internal/render/ds_test.go | 3 + internal/render/ep.go | 3 + internal/render/ep_test.go | 3 + internal/render/ev.go | 3 + internal/render/ev_test.go | 3 + internal/render/generic.go | 6 +- internal/render/generic_test.go | 3 + internal/render/header.go | 3 + internal/render/header_test.go | 3 + internal/render/helm.go | 3 + internal/render/helpers.go | 3 + internal/render/helpers_test.go | 3 + internal/render/job.go | 3 + internal/render/job_test.go | 3 + internal/render/node.go | 3 + internal/render/node_test.go | 3 + internal/render/np.go | 3 + internal/render/np_test.go | 3 + internal/render/ns.go | 3 + internal/render/ns_test.go | 3 + internal/render/ofaas.go | 3 + internal/render/ofaas_test.go | 3 + internal/render/pdb.go | 3 + internal/render/pdb_test.go | 3 + internal/render/pod.go | 3 + internal/render/pod_test.go | 3 + internal/render/policy.go | 3 + internal/render/policy_test.go | 3 + internal/render/popeye.go | 3 + internal/render/port_forward_test.go | 3 + internal/render/portforward.go | 3 + internal/render/pv.go | 3 + internal/render/pv_test.go | 3 + internal/render/pvc.go | 3 + internal/render/pvc_test.go | 3 + internal/render/rbac.go | 3 + internal/render/reference.go | 3 + internal/render/reference_test.go | 3 + internal/render/render_test.go | 3 + internal/render/ro.go | 3 + internal/render/ro_test.go | 3 + internal/render/rob.go | 3 + internal/render/rob_test.go | 3 + internal/render/row.go | 3 + internal/render/row_event.go | 3 + internal/render/row_event_test.go | 3 + internal/render/row_test.go | 3 + internal/render/rs.go | 3 + internal/render/rs_test.go | 3 + internal/render/sa.go | 3 + internal/render/sa_test.go | 3 + internal/render/sc.go | 3 + internal/render/sc_test.go | 3 + internal/render/screen_dump.go | 3 + internal/render/screen_dump_test.go | 3 + internal/render/sts.go | 3 + internal/render/sts_test.go | 3 + internal/render/subject.go | 3 + internal/render/svc.go | 3 + internal/render/svc_test.go | 3 + internal/render/table_data.go | 3 + internal/render/table_data_test.go | 3 + internal/render/types.go | 3 + internal/tchart/component.go | 3 + internal/tchart/component_int_test.go | 3 + internal/tchart/component_test.go | 3 + internal/tchart/dot_matrix.go | 3 + internal/tchart/dot_matrix_test.go | 3 + internal/tchart/gauge.go | 3 + internal/tchart/gauge_int_test.go | 3 + internal/tchart/gauge_test.go | 3 + internal/tchart/sparkline.go | 3 + internal/tchart/sparkline_int_test.go | 3 + internal/ui/action.go | 3 + internal/ui/action_test.go | 3 + internal/ui/app.go | 3 + internal/ui/app_test.go | 3 + internal/ui/config.go | 3 + internal/ui/config_test.go | 3 + internal/ui/crumbs.go | 3 + internal/ui/crumbs_test.go | 3 + internal/ui/deltas.go | 3 + internal/ui/deltas_test.go | 3 + internal/ui/dialog/confirm.go | 3 + internal/ui/dialog/confirm_test.go | 3 + internal/ui/dialog/delete.go | 3 + internal/ui/dialog/delete_test.go | 3 + internal/ui/dialog/error.go | 3 + internal/ui/dialog/error_test.go | 3 + internal/ui/dialog/transfer.go | 3 + internal/ui/flash.go | 3 + internal/ui/flash_test.go | 3 + internal/ui/indicator.go | 3 + internal/ui/indicator_test.go | 3 + internal/ui/key.go | 3 + internal/ui/logo.go | 3 + internal/ui/logo_test.go | 3 + internal/ui/menu.go | 3 + internal/ui/menu_test.go | 3 + internal/ui/padding.go | 3 + internal/ui/padding_test.go | 3 + internal/ui/pages.go | 3 + internal/ui/pages_test.go | 3 + internal/ui/prompt.go | 3 + internal/ui/prompt_test.go | 6 +- internal/ui/select_table.go | 3 + internal/ui/splash.go | 3 + internal/ui/splash_test.go | 3 + internal/ui/table.go | 3 + internal/ui/table_helper.go | 3 + internal/ui/table_helper_test.go | 3 + internal/ui/table_test.go | 3 + internal/ui/tree.go | 3 + internal/ui/types.go | 3 + internal/view/actions.go | 3 + internal/view/actions_test.go | 3 + internal/view/alias.go | 3 + internal/view/alias_test.go | 3 + internal/view/app.go | 3 + internal/view/app_test.go | 3 + internal/view/benchmark.go | 3 + internal/view/browser.go | 3 + internal/view/cluster_info.go | 3 + internal/view/cm.go | 3 + internal/view/cm_test.go | 3 + internal/view/command.go | 3 + internal/view/container.go | 3 + internal/view/container_test.go | 3 + internal/view/context.go | 3 + internal/view/context_test.go | 3 + internal/view/cow.go | 3 + internal/view/cronjob.go | 3 + internal/view/details.go | 3 + internal/view/dir.go | 3 + internal/view/dir_int_test.go | 3 + internal/view/dir_test.go | 3 + internal/view/dp.go | 3 + internal/view/dp_test.go | 3 + internal/view/drain_dialog.go | 3 + internal/view/ds.go | 3 + internal/view/ds_test.go | 3 + internal/view/env.go | 3 + internal/view/env_test.go | 3 + internal/view/event.go | 3 + internal/view/exec.go | 3 + internal/view/group.go | 3 + internal/view/helm.go | 3 + internal/view/help.go | 3 + internal/view/help_test.go | 3 + internal/view/helpers.go | 3 + internal/view/helpers_test.go | 3 + internal/view/image_extender.go | 3 + internal/view/job.go | 3 + internal/view/live_view.go | 3 + internal/view/live_view_test.go | 3 + internal/view/log.go | 3 + internal/view/log_indicator.go | 3 + internal/view/log_indicator_test.go | 3 + internal/view/log_int_test.go | 3 + internal/view/log_test.go | 3 + internal/view/logger.go | 3 + internal/view/logs_extender.go | 3 + internal/view/node.go | 3 + internal/view/ns.go | 3 + internal/view/ns_test.go | 3 + internal/view/ofaas.go | 3 + internal/view/page_stack.go | 3 + internal/view/pf.go | 3 + internal/view/pf_dialog.go | 3 + internal/view/pf_dialog_test.go | 3 + internal/view/pf_extender.go | 3 + internal/view/pf_extender_test.go | 3 + internal/view/pf_test.go | 3 + internal/view/picker.go | 3 + internal/view/pod.go | 3 + internal/view/pod_int_test.go | 3 + internal/view/pod_test.go | 3 + internal/view/policy.go | 3 + internal/view/popeye.go | 3 + internal/view/priorityclass.go | 3 + internal/view/priorityclass_test.go | 3 + internal/view/pulse.go | 3 + internal/view/pvc.go | 3 + internal/view/pvc_test.go | 3 + internal/view/rbac.go | 3 + internal/view/rbac_test.go | 3 + internal/view/reference.go | 3 + internal/view/reference_test.go | 3 + internal/view/registrar.go | 3 + internal/view/restart_extender.go | 3 + internal/view/rs.go | 3 + internal/view/sa.go | 3 + internal/view/sanitizer.go | 3 + internal/view/scale_extender.go | 3 + internal/view/screen_dump.go | 3 + internal/view/screen_dump_test.go | 3 + internal/view/secret.go | 3 + internal/view/secret_test.go | 3 + internal/view/sts.go | 3 + internal/view/sts_test.go | 3 + internal/view/svc.go | 3 + internal/view/svc_test.go | 3 + internal/view/table.go | 3 + internal/view/table_helper.go | 3 + internal/view/table_int_test.go | 3 + internal/view/types.go | 3 + internal/view/user.go | 3 + internal/view/xray.go | 3 + internal/view/yaml.go | 3 + internal/view/yaml_test.go | 3 + internal/watch/factory.go | 3 + internal/watch/forwarders.go | 3 + internal/watch/forwarders_test.go | 3 + internal/watch/helper.go | 3 + internal/xray/container.go | 3 + internal/xray/container_test.go | 3 + internal/xray/dp.go | 3 + internal/xray/dp_test.go | 3 + internal/xray/ds.go | 3 + internal/xray/ds_test.go | 3 + internal/xray/generic.go | 3 + internal/xray/generic_test.go | 3 + internal/xray/ns.go | 3 + internal/xray/ns_test.go | 3 + internal/xray/pod.go | 3 + internal/xray/pod_test.go | 3 + internal/xray/rs.go | 3 + internal/xray/rs_test.go | 3 + internal/xray/sa.go | 3 + internal/xray/sa_test.go | 3 + internal/xray/section.go | 3 + internal/xray/sts.go | 3 + internal/xray/sts_test.go | 3 + internal/xray/svc.go | 3 + internal/xray/svc_test.go | 3 + internal/xray/tree_node.go | 3 + internal/xray/tree_node_test.go | 3 + main.go | 9 + 432 files changed, 1462 insertions(+), 753 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bbc69f3ac9..4a65fcef0b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -14,12 +14,12 @@ assignees: ''

- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -32,9 +32,10 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Versions (please complete the following information):** - - OS: [e.g. OSX] - - K9s: [e.g. 0.1.0] - - K8s: [e.g. 1.11.0] + +- OS: [e.g. OSX] +- K9s: [e.g. 0.1.0] +- K8s: [e.g. 1.11.0] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 30d47509ee..397760147b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -13,9 +13,8 @@ assignees: ''

- **Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..ccfa529cf6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: K9s Lint + +on: + pull_request: + branches: [ main ] + +jobs: + golangci: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@4.1.1 + + - name: Install Go + uses: actions/setup-go@v4.1.0 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + + - name: Lint + uses: golangci/golangci-lint-action@3.7.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: github-pr-check \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6133c17697..823afddffc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: K9s Checks +name: K9s Test on: workflow_dispatch: @@ -13,7 +13,7 @@ on: - master jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4.1.1 diff --git a/.golangci.yml b/.golangci.yml index a13a58d5c3..78a2a5885f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,3 @@ -# This file contains all available configuration options -# with their default values. - # options for analysis running run: # default concurrency is a available CPU number @@ -15,33 +12,17 @@ run: # include test files or not, default is true tests: true - # list of build tags, all linters use it. Default is empty list. - build-tags: - - mytag - - # which dirs to skip: issues from them won't be reported; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but default dirs are skipped independently - # from this option's value (see skip-dirs-use-default). - # "/" will be replaced by current OS file path separator to properly work - # on Windows. - skip-dirs: - - src/external_libs - - autogenerated_by_my_lib - # default is true. Enables skipping of directories: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ skip-dirs-use-default: true - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - # "/" will be replaced by current OS file path separator to properly work - # on Windows. - skip-files: - - ".*\\.my\\.go$" - - lib/bad.go + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # skip-dirs: + # - ^test.* # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": # If invoked with -mod=readonly, the go command is disallowed from the implicit @@ -51,732 +32,97 @@ run: # If invoked with -mod=vendor, the go command assumes that the vendor # directory holds the correct copies of dependencies and ignores # the dependency descriptions in go.mod. - # modules-download-mode: readonly|vendor|mod - - # Allow multiple parallel golangci-lint instances running. - # If false (default) - golangci-lint acquires file lock on start. - allow-parallel-runners: false - -# output configuration options -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions - # default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - - # make issues output unique by line, default is true - uniq-by-line: true - - # add a prefix to the output file references; default is no prefix - path-prefix: "" + modules-download-mode: readonly - # sorts results by: filepath, line and column - sort-results: false + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + skip-files: + # - ".*\\.my\\.go$" + # - lib/bad.go # all available settings of specific linters linters-settings: - cyclop: - # the maximal code complexity to report - max-complexity: 20 - # the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled (default 0.0) - package-average: 0.0 - # should ignore tests (default false) - skip-tests: false - - dogsled: - # checks assignments with too many blank identifiers; default is 2 - max-blank-identifiers: 2 - - dupl: - # tokens count to trigger issue, 150 by default - threshold: 100 - - errcheck: - # report about not checking of errors in type assertions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,io/ioutil:^Read.* - - # path to a file containing a list of functions to exclude from checking - # see https://github.com/kisielk/errcheck#excluding-functions for details - # exclude: /path/to/file.txt - - errorlint: - # Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats - errorf: true - # Check for plain type assertions and type switches - asserts: true - # Check for plain error comparisons - comparison: true - - exhaustive: - # check switch statements in generated files also - check-generated: false - # indicates that switch statements are to be considered exhaustive if a - # 'default' case is present, even if all enum members aren't listed in the - # switch - default-signifies-exhaustive: false - - exhaustivestruct: - # Struct Patterns is list of expressions to match struct packages and names - # The struct packages have the form example.com/package.ExampleStruct - # The matching patterns can use matching syntax from https://pkg.go.dev/path#Match - # If this list is empty, all structs are tested. - struct-patterns: - - "*.Test" - - "example.com/package.ExampleStruct" - - forbidigo: - # Forbid the following identifiers (identifiers are written using regexp): - forbid: - - ^print.*$ - - 'fmt\.Print.*' - # Exclude godoc examples from forbidigo checks. Default is true. - exclude_godoc_examples: false - - funlen: - lines: 100 - statements: 40 - - gci: - # put imports beginning with prefix after 3rd-party packages; - # only support one prefix - # if not set, use goimports.local-prefixes - local-prefixes: github.com/org/project - - gocognit: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 - - nestif: - # minimal complexity of if statements to report, 5 by default - min-complexity: 4 - - goconst: - # minimal length of string constant, 3 by default - min-len: 3 - # minimal occurrences count to trigger, 3 by default - min-occurrences: 3 - - gocritic: - # Which checks should be enabled; can't be combined with 'disabled-checks'; - # See https://go-critic.github.io/overview#checks-overview - # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` - # By default list of stable checks is used. - # enabled-checks: - # - rangeValCopy - - # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty - disabled-checks: - - regexpMust - - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. - # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-tags: - - performance - disabled-tags: - - experimental - - # Settings passed to gocritic. - # The settings key is the name of a supported gocritic checker. - # The list of supported checkers can be find in https://go-critic.github.io/overview. - settings: - captLocal: # must be valid enabled check name - # whether to restrict checker to params only (default true) - paramsOnly: true - elseif: - # whether to skip balanced if-else pairs (default true) - skipBalanced: true - hugeParam: - # size in bytes that makes the warning trigger (default 80) - sizeThreshold: 80 - # nestingReduce: - # # min number of statements inside a branch to trigger a warning (default 5) - # bodyWidth: 5 - rangeExprCopy: - # size in bytes that makes the warning trigger (default 512) - sizeThreshold: 512 - # whether to check test functions (default true) - skipTestFuncs: true - rangeValCopy: - # size in bytes that makes the warning trigger (default 128) - sizeThreshold: 32 - # whether to check test functions (default true) - skipTestFuncs: true - # ruleguard: - # path to a gorules file for the ruleguard checker - # rules: "" - # truncateCmp: - # # whether to skip int/uint/uintptr types (default true) - # skipArchDependent: true - underef: - # whether to skip (*x).method() calls where x is a pointer receiver (default true) - skipRecvDeref: true - # unnamedResult: - # # whether to check exported functions - # checkExported: true - gocyclo: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 20 - - godot: - # comments to be checked: `declarations`, `toplevel`, or `all` - scope: declarations - # list of regexps for excluding particular comment lines from check - exclude: - # example: exclude comments which contain numbers - # - '[0-9]+' - # check that each sentence starts with a capital letter - capital: false - - godox: - # report any comments starting with keywords, this is useful for TODO or FIXME comments that - # might be left in the code accidentally and should be resolved before merging - keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting - - NOTE - - OPTIMIZE # marks code that should be optimized before merging - - HACK # marks hack-arounds that should be removed before merging - - gofmt: - # simplify code: gofmt with `-s` option, true by default - simplify: true - - gofumpt: - # Choose whether or not to use the extra rules that are disabled - # by default - extra-rules: false - - # goheader: - # values: - # const: - # define here const type values in format k:v, for example: - # COMPANY: MY COMPANY - # regexp: - # define here regexp type values, for example - # AUTHOR: .*@mycompany\.com - # template:# |- - # put here copyright header template for source code files, for example: - # Note: {{ YEAR }} is a builtin value that returns the year relative to the current machine time. - # - # {{ AUTHOR }} {{ COMPANY }} {{ YEAR }} - # SPDX-License-Identifier: Apache-2.0 - # 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. - # template-path: - # also as alternative of directive 'template' you may put the path to file with the template source - - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - local-prefixes: github.com/org/project - - golint: - # minimal confidence for issues, default is 0.8 - min-confidence: 0.8 - - gomnd: - settings: - mnd: - # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. - checks: - - argument - - case - - condition - - operation - - return - - assign - # ignored-numbers: 1000 - # ignored-files: magic_.*.go - # ignored-functions: math.* - - gomoddirectives: - # Allow local `replace` directives. Default is false. - replace-local: false - # List of allowed `replace` directives. Default is empty. - replace-allow-list: - - launchpad.net/gocheck - # Allow to not explain why the version has been retracted in the `retract` directives. Default is false. - retract-allow-no-explanation: false - # Forbid the use of the `exclude` directives. Default is false. - exclude-forbidden: false - - gomodguard: - # allowed: - # modules: # List of allowed modules - # - gopkg.in/yaml.v2 - # domains:# List of allowed module domains - # - golang.org - # blocked: - # modules: # List of blocked modules - # - github.com/uudashr/go-module: # Blocked module - # recommendations: # Recommended modules that should be used instead (Optional) - # - golang.org/x/mod - # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) - # versions:# List of blocked module version constraints - # - github.com/mitchellh/go-homedir: # Blocked module with version constraint - # version: "< 1.1.0" # Version constraint, see https://github.com/Masterminds/semver#basic-comparisons - # reason: "testing if blocked version constraint works." # Reason why the version constraint exists. (Optional) - local_replace_directives: false # Set to true to raise lint issues for packages that are loaded from a local path via replace directive - - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - includes: - - G401 - - G306 - - G101 - # To specify a set of rules to explicitly exclude. - # Available rules: https://github.com/securego/gosec#available-rules - excludes: - - G204 - # To specify the configuration of rules. - # The configuration of rules is not fully documented by gosec: - # https://github.com/securego/gosec#configuration - # https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/rules/rulelist.go#L60-L102 - config: - G306: "0600" - G101: - pattern: "(?i)example" - ignore_entropy: false - entropy_threshold: "80.0" - per_char_threshold: "3.0" - truncate: "32" - - gosimple: - # Select the Go version to target. The default is '1.13'. - go: "1.20" - # https://staticcheck.io/docs/options#checks - checks: ["all"] + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 35 govet: - # report about shadowed variables - check-shadowing: true - - # settings per analyzer - settings: - printf: # analyzer name, run `go tool vet help` to see all analyzers - funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - - # enable or disable analyzers by name - # run `go tool vet help` to see all analyzers - # enable: - # - atomicalign - # enable-all: true - # disable: - # - shadow - # disable-all: false - - depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/sirupsen/logrus - packages-with-error-message: - # specify an error message to output when a blacklisted package is used - - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" - - ifshort: - # Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax. - # Has higher priority than max-decl-chars. - max-decl-lines: 1 - # Maximum length of variable declaration measured in number of characters, after which linter won't suggest using short syntax. - max-decl-chars: 30 - - importas: - # if set to `true`, force to use alias. - no-unaliased: true - # List of aliases - alias: - # using `servingv1` alias for `knative.dev/serving/pkg/apis/serving/v1` package - - pkg: knative.dev/serving/pkg/apis/serving/v1 - alias: servingv1 - # using `autoscalingv1alpha1` alias for `knative.dev/serving/pkg/apis/autoscaling/v1alpha1` package - - pkg: knative.dev/serving/pkg/apis/autoscaling/v1alpha1 - alias: autoscalingv1alpha1 - # You can specify the package path by regular expression, - # and alias by regular expression expansion syntax like below. - # see https://github.com/julz/importas#use-regular-expression for details - - pkg: knative.dev/serving/pkg/apis/(\w+)/(v[\w\d]+) - alias: $1$2 - - lll: - # max line length, lines longer will be reported. Default is 120. - # '\t' is counted as 1 character by default, and can be changed with the tab-width option - line-length: 120 - # tab width in spaces. Default to 1. - tab-width: 1 - - makezero: - # Allow only slices initialized with a length of zero. Default is false. - always: false - - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - ignore-words: - - someword - - nakedret: - # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 - max-func-lines: 30 - - prealloc: - # XXX: we don't recommend using this linter before doing performance profiling. - # For most programs usage of prealloc will be a premature optimization. - - # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. - # True by default. - simple: true - range-loops: true # Report preallocation suggestions on range loops, true by default - for-loops: false # Report preallocation suggestions on for loops, false by default - - promlinter: - # Promlinter cannot infer all metrics name in static analysis. - # Enable strict mode will also include the errors caused by failing to parse the args. - strict: false - # Please refer to https://github.com/yeya24/promlinter#usage for detailed usage. - disabled-linters: - # - "Help" - # - "MetricUnits" - # - "Counter" - # - "HistogramSummaryReserved" - # - "MetricTypeInName" - # - "ReservedChars" - # - "CamelCase" - # - "lintUnitAbbreviations" - - predeclared: - # comma-separated list of predeclared identifiers to not report on - ignore: "" - # include method names and field names (i.e., qualified names) in checks - q: false - - nolintlint: - # Enable to ensure that nolint directives are all used. Default is true. - allow-unused: false - # Disable to ensure that nolint directives don't have a leading space. Default is true. - allow-leading-space: true - # Exclude following linters from requiring an explanation. Default is []. - allow-no-explanation: [] - # Enable to require an explanation of nonzero length after each nolint directive. Default is false. - require-explanation: true - # Enable to require nolint directives to mention the specific linter being suppressed. Default is false. - require-specific: true - - rowserrcheck: - packages: - - github.com/jmoiron/sqlx - - revive: - # see https://github.com/mgechev/revive#available-rules for details. - ignore-generated-header: true - severity: warning - rules: - - name: indent-error-flow - severity: warning - - name: add-constant - severity: warning - arguments: - - maxLitCount: "3" - allowStrs: '""' - allowInts: "0,1,2" - allowFloats: "0.0,0.,1.0,1.,2.0,2." - + enable: + - nilness + goimports: + local-prefixes: github.com/cilium/cilium staticcheck: - # Select the Go version to target. The default is '1.13'. go: "1.20" - # https://staticcheck.io/docs/options#checks - checks: ["all"] - - stylecheck: - # Select the Go version to target. The default is '1.13'. - go: "1.20" - # https://staticcheck.io/docs/options#checks - checks: - ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] - # https://staticcheck.io/docs/options#dot_import_whitelist - dot-import-whitelist: - - fmt - # https://staticcheck.io/docs/options#initialisms - initialisms: - [ - "ACL", - "API", - "ASCII", - "CPU", - "CSS", - "DNS", - "EOF", - "GUID", - "HTML", - "HTTP", - "HTTPS", - "ID", - "IP", - "JSON", - "QPS", - "RAM", - "RPC", - "SLA", - "SMTP", - "SQL", - "SSH", - "TCP", - "TLS", - "TTL", - "UDP", - "UI", - "GID", - "UID", - "UUID", - "URI", - "URL", - "UTF8", - "VM", - "XML", - "XMPP", - "XSRF", - "XSS", - ] - # https://staticcheck.io/docs/options#http_status_code_whitelist - http-status-code-whitelist: ["200", "400", "404", "500"] - - tagliatelle: - # check the struck tag name case - case: - # use the struct field name to check the name of the struct tag - use-field-name: true - rules: - # any struct tag type can be used. - # support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` - json: camel - yaml: camel - xml: camel - bson: camel - avro: snake - mapstructure: kebab - - testpackage: - # regexp pattern to skip files - skip-regexp: (export|internal)_test\.go - - thelper: - # The following configurations enable all checks. It can be omitted because all checks are enabled by default. - # You can enable only required checks deleting unnecessary checks. - test: - first: true - name: true - begin: true - benchmark: - first: true - name: true - begin: true - tb: - first: true - name: true - begin: true - - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - unused: - # Select the Go version to target. The default is '1.13'. go: "1.20" + goheader: + values: + regexp: + PROJECT: 'K9s' + template: |- + SPDX-License-Identifier: Apache-2.0 + Copyright Authors of {{ PROJECT }} + gosec: + includes: + - G402 + gomodguard: + blocked: + modules: + - github.com/miekg/dns: + recommendations: + - github.com/cilium/dns + reason: "use the cilium fork directly to avoid replace directives in go.mod, see https://github.com/cilium/cilium/pull/27582" + - gopkg.in/check.v1: + recommendations: + - testing + - github.com/stretchr/testify/assert + reason: "gocheck has been deprecated, see https://docs.cilium.io/en/latest/contributing/testing/unit/#migrating-tests-off-of-gopkg-in-check-v1" + - go.uber.org/multierr: + recommendations: + - errors + reason: "Go 1.20+ has support for combining multiple errors, see https://go.dev/doc/go1.20#errors" - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - - wrapcheck: - # An array of strings that specify substrings of signatures to ignore. - # If this set, it will override the default set of ignored signatures. - # See https://github.com/tomarrell/wrapcheck#configuration for more information. - ignoreSigs: - - .Errorf( - - errors.New( - - errors.Unwrap( - - .Wrap( - - .Wrapf( - - .WithMessage( - - wsl: - # See https://github.com/bombsimon/wsl/blob/master/doc/configuration.md for - # documentation of available settings. These are the defaults for - # `golangci-lint`. - allow-assign-and-anything: false - allow-assign-and-call: true - allow-cuddle-declarations: false - allow-multiline-assign: true - allow-separated-leading-comment: false - allow-trailing-comment: false - force-case-trailing-whitespace: 0 - force-err-cuddling: false - force-short-decl-cuddling: false - strict-append: true - - # The custom section can be used to define linter plugins to be loaded at runtime. - # See README doc for more info. - # custom: - # # Each custom linter should have a unique name. - # example: - # # The path to the plugin *.so. Can be absolute or local. Required for each custom linter - # path: /path/to/example.so - # # The description of the linter. Optional, just for documentation purposes. - # description: This is an example usage of a plugin linter. - # # Intended to point to the repo location of the linter. Optional, just for documentation purposes. - # original-url: github.com/golangci/example-linter +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - linters: [staticcheck] + text: "SA1019" # this is rule for deprecated method + - linters: [staticcheck] + text: "SA9003: empty branch" + - linters: [staticcheck] + text: "SA2001: empty critical section" + - linters: [goerr113] + text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text") + # Skip goimports check on generated files + - path: \\.(generated\\.deepcopy|pb)\\.go$ + linters: + - goimports + # Skip goheader check on files imported and modified from upstream k8s + - path: "pkg/ipam/(cidrset|service)/.+\\.go" + linters: + - goheader linters: - # disable-all: true + disable-all: true enable: - - megacheck + - goerr113 + - gofmt + - goimports - govet - - funlen + - ineffassign + - misspell + - staticcheck + - unused + - goheader + - gosec + - gomodguard + - gosimple + - errcheck - gocyclo - # - fieldalignment + - gosec + - gosimple + - misspell - prealloc - typecheck - # enable-all: true - disable: - - gosec - # - maligned - # - prealloc - presets: - - bugs - - unused - fast: false - -issues: - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - exclude: - - abcdef - - # Excluding configuration per-path, per-linter, per-text and per-source - exclude-rules: - # Exclude some linters from running on tests files. - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - funlen - - goconst - - gocognit - - # Exclude known linters from partially hard-vendored code, - # which is impossible to exclude via "nolint" comments. - - path: internal/hmac/ - text: "weak cryptographic primitive" - linters: - - gosec - - # Exclude some staticcheck messages - - linters: - - staticcheck - text: "SA9003:" - - # Exclude lll issues for long lines with go:generate - - linters: - - lll - source: "^//go:generate " - - # Independently from option `exclude` we use default exclude patterns, - # it can be disabled by this option. To list all - # excluded by default patterns execute `golangci-lint run --help`. - # Default value for this option is true. - exclude-use-default: false - - # The default value is false. If set to true exclude and exclude-rules - # regular expressions become case sensitive. - exclude-case-sensitive: false - - # The list of ids of default excludes to include or disable. By default it's empty. - include: - - EXC0002 # disable excluding of issues about comments from golint - - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-issues-per-linter: 0 - - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. - max-same-issues: 0 - - # Show only new issues: if there are unstaged changes or untracked files, - # only those changes are analyzed, else only changes in HEAD~ are analyzed. - # It's a super-useful option for integration of golangci-lint into existing - # large codebase. It's not practical to fix all existing issues at the moment - # of integration: much better don't allow issues in new code. - # Default is false. - new: false - - # Show only new issues created after git revision `REV` - # new-from-rev: REV - - # Show only new issues created in git patch with set file path. - # new-from-patch: path/to/patch/file - - # Fix found issues (if it's supported by the linter) - fix: true - -severity: - # Default value is empty string. - # Set the default severity for issues. If severity rules are defined and the issues - # do not match or no severity is provided to the rule this will be the default - # severity applied. Severities should match the supported severity names of the - # selected out format. - # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity - # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity - # - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message - default-severity: error - - # The default value is false. - # If set to true severity-rules regular expressions become case sensitive. - case-sensitive: false - - # Default value is empty list. - # When a list of severity rules are provided, severity information will be added to lint - # issues. Severity rules have the same filtering capability as exclude rules except you - # are allowed to specify one matcher per severity rule. - # Only affects out formats that support setting severity information. - rules: - - linters: - - dupl - severity: info diff --git a/.goreleaser.yml b/.goreleaser.yml index e19e0d4d67..dc7a1d2c56 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -23,7 +23,10 @@ builds: flags: - -trimpath ldflags: - - -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}} -X github.com/derailed/k9s/cmd.commit={{.Commit}} -X github.com/derailed/k9s/cmd.date={{.Date}} + - -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}} + - -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}} + - -s -w -X github.com/derailed/k9s/cmd.date={{.Date}} + archives: - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" replacements: @@ -36,10 +39,13 @@ archives: format_overrides: - goos: windows format: zip + checksum: name_template: "checksums.sha256" + snapshot: name_template: "{{ .Tag }}-next" + changelog: sort: asc filters: @@ -47,7 +53,6 @@ changelog: - "^docs:" - "^test:" -# Homebrew brews: - name: k9s tap: @@ -61,3 +66,26 @@ brews: description: Kubernetes CLI To Manage Your Clusters In Style! test: | system "k9s version" + +nfpms: + - file_name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' + replacements: + linux: Linux + maintainer: Fernand Galiana + homepage: https://k9scli.io + description: Kubernetes CLI To Manage Your Clusters In Style! + license: "Apache-2.0" + formats: + - deb + - rpm + - apk + bindir: /usr/bin + section: utils + contents: + - src: ./LICENSE + dst: /usr/share/doc/nfpm/copyright + file_info: + mode: 0644 + +sboms: + - artifacts: archive \ No newline at end of file diff --git a/cmd/info.go b/cmd/info.go index 9828aab286..1ac5a5ff5f 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package cmd import ( diff --git a/cmd/info_test.go b/cmd/info_test.go index 867d34789e..6f2a240780 100644 --- a/cmd/info_test.go +++ b/cmd/info_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package cmd import ( diff --git a/cmd/root.go b/cmd/root.go index 357e42b07f..882b96e270 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package cmd import ( diff --git a/cmd/version.go b/cmd/version.go index 13617a4801..67727c7d23 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package cmd import ( diff --git a/internal/client/client.go b/internal/client/client.go index a7fcf55889..6bbdffdd9b 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/client_test.go b/internal/client/client_test.go index a489f46784..e777b29877 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/config.go b/internal/client/config.go index 6086ac3159..6603850b4a 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/config_test.go b/internal/client/config_test.go index cf2e854945..37a1029c03 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client_test import ( diff --git a/internal/client/errors.go b/internal/client/errors.go index 99dabe3d09..f13260ddf0 100644 --- a/internal/client/errors.go +++ b/internal/client/errors.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import metricsapi "k8s.io/metrics/pkg/apis/metrics" diff --git a/internal/client/gvr.go b/internal/client/gvr.go index 6822e93d3b..715e274dd7 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/gvr_test.go b/internal/client/gvr_test.go index 224ee2824e..714f1f454f 100644 --- a/internal/client/gvr_test.go +++ b/internal/client/gvr_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client_test import ( diff --git a/internal/client/helper_test.go b/internal/client/helper_test.go index 4a4c509161..74e4988da9 100644 --- a/internal/client/helper_test.go +++ b/internal/client/helper_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client_test import ( diff --git a/internal/client/helpers.go b/internal/client/helpers.go index 6f45b6040d..68006ed132 100644 --- a/internal/client/helpers.go +++ b/internal/client/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 45aba6ca70..79f34eeb13 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/client/metrics_test.go b/internal/client/metrics_test.go index 3a632efc80..cb1a357295 100644 --- a/internal/client/metrics_test.go +++ b/internal/client/metrics_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client_test import ( diff --git a/internal/client/types.go b/internal/client/types.go index c57dbbdaac..fbeb657d77 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package client import ( diff --git a/internal/color/colorize.go b/internal/color/colorize.go index f4e7177e2e..24811488e2 100644 --- a/internal/color/colorize.go +++ b/internal/color/colorize.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package color import ( diff --git a/internal/color/colorize_test.go b/internal/color/colorize_test.go index db9b768295..3d71f83f7b 100644 --- a/internal/color/colorize_test.go +++ b/internal/color/colorize_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package color_test import ( diff --git a/internal/config/alias.go b/internal/config/alias.go index 8dddf782be..1b5848c2f1 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index a627ddd283..3d40bc41a2 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/bench.go b/internal/config/bench.go index c3f6c4c95b..b837b361fc 100644 --- a/internal/config/bench.go +++ b/internal/config/bench.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/bench_test.go b/internal/config/bench_test.go index fd6e4d97aa..d6c225db5e 100644 --- a/internal/config/bench_test.go +++ b/internal/config/bench_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/cluster.go b/internal/config/cluster.go index ec0a9c3274..e95150191b 100644 --- a/internal/config/cluster.go +++ b/internal/config/cluster.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import "github.com/derailed/k9s/internal/client" diff --git a/internal/config/cluster_test.go b/internal/config/cluster_test.go index 53e3c0565e..86f4957886 100644 --- a/internal/config/cluster_test.go +++ b/internal/config/cluster_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/config.go b/internal/config/config.go index 3a10544cfa..7342c1df8a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ee17101195..f33b8aecfe 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/feature.go b/internal/config/feature.go index 52164ec5da..f94f5855a4 100644 --- a/internal/config/feature.go +++ b/internal/config/feature.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config // FeatureGates represents K9s opt-in features. diff --git a/internal/config/flags.go b/internal/config/flags.go index e521ce2cf1..aa2939ea47 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 1ed01ab472..f3668370af 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/helpers_test.go b/internal/config/helpers_test.go index 381255853d..06dcf9442f 100644 --- a/internal/config/helpers_test.go +++ b/internal/config/helpers_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index 707bc0c718..ea7bd715a4 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/hotkey_test.go b/internal/config/hotkey_test.go index 96515b243f..91fe6a4b5a 100644 --- a/internal/config/hotkey_test.go +++ b/internal/config/hotkey_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 4afbb2b111..0f1f2ddadb 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index 631af2228c..b4c233ada8 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/logger.go b/internal/config/logger.go index ba8c163887..93f6bbd418 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/logger_test.go b/internal/config/logger_test.go index ff1d2ff11a..e0253505f9 100644 --- a/internal/config/logger_test.go +++ b/internal/config/logger_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/mock_connection_test.go b/internal/config/mock_connection_test.go index 2a024947ca..77f4568b07 100644 --- a/internal/config/mock_connection_test.go +++ b/internal/config/mock_connection_test.go @@ -1,6 +1,9 @@ // Code generated by pegomock. DO NOT EDIT. // Source: github.com/derailed/k9s/internal/client (interfaces: Connection) +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/mock_kubesettings_test.go b/internal/config/mock_kubesettings_test.go index 9269469f91..68d7fd0901 100644 --- a/internal/config/mock_kubesettings_test.go +++ b/internal/config/mock_kubesettings_test.go @@ -1,6 +1,9 @@ // Code generated by pegomock. DO NOT EDIT. // Source: github.com/derailed/k9s/internal/config (interfaces: KubeSettings) +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/ns.go b/internal/config/ns.go index 411f4fca96..c49f4324b1 100644 --- a/internal/config/ns.go +++ b/internal/config/ns.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/ns_test.go b/internal/config/ns_test.go index 3348936651..c84ffe54cf 100644 --- a/internal/config/ns_test.go +++ b/internal/config/ns_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 029bda8995..2deca3a4b2 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( @@ -45,7 +48,7 @@ func NewPlugins() Plugins { // Load K9s plugins. func (p Plugins) Load() error { - var pluginDirs []string + pluginDirs := make([]string, 0, len(xdg.DataDirs)) for _, dataDir := range xdg.DataDirs { pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory)) } diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go index 8350625e67..175a8285ab 100644 --- a/internal/config/plugin_test.go +++ b/internal/config/plugin_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index a9c429b832..f1e21692fb 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/styles.go b/internal/config/styles.go index 9e1194165c..26bb653350 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/styles_test.go b/internal/config/styles_test.go index e934e880b3..d3d8874a1e 100644 --- a/internal/config/styles_test.go +++ b/internal/config/styles_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/threshold.go b/internal/config/threshold.go index 1db1ccfa97..f3300178bf 100644 --- a/internal/config/threshold.go +++ b/internal/config/threshold.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/threshold_test.go b/internal/config/threshold_test.go index 1c1be9370c..11cabae3bf 100644 --- a/internal/config/threshold_test.go +++ b/internal/config/threshold_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/view.go b/internal/config/view.go index e9461c833f..e4078ccdbe 100644 --- a/internal/config/view.go +++ b/internal/config/view.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config const defaultView = "po" diff --git a/internal/config/view_test.go b/internal/config/view_test.go index 2fc7532a90..10dfb152e8 100644 --- a/internal/config/view_test.go +++ b/internal/config/view_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/config/views.go b/internal/config/views.go index 4c10a9e9d3..929c14b284 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config import ( diff --git a/internal/config/views_test.go b/internal/config/views_test.go index 0005cfd20e..af3885fe77 100644 --- a/internal/config/views_test.go +++ b/internal/config/views_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package config_test import ( diff --git a/internal/dao/alias.go b/internal/dao/alias.go index 4c073527bc..bce58361a0 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go index 337c78a76a..ceab249822 100644 --- a/internal/dao/alias_test.go +++ b/internal/dao/alias_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go index d0d1afe678..b5dbef1cba 100644 --- a/internal/dao/benchmark.go +++ b/internal/dao/benchmark.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/benchmark_test.go b/internal/dao/benchmark_test.go index b3e52f2192..0d4aae08c8 100644 --- a/internal/dao/benchmark_test.go +++ b/internal/dao/benchmark_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go index 38d8cb5ca2..079e880450 100644 --- a/internal/dao/cluster.go +++ b/internal/dao/cluster.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/container.go b/internal/dao/container.go index 216d40ee7d..df8cd76627 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index 61df494829..6765599942 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/context.go b/internal/dao/context.go index 62ac939dc3..222dbc6c32 100644 --- a/internal/dao/context.go +++ b/internal/dao/context.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/crd.go b/internal/dao/crd.go index d05d5f9a05..fcf7f53b65 100644 --- a/internal/dao/crd.go +++ b/internal/dao/crd.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 9c5d5011f9..201f771369 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( @@ -57,9 +63,9 @@ func (c *CronJob) Run(path string) error { true := true job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - Name: jobName + "-manual-" + rand.String(3), - Namespace: ns, - Labels: cj.Spec.JobTemplate.Labels, + Name: jobName + "-manual-" + rand.String(3), + Namespace: ns, + Labels: cj.Spec.JobTemplate.Labels, Annotations: cj.Spec.JobTemplate.Annotations, OwnerReferences: []metav1.OwnerReference{ { diff --git a/internal/dao/cruiser.go b/internal/dao/cruiser.go index 4709827eaf..25a194eeb4 100644 --- a/internal/dao/cruiser.go +++ b/internal/dao/cruiser.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/cruiser_test.go b/internal/dao/cruiser_test.go index da8769e7f4..6e88cb8649 100644 --- a/internal/dao/cruiser_test.go +++ b/internal/dao/cruiser_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/describe.go b/internal/dao/describe.go index 6d40d482ae..32db74b440 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/dir.go b/internal/dao/dir.go index b5a3a45627..95239cc070 100644 --- a/internal/dao/dir.go +++ b/internal/dao/dir.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/dir_test.go b/internal/dao/dir_test.go index cd604484e5..c33e5d785a 100644 --- a/internal/dao/dir_test.go +++ b/internal/dao/dir_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 8e21d0c46f..541415b180 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 1ef7bf39a6..d970afd274 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/generic.go b/internal/dao/generic.go index 22e4eebefd..e205e1f525 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/helm.go b/internal/dao/helm.go index 0c4ffd48c5..5ec675bd89 100644 --- a/internal/dao/helm.go +++ b/internal/dao/helm.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index c1b0a047f8..049324a507 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/helpers_test.go b/internal/dao/helpers_test.go index 8687e119a9..4379627e9b 100644 --- a/internal/dao/helpers_test.go +++ b/internal/dao/helpers_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/job.go b/internal/dao/job.go index 6264dff3ad..4b9311b3fd 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/log_item.go b/internal/dao/log_item.go index 2a814bca4b..2ef1ba3d43 100644 --- a/internal/dao/log_item.go +++ b/internal/dao/log_item.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/log_item_test.go b/internal/dao/log_item_test.go index 0e4e78d8a9..711db12384 100644 --- a/internal/dao/log_item_test.go +++ b/internal/dao/log_item_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/log_items.go b/internal/dao/log_items.go index 888c3db395..e84c874002 100644 --- a/internal/dao/log_items.go +++ b/internal/dao/log_items.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/log_items_test.go b/internal/dao/log_items_test.go index 924664a838..787fddfe13 100644 --- a/internal/dao/log_items_test.go +++ b/internal/dao/log_items_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/log_options.go b/internal/dao/log_options.go index bd3fd03d6f..edd20afb6b 100644 --- a/internal/dao/log_options.go +++ b/internal/dao/log_options.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/log_options_test.go b/internal/dao/log_options_test.go index 71a594e648..feaaef8622 100644 --- a/internal/dao/log_options_test.go +++ b/internal/dao/log_options_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/node.go b/internal/dao/node.go index 9504f110be..67b14f2feb 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/non_resource.go b/internal/dao/non_resource.go index 96cf94b7ed..c32728d3da 100644 --- a/internal/dao/non_resource.go +++ b/internal/dao/non_resource.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/ns.go b/internal/dao/ns.go index d9daddfca2..492e3df4de 100644 --- a/internal/dao/ns.go +++ b/internal/dao/ns.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/ofaas.go b/internal/dao/ofaas.go index 4716a48466..0030aa4e85 100644 --- a/internal/dao/ofaas.go +++ b/internal/dao/ofaas.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao // BOZO!! Revamp with latest diff --git a/internal/dao/patch.go b/internal/dao/patch.go index 20fc8213ef..e834f196d0 100644 --- a/internal/dao/patch.go +++ b/internal/dao/patch.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/patch_test.go b/internal/dao/patch_test.go index 0fbda32a6b..efe460419f 100644 --- a/internal/dao/patch_test.go +++ b/internal/dao/patch_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/pod.go b/internal/dao/pod.go index a65e63d39f..70b7f3dc5e 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -1,3 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/pod_test.go b/internal/dao/pod_test.go index cec9b794b9..2ec160b1cd 100644 --- a/internal/dao/pod_test.go +++ b/internal/dao/pod_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 7dab2be6a2..962f0816eb 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 8e7e4c5465..3c62ccdc95 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/port_forward_test.go b/internal/dao/port_forward_test.go index d5d757a379..1fbb0d4721 100644 --- a/internal/dao/port_forward_test.go +++ b/internal/dao/port_forward_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 37812215c2..3c6d2928a3 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/pulse.go b/internal/dao/pulse.go index 4c93ad38d7..b7d09c7b33 100644 --- a/internal/dao/pulse.go +++ b/internal/dao/pulse.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rbac.go b/internal/dao/rbac.go index 540204a92e..b9637d7a79 100644 --- a/internal/dao/rbac.go +++ b/internal/dao/rbac.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go index a7f86543e2..3233c0ba2b 100644 --- a/internal/dao/rbac_policy.go +++ b/internal/dao/rbac_policy.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rbac_policy_test.go b/internal/dao/rbac_policy_test.go index af49a10802..29967fdceb 100644 --- a/internal/dao/rbac_policy_test.go +++ b/internal/dao/rbac_policy_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rbac_subject.go b/internal/dao/rbac_subject.go index 73f1392203..0939b50a62 100644 --- a/internal/dao/rbac_subject.go +++ b/internal/dao/rbac_subject.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/reference.go b/internal/dao/reference.go index d0331343aa..25db064a9b 100644 --- a/internal/dao/reference.go +++ b/internal/dao/reference.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 64638842c2..8a3eb212f0 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/registry_test.go b/internal/dao/registry_test.go index 4d28895bd4..51a05dba08 100644 --- a/internal/dao/registry_test.go +++ b/internal/dao/registry_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/resource.go b/internal/dao/resource.go index 18ea41881e..5cf85d71d3 100644 --- a/internal/dao/resource.go +++ b/internal/dao/resource.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rest_mapper.go b/internal/dao/rest_mapper.go index 3220fe0a79..9e1391d4a9 100644 --- a/internal/dao/rest_mapper.go +++ b/internal/dao/rest_mapper.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/rs.go b/internal/dao/rs.go index 7e7ae755b5..06d91c9158 100644 --- a/internal/dao/rs.go +++ b/internal/dao/rs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/screen_dump.go b/internal/dao/screen_dump.go index 7ca9962684..18b304d3ec 100644 --- a/internal/dao/screen_dump.go +++ b/internal/dao/screen_dump.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 8e346233b1..d9ce1e1e6c 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/svc.go b/internal/dao/svc.go index 55023c4369..382a4373c2 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/table.go b/internal/dao/table.go index 422ccdddb4..823408f9eb 100644 --- a/internal/dao/table.go +++ b/internal/dao/table.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/dao/types.go b/internal/dao/types.go index 23ac9e7221..9af913985c 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao import ( diff --git a/internal/health/check.go b/internal/health/check.go index 14a56c1293..745c6e2af4 100644 --- a/internal/health/check.go +++ b/internal/health/check.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package health import ( diff --git a/internal/health/check_test.go b/internal/health/check_test.go index 0cdd27030e..1b6f112684 100644 --- a/internal/health/check_test.go +++ b/internal/health/check_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package health_test import ( diff --git a/internal/health/types.go b/internal/health/types.go index 4bb5de2117..b2c0822961 100644 --- a/internal/health/types.go +++ b/internal/health/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package health // Level tracks health count categories. diff --git a/internal/keys.go b/internal/keys.go index f184b49d5f..1b53cfece6 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package internal // ContextKey represents context key. diff --git a/internal/model/cluster.go b/internal/model/cluster.go index 363c32325c..d4d2ce30ec 100644 --- a/internal/model/cluster.go +++ b/internal/model/cluster.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/cluster_info.go b/internal/model/cluster_info.go index 0f9d98c280..ac69ee85ff 100644 --- a/internal/model/cluster_info.go +++ b/internal/model/cluster_info.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/cluster_info_test.go b/internal/model/cluster_info_test.go index 84155a651f..48426ba5e2 100644 --- a/internal/model/cluster_info_test.go +++ b/internal/model/cluster_info_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/cmd_buff.go b/internal/model/cmd_buff.go index ba4db4b392..f62ca79949 100644 --- a/internal/model/cmd_buff.go +++ b/internal/model/cmd_buff.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/cmd_buff_test.go b/internal/model/cmd_buff_test.go index 0933affbca..924470ad3e 100644 --- a/internal/model/cmd_buff_test.go +++ b/internal/model/cmd_buff_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/describe.go b/internal/model/describe.go index 8a44af1bd9..96b5a6df00 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/fish_buff.go b/internal/model/fish_buff.go index 77098df946..b21e2f497b 100644 --- a/internal/model/fish_buff.go +++ b/internal/model/fish_buff.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/fish_buff_test.go b/internal/model/fish_buff_test.go index 7fcde49866..ae8abe3ca2 100644 --- a/internal/model/fish_buff_test.go +++ b/internal/model/fish_buff_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/flash.go b/internal/model/flash.go index 150ae8bce6..def08b090e 100644 --- a/internal/model/flash.go +++ b/internal/model/flash.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/flash_test.go b/internal/model/flash_test.go index 2749239f2a..2484fdef58 100644 --- a/internal/model/flash_test.go +++ b/internal/model/flash_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/helpers.go b/internal/model/helpers.go index c09cd5e5e5..507de8697b 100644 --- a/internal/model/helpers.go +++ b/internal/model/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/helpers_test.go b/internal/model/helpers_test.go index ce45d5ecd9..813889e27e 100644 --- a/internal/model/helpers_test.go +++ b/internal/model/helpers_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/hint.go b/internal/model/hint.go index 84a5ecdd49..8bdeb850e2 100644 --- a/internal/model/hint.go +++ b/internal/model/hint.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model // HintListener represents a menu hints listener. diff --git a/internal/model/hint_test.go b/internal/model/hint_test.go index ef1af0966f..383b2c5096 100644 --- a/internal/model/hint_test.go +++ b/internal/model/hint_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/history.go b/internal/model/history.go index 922720f3f2..04881796f5 100644 --- a/internal/model/history.go +++ b/internal/model/history.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/history_test.go b/internal/model/history_test.go index d4fade171d..1655675228 100644 --- a/internal/model/history_test.go +++ b/internal/model/history_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/log.go b/internal/model/log.go index 52290a73cb..09297b06ef 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/log_int_test.go b/internal/model/log_int_test.go index c7d17c659b..57dd952078 100644 --- a/internal/model/log_int_test.go +++ b/internal/model/log_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/log_test.go b/internal/model/log_test.go index 09d00c6927..18f629bace 100644 --- a/internal/model/log_test.go +++ b/internal/model/log_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/menu_hint.go b/internal/model/menu_hint.go index 7d1da43e7e..2ac0ffc9e4 100644 --- a/internal/model/menu_hint.go +++ b/internal/model/menu_hint.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/menu_hint_test.go b/internal/model/menu_hint_test.go index d364dc4c60..0aa23449be 100644 --- a/internal/model/menu_hint_test.go +++ b/internal/model/menu_hint_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/mock_clustermeta_test.go b/internal/model/mock_clustermeta_test.go index a47a8434b6..bec8892605 100644 --- a/internal/model/mock_clustermeta_test.go +++ b/internal/model/mock_clustermeta_test.go @@ -1,6 +1,9 @@ // Code generated by pegomock. DO NOT EDIT. // Source: github.com/derailed/k9s/internal/model (interfaces: ClusterMeta) +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/mock_connection_test.go b/internal/model/mock_connection_test.go index 9ce08a7ea8..e0ed4c9a60 100644 --- a/internal/model/mock_connection_test.go +++ b/internal/model/mock_connection_test.go @@ -1,6 +1,9 @@ // Code generated by pegomock. DO NOT EDIT. // Source: github.com/derailed/k9s/internal/client (interfaces: Connection) +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/mock_metricsserver_test.go b/internal/model/mock_metricsserver_test.go index 8447f578a6..f206c11b4b 100644 --- a/internal/model/mock_metricsserver_test.go +++ b/internal/model/mock_metricsserver_test.go @@ -1,6 +1,9 @@ // Code generated by pegomock. DO NOT EDIT. // Source: github.com/derailed/k9s/internal/model (interfaces: MetricsServer) +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/pulse.go b/internal/model/pulse.go index d26ecc83f5..0b62a1a7c1 100644 --- a/internal/model/pulse.go +++ b/internal/model/pulse.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index 8cd9b10e15..ecac480c9a 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/registry.go b/internal/model/registry.go index bce63731f6..cb4259792a 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/semver.go b/internal/model/semver.go index d2fea1e9d6..4b88794edf 100644 --- a/internal/model/semver.go +++ b/internal/model/semver.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/semver_test.go b/internal/model/semver_test.go index 6ab3fd5bd0..45876f1e1b 100644 --- a/internal/model/semver_test.go +++ b/internal/model/semver_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/stack.go b/internal/model/stack.go index 53cd0feaca..19f291a304 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go index 9ccc5ed6cb..aaa43fbcc4 100644 --- a/internal/model/stack_test.go +++ b/internal/model/stack_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/table.go b/internal/model/table.go index 861fd1eb5b..8924ef6c11 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/table_int_test.go b/internal/model/table_int_test.go index 7667efb8b6..abe9a606d2 100644 --- a/internal/model/table_int_test.go +++ b/internal/model/table_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/table_test.go b/internal/model/table_test.go index 55d7261a87..b944cc6d5e 100644 --- a/internal/model/table_test.go +++ b/internal/model/table_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/text.go b/internal/model/text.go index 7b1f5e2b82..0cc98cfdd0 100644 --- a/internal/model/text.go +++ b/internal/model/text.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/text_test.go b/internal/model/text_test.go index d8c2f94923..ffa390cd41 100644 --- a/internal/model/text_test.go +++ b/internal/model/text_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model_test import ( diff --git a/internal/model/tree.go b/internal/model/tree.go index 3287d40616..4e21954c7c 100644 --- a/internal/model/tree.go +++ b/internal/model/tree.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/types.go b/internal/model/types.go index 489fedd6ea..3c9c8e3c2e 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/values.go b/internal/model/values.go index 46022bc287..ef1a898122 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/yaml.go b/internal/model/yaml.go index e919b26fce..dd914462d3 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/model/yaml_test.go b/internal/model/yaml_test.go index 9b595134e0..35612cefc7 100644 --- a/internal/model/yaml_test.go +++ b/internal/model/yaml_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package model import ( diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index 8cd529372e..c13f3a0a52 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -1,10 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package perf import ( "bytes" "context" "fmt" - "github.com/derailed/k9s/internal/dao" "io" "net/http" "os" @@ -12,6 +14,8 @@ import ( "sync" "time" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/rakyll/hey/requester" diff --git a/internal/port/ann.go b/internal/port/ann.go index 8d7b12b4d6..17ebb589b3 100644 --- a/internal/port/ann.go +++ b/internal/port/ann.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port import ( diff --git a/internal/port/ann_test.go b/internal/port/ann_test.go index 2351885791..336a4f37dc 100644 --- a/internal/port/ann_test.go +++ b/internal/port/ann_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port_test import ( diff --git a/internal/port/co_portspec.go b/internal/port/co_portspec.go index 13c8ef8167..bddec9713e 100644 --- a/internal/port/co_portspec.go +++ b/internal/port/co_portspec.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port import ( diff --git a/internal/port/co_portspec_test.go b/internal/port/co_portspec_test.go index 3886477a14..39f57040a3 100644 --- a/internal/port/co_portspec_test.go +++ b/internal/port/co_portspec_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port_test import ( diff --git a/internal/port/pf.go b/internal/port/pf.go index fb4da5709f..26d4ffbc4c 100644 --- a/internal/port/pf.go +++ b/internal/port/pf.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port import ( diff --git a/internal/port/pf_test.go b/internal/port/pf_test.go index 62b6bf99da..66a11d15a4 100644 --- a/internal/port/pf_test.go +++ b/internal/port/pf_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port_test import ( diff --git a/internal/port/pfs.go b/internal/port/pfs.go index 10cc6bd0f7..85b0a2e515 100644 --- a/internal/port/pfs.go +++ b/internal/port/pfs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port import ( diff --git a/internal/port/pfs_test.go b/internal/port/pfs_test.go index 7b60d86693..3d003a47ac 100644 --- a/internal/port/pfs_test.go +++ b/internal/port/pfs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port_test import ( diff --git a/internal/port/tunnel.go b/internal/port/tunnel.go index 8074372abb..758675adc4 100644 --- a/internal/port/tunnel.go +++ b/internal/port/tunnel.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port import ( diff --git a/internal/port/tunnel_test.go b/internal/port/tunnel_test.go index d88dab582f..763d412017 100644 --- a/internal/port/tunnel_test.go +++ b/internal/port/tunnel_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package port_test import ( diff --git a/internal/render/alias.go b/internal/render/alias.go index 2cb658166d..78855128c5 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go index 18c0e5ae95..a9c87a842e 100644 --- a/internal/render/alias_test.go +++ b/internal/render/alias_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/base.go b/internal/render/base.go index 3a668993be..65e66b44bb 100644 --- a/internal/render/base.go +++ b/internal/render/base.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render // DecoratorFunc decorates a string. diff --git a/internal/render/benchmark.go b/internal/render/benchmark.go index 2b60cff68c..9124e0d15a 100644 --- a/internal/render/benchmark.go +++ b/internal/render/benchmark.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/benchmark_int_test.go b/internal/render/benchmark_int_test.go index 4fe0963948..bd296e23e6 100644 --- a/internal/render/benchmark_int_test.go +++ b/internal/render/benchmark_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/color.go b/internal/render/color.go index cc1339b210..816c1e39f7 100644 --- a/internal/render/color.go +++ b/internal/render/color.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/color_test.go b/internal/render/color_test.go index 77bb4c51fa..baa8c5ff49 100644 --- a/internal/render/color_test.go +++ b/internal/render/color_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/container.go b/internal/render/container.go index e879265e3e..c32b64d9aa 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/container_test.go b/internal/render/container_test.go index f826ab595d..e574df56e6 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/context.go b/internal/render/context.go index f4f1c54b06..3c105e0f20 100644 --- a/internal/render/context.go +++ b/internal/render/context.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/context_test.go b/internal/render/context_test.go index b1ebbd96d9..4c20249ce5 100644 --- a/internal/render/context_test.go +++ b/internal/render/context_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/cr.go b/internal/render/cr.go index c115d2e587..925b75350f 100644 --- a/internal/render/cr.go +++ b/internal/render/cr.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/cr_test.go b/internal/render/cr_test.go index 32492370b3..d6908a0ecc 100644 --- a/internal/render/cr_test.go +++ b/internal/render/cr_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/crb.go b/internal/render/crb.go index 89873e98af..04842356a3 100644 --- a/internal/render/crb.go +++ b/internal/render/crb.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/crb_test.go b/internal/render/crb_test.go index 931046e8f4..85aac27671 100644 --- a/internal/render/crb_test.go +++ b/internal/render/crb_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/crd.go b/internal/render/crd.go index 0e6c2b0048..b51f90b033 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/crd_test.go b/internal/render/crd_test.go index 0bd9d18634..fd12851049 100644 --- a/internal/render/crd_test.go +++ b/internal/render/crd_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go index 8dc08a2e4d..efaab7b355 100644 --- a/internal/render/cronjob.go +++ b/internal/render/cronjob.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/cronjob_test.go b/internal/render/cronjob_test.go index 0e3f86792e..e24ac7852a 100644 --- a/internal/render/cronjob_test.go +++ b/internal/render/cronjob_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/delta.go b/internal/render/delta.go index 557dd25fdd..bdc95aa1c0 100644 --- a/internal/render/delta.go +++ b/internal/render/delta.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/delta_test.go b/internal/render/delta_test.go index c8140d7f1c..08d8960c34 100644 --- a/internal/render/delta_test.go +++ b/internal/render/delta_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/dir.go b/internal/render/dir.go index 6c50c17d04..b444c8c7c4 100644 --- a/internal/render/dir.go +++ b/internal/render/dir.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/dp.go b/internal/render/dp.go index 88841df5be..1e1b1738bc 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/dp_test.go b/internal/render/dp_test.go index ea8168c6a0..c82a1defa4 100644 --- a/internal/render/dp_test.go +++ b/internal/render/dp_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ds.go b/internal/render/ds.go index 80a3bff2e3..dea0da35f2 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/ds_test.go b/internal/render/ds_test.go index 4ab3796395..a493544545 100644 --- a/internal/render/ds_test.go +++ b/internal/render/ds_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ep.go b/internal/render/ep.go index bb8c23c1b1..4a6b3640f2 100644 --- a/internal/render/ep.go +++ b/internal/render/ep.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/ep_test.go b/internal/render/ep_test.go index 2e1b0a10e3..620f87e0cf 100644 --- a/internal/render/ep_test.go +++ b/internal/render/ep_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ev.go b/internal/render/ev.go index 018e899c3a..f028c23844 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/ev_test.go b/internal/render/ev_test.go index e65137a153..ce7ba27c73 100644 --- a/internal/render/ev_test.go +++ b/internal/render/ev_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test // BOZO!! diff --git a/internal/render/generic.go b/internal/render/generic.go index d049d204df..f72ae0dcfc 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -1,13 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( "encoding/json" "errors" "fmt" - "github.com/rs/zerolog/log" "strings" "github.com/derailed/k9s/internal/client" + "github.com/rs/zerolog/log" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" ) diff --git a/internal/render/generic_test.go b/internal/render/generic_test.go index 7f3f488407..07172d2965 100644 --- a/internal/render/generic_test.go +++ b/internal/render/generic_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/header.go b/internal/render/header.go index 253189cf29..780cc3d073 100644 --- a/internal/render/header.go +++ b/internal/render/header.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/header_test.go b/internal/render/header_test.go index 140e89c890..8c82a141dc 100644 --- a/internal/render/header_test.go +++ b/internal/render/header_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/helm.go b/internal/render/helm.go index 2d2e0d7707..6ec905a6ac 100644 --- a/internal/render/helm.go +++ b/internal/render/helm.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 95ed2cf6ef..cba70c9a71 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 7f8e2c0de1..8b220c2174 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/job.go b/internal/render/job.go index a8e4e5b176..c036a82d45 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/job_test.go b/internal/render/job_test.go index e2a5fe5c03..5479291270 100644 --- a/internal/render/job_test.go +++ b/internal/render/job_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/node.go b/internal/render/node.go index 2e892b104b..3fb2bd74ec 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/node_test.go b/internal/render/node_test.go index 909868273b..584670201a 100644 --- a/internal/render/node_test.go +++ b/internal/render/node_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/np.go b/internal/render/np.go index 9275a21733..2904a353fb 100644 --- a/internal/render/np.go +++ b/internal/render/np.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/np_test.go b/internal/render/np_test.go index 2726e6c41e..bd3412f205 100644 --- a/internal/render/np_test.go +++ b/internal/render/np_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ns.go b/internal/render/ns.go index 35a8d783cc..6dcd2a6f9d 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go index d83524564e..81ca793e3d 100644 --- a/internal/render/ns_test.go +++ b/internal/render/ns_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ofaas.go b/internal/render/ofaas.go index a529b320d2..0df5f904df 100644 --- a/internal/render/ofaas.go +++ b/internal/render/ofaas.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render // BOZO!! revamp with latest... diff --git a/internal/render/ofaas_test.go b/internal/render/ofaas_test.go index 41222a1c0e..dbefa9d6d2 100644 --- a/internal/render/ofaas_test.go +++ b/internal/render/ofaas_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test // BOZO!! revamp with latest... diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 24d4ccb9e5..357133b071 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/pdb_test.go b/internal/render/pdb_test.go index 7e14753b6e..2f40acaedb 100644 --- a/internal/render/pdb_test.go +++ b/internal/render/pdb_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/pod.go b/internal/render/pod.go index afbcd7da81..46d118e4f5 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index e439b6caf2..c442c2e50f 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/policy.go b/internal/render/policy.go index 9f3ff91941..568ac540c8 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/policy_test.go b/internal/render/policy_test.go index 3e9943a3d7..536425fb3d 100644 --- a/internal/render/policy_test.go +++ b/internal/render/policy_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/popeye.go b/internal/render/popeye.go index b9709008b7..c8dd757d54 100644 --- a/internal/render/popeye.go +++ b/internal/render/popeye.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/port_forward_test.go b/internal/render/port_forward_test.go index d8728f8fb3..4084cb40c5 100644 --- a/internal/render/port_forward_test.go +++ b/internal/render/port_forward_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/portforward.go b/internal/render/portforward.go index 2a039acf93..d3908bb9d9 100644 --- a/internal/render/portforward.go +++ b/internal/render/portforward.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/pv.go b/internal/render/pv.go index 630c2e77ac..72ee55e097 100644 --- a/internal/render/pv.go +++ b/internal/render/pv.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/pv_test.go b/internal/render/pv_test.go index d1e4cf21ff..93f77fb583 100644 --- a/internal/render/pv_test.go +++ b/internal/render/pv_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/pvc.go b/internal/render/pvc.go index f5514d5b58..76414e12fb 100644 --- a/internal/render/pvc.go +++ b/internal/render/pvc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/pvc_test.go b/internal/render/pvc_test.go index 8a210efd15..c1005cb1cf 100644 --- a/internal/render/pvc_test.go +++ b/internal/render/pvc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/rbac.go b/internal/render/rbac.go index 5d0f7a3cd7..5af09c87e7 100644 --- a/internal/render/rbac.go +++ b/internal/render/rbac.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/reference.go b/internal/render/reference.go index 33b8493c09..31695438ee 100644 --- a/internal/render/reference.go +++ b/internal/render/reference.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/reference_test.go b/internal/render/reference_test.go index 697ffcc19c..50654c1ade 100644 --- a/internal/render/reference_test.go +++ b/internal/render/reference_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/render_test.go b/internal/render/render_test.go index 20a1c331bc..13192cb9ab 100644 --- a/internal/render/render_test.go +++ b/internal/render/render_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/ro.go b/internal/render/ro.go index c1ce340fa6..e88e084edb 100644 --- a/internal/render/ro.go +++ b/internal/render/ro.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/ro_test.go b/internal/render/ro_test.go index b2bbe66c89..1e0e4cc5d4 100644 --- a/internal/render/ro_test.go +++ b/internal/render/ro_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/rob.go b/internal/render/rob.go index 9c9f6f8d77..1cb56adc05 100644 --- a/internal/render/rob.go +++ b/internal/render/rob.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/rob_test.go b/internal/render/rob_test.go index cb3e488261..306cab3066 100644 --- a/internal/render/rob_test.go +++ b/internal/render/rob_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/row.go b/internal/render/row.go index 1bd5e3872f..c562bc23cd 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/row_event.go b/internal/render/row_event.go index 83c7c44022..e3f1230d11 100644 --- a/internal/render/row_event.go +++ b/internal/render/row_event.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/row_event_test.go b/internal/render/row_event_test.go index c106673098..d75e1894f3 100644 --- a/internal/render/row_event_test.go +++ b/internal/render/row_event_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/row_test.go b/internal/render/row_test.go index 82e4728f44..f1d5a5bc0a 100644 --- a/internal/render/row_test.go +++ b/internal/render/row_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/rs.go b/internal/render/rs.go index b5eb1a3825..59a1a16bdb 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/rs_test.go b/internal/render/rs_test.go index ef051afb0f..066f5e0878 100644 --- a/internal/render/rs_test.go +++ b/internal/render/rs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/sa.go b/internal/render/sa.go index ac728cb1cf..dfa276ad68 100644 --- a/internal/render/sa.go +++ b/internal/render/sa.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/sa_test.go b/internal/render/sa_test.go index 31a7b772b8..c143bc509d 100644 --- a/internal/render/sa_test.go +++ b/internal/render/sa_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/sc.go b/internal/render/sc.go index a2995eab7e..392c1c4e97 100644 --- a/internal/render/sc.go +++ b/internal/render/sc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/sc_test.go b/internal/render/sc_test.go index 60cd56f242..004e91c494 100644 --- a/internal/render/sc_test.go +++ b/internal/render/sc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/screen_dump.go b/internal/render/screen_dump.go index 230303a1bf..36293fe6b7 100644 --- a/internal/render/screen_dump.go +++ b/internal/render/screen_dump.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/screen_dump_test.go b/internal/render/screen_dump_test.go index b7737a90de..bde7f109fd 100644 --- a/internal/render/screen_dump_test.go +++ b/internal/render/screen_dump_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/sts.go b/internal/render/sts.go index 58b8f2ba05..78be0c123f 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/sts_test.go b/internal/render/sts_test.go index 600daa9384..f3f9c7bd39 100644 --- a/internal/render/sts_test.go +++ b/internal/render/sts_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/subject.go b/internal/render/subject.go index 07b5c54867..b61f95daaa 100644 --- a/internal/render/subject.go +++ b/internal/render/subject.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/svc.go b/internal/render/svc.go index 6c72d3615b..642c293c10 100644 --- a/internal/render/svc.go +++ b/internal/render/svc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/svc_test.go b/internal/render/svc_test.go index 51df052236..e0b70471ac 100644 --- a/internal/render/svc_test.go +++ b/internal/render/svc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/table_data.go b/internal/render/table_data.go index c3f3b12b7a..aaf96cf227 100644 --- a/internal/render/table_data.go +++ b/internal/render/table_data.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render import ( diff --git a/internal/render/table_data_test.go b/internal/render/table_data_test.go index efa81fe0e0..0ddb7c1516 100644 --- a/internal/render/table_data_test.go +++ b/internal/render/table_data_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render_test import ( diff --git a/internal/render/types.go b/internal/render/types.go index 9f5b1ca0a2..3b1c37aebb 100644 --- a/internal/render/types.go +++ b/internal/render/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package render const ( diff --git a/internal/tchart/component.go b/internal/tchart/component.go index c86e3cb2f1..b557631e25 100644 --- a/internal/tchart/component.go +++ b/internal/tchart/component.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/component_int_test.go b/internal/tchart/component_int_test.go index ec01a3817e..22945ac5bc 100644 --- a/internal/tchart/component_int_test.go +++ b/internal/tchart/component_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/component_test.go b/internal/tchart/component_test.go index eee63a60f2..07a89c5a48 100644 --- a/internal/tchart/component_test.go +++ b/internal/tchart/component_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart_test import ( diff --git a/internal/tchart/dot_matrix.go b/internal/tchart/dot_matrix.go index cfa781f204..ae6b1bccda 100644 --- a/internal/tchart/dot_matrix.go +++ b/internal/tchart/dot_matrix.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/dot_matrix_test.go b/internal/tchart/dot_matrix_test.go index ad9cf7df6f..df1c85d208 100644 --- a/internal/tchart/dot_matrix_test.go +++ b/internal/tchart/dot_matrix_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart_test import ( diff --git a/internal/tchart/gauge.go b/internal/tchart/gauge.go index f2da693145..5b83052839 100644 --- a/internal/tchart/gauge.go +++ b/internal/tchart/gauge.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/gauge_int_test.go b/internal/tchart/gauge_int_test.go index a335eeea4b..b9f191601a 100644 --- a/internal/tchart/gauge_int_test.go +++ b/internal/tchart/gauge_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/gauge_test.go b/internal/tchart/gauge_test.go index 1843947f4e..8708402adf 100644 --- a/internal/tchart/gauge_test.go +++ b/internal/tchart/gauge_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart_test import ( diff --git a/internal/tchart/sparkline.go b/internal/tchart/sparkline.go index ce304f1303..2df126b008 100644 --- a/internal/tchart/sparkline.go +++ b/internal/tchart/sparkline.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/tchart/sparkline_int_test.go b/internal/tchart/sparkline_int_test.go index 36d516124f..14f0cd9b54 100644 --- a/internal/tchart/sparkline_int_test.go +++ b/internal/tchart/sparkline_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package tchart import ( diff --git a/internal/ui/action.go b/internal/ui/action.go index 1bf9e0724e..66913ba308 100644 --- a/internal/ui/action.go +++ b/internal/ui/action.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/action_test.go b/internal/ui/action_test.go index 34a7d6694b..de031ebd88 100644 --- a/internal/ui/action_test.go +++ b/internal/ui/action_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/app.go b/internal/ui/app.go index fd7aa2476e..3b656af2d1 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index 133b45aa7e..a49cb34f58 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/config.go b/internal/ui/config.go index 4a95e10151..cc177fa6f8 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index fd2ffe56c8..25355d535f 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/crumbs.go b/internal/ui/crumbs.go index 4c976d541d..8eab72308f 100644 --- a/internal/ui/crumbs.go +++ b/internal/ui/crumbs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/crumbs_test.go b/internal/ui/crumbs_test.go index 80b7fdb9a2..7d992768db 100644 --- a/internal/ui/crumbs_test.go +++ b/internal/ui/crumbs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/deltas.go b/internal/ui/deltas.go index 99b8c73f0e..5e4fdb7bfe 100644 --- a/internal/ui/deltas.go +++ b/internal/ui/deltas.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/deltas_test.go b/internal/ui/deltas_test.go index 6796d58911..005dc3ede0 100644 --- a/internal/ui/deltas_test.go +++ b/internal/ui/deltas_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/dialog/confirm.go b/internal/ui/dialog/confirm.go index fec298763e..6ddcc9d565 100644 --- a/internal/ui/dialog/confirm.go +++ b/internal/ui/dialog/confirm.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/confirm_test.go b/internal/ui/dialog/confirm_test.go index f5cd142985..211e209188 100644 --- a/internal/ui/dialog/confirm_test.go +++ b/internal/ui/dialog/confirm_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/delete.go b/internal/ui/dialog/delete.go index bd1bb675b3..686e95e6ac 100644 --- a/internal/ui/dialog/delete.go +++ b/internal/ui/dialog/delete.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/delete_test.go b/internal/ui/dialog/delete_test.go index a4c141668e..39b2eba091 100644 --- a/internal/ui/dialog/delete_test.go +++ b/internal/ui/dialog/delete_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/error.go b/internal/ui/dialog/error.go index f39e31fd36..26fdebfa5b 100644 --- a/internal/ui/dialog/error.go +++ b/internal/ui/dialog/error.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/error_test.go b/internal/ui/dialog/error_test.go index f882ac68da..b21fa4c67b 100644 --- a/internal/ui/dialog/error_test.go +++ b/internal/ui/dialog/error_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/transfer.go b/internal/ui/dialog/transfer.go index 7d51cba408..7db559c0d6 100644 --- a/internal/ui/dialog/transfer.go +++ b/internal/ui/dialog/transfer.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/flash.go b/internal/ui/flash.go index 7455a1321f..33c9a8c1e4 100644 --- a/internal/ui/flash.go +++ b/internal/ui/flash.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/flash_test.go b/internal/ui/flash_test.go index 76acec05d8..1b5cd6e3ce 100644 --- a/internal/ui/flash_test.go +++ b/internal/ui/flash_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/indicator.go b/internal/ui/indicator.go index cdcabdb751..2e0b524bce 100644 --- a/internal/ui/indicator.go +++ b/internal/ui/indicator.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/indicator_test.go b/internal/ui/indicator_test.go index 7a3a09226d..ad782274d2 100644 --- a/internal/ui/indicator_test.go +++ b/internal/ui/indicator_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/key.go b/internal/ui/key.go index fbf0028c0e..ff90b310ba 100644 --- a/internal/ui/key.go +++ b/internal/ui/key.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import "github.com/derailed/tcell/v2" diff --git a/internal/ui/logo.go b/internal/ui/logo.go index a8cddad04c..1971410cfe 100644 --- a/internal/ui/logo.go +++ b/internal/ui/logo.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/logo_test.go b/internal/ui/logo_test.go index 2e3c053c69..6a2fc0a0d4 100644 --- a/internal/ui/logo_test.go +++ b/internal/ui/logo_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/menu.go b/internal/ui/menu.go index 02cbedbe79..f86a81e919 100644 --- a/internal/ui/menu.go +++ b/internal/ui/menu.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/menu_test.go b/internal/ui/menu_test.go index ab000d93d0..599e2d2c62 100644 --- a/internal/ui/menu_test.go +++ b/internal/ui/menu_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/padding.go b/internal/ui/padding.go index 62272da4b7..b57cdb1f72 100644 --- a/internal/ui/padding.go +++ b/internal/ui/padding.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/padding_test.go b/internal/ui/padding_test.go index 18aa83e54d..51a0bcde43 100644 --- a/internal/ui/padding_test.go +++ b/internal/ui/padding_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/pages.go b/internal/ui/pages.go index 2f0835ef8e..580bb09628 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/pages_test.go b/internal/ui/pages_test.go index f6e447e497..2dc0ea7c28 100644 --- a/internal/ui/pages_test.go +++ b/internal/ui/pages_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index 117dd10017..fd4051956a 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go index 41b1c96497..bbe456a374 100644 --- a/internal/ui/prompt_test.go +++ b/internal/ui/prompt_test.go @@ -1,9 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( - "github.com/derailed/tcell/v2" "testing" + "github.com/derailed/tcell/v2" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index 335f362209..cf07c9f4d1 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/splash.go b/internal/ui/splash.go index ba44f2091e..bfe58e461b 100644 --- a/internal/ui/splash.go +++ b/internal/ui/splash.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/splash_test.go b/internal/ui/splash_test.go index 2113819c92..69b4b50d4c 100644 --- a/internal/ui/splash_test.go +++ b/internal/ui/splash_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/table.go b/internal/ui/table.go index 08d57436e9..47d1714b0b 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 21af967c90..0e89bf890e 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 8f1c18d9a4..f532643ef8 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 6567bb9a5a..ab5e09ba01 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui_test import ( diff --git a/internal/ui/tree.go b/internal/ui/tree.go index c62416206c..10eb31e764 100644 --- a/internal/ui/tree.go +++ b/internal/ui/tree.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/types.go b/internal/ui/types.go index 297f22b4f8..b426bc24cf 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/view/actions.go b/internal/view/actions.go index ff6b2a1c71..8410911d40 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/actions_test.go b/internal/view/actions_test.go index 7371091b7a..47176c0a7a 100644 --- a/internal/view/actions_test.go +++ b/internal/view/actions_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/alias.go b/internal/view/alias.go index c31623e876..fc33dc9237 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 00f9168413..1b82b378bf 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/app.go b/internal/view/app.go index a921d443d0..89a4e147e8 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/app_test.go b/internal/view/app_test.go index 4726eb9ac0..e214d7c4db 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 5601c42a18..db9f856c55 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/browser.go b/internal/view/browser.go index 0678bfacd3..c5f46447d7 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index c5a2cab7a6..8b91eff8a0 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/cm.go b/internal/view/cm.go index a0793c9ace..c1694ca6dc 100644 --- a/internal/view/cm.go +++ b/internal/view/cm.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/cm_test.go b/internal/view/cm_test.go index 461ff4ba00..02781af46d 100644 --- a/internal/view/cm_test.go +++ b/internal/view/cm_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/command.go b/internal/view/command.go index 1cc473fb5c..0fc57bf099 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/container.go b/internal/view/container.go index 9d39eda48e..6094fd2ed0 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/container_test.go b/internal/view/container_test.go index 161f4cec9d..cc1133e88a 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/context.go b/internal/view/context.go index 4896d06a29..97a5c91b5d 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/context_test.go b/internal/view/context_test.go index e6ce542a74..a265459ae7 100644 --- a/internal/view/context_test.go +++ b/internal/view/context_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/cow.go b/internal/view/cow.go index 140faf99bb..d71f3e898e 100644 --- a/internal/view/cow.go +++ b/internal/view/cow.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index cb265a3500..a5a5d7de15 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/details.go b/internal/view/details.go index d65485a2dc..e3f96f44c7 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/dir.go b/internal/view/dir.go index 38700a9345..061a72f327 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/dir_int_test.go b/internal/view/dir_int_test.go index a22d715e4a..d6f4c170c0 100644 --- a/internal/view/dir_int_test.go +++ b/internal/view/dir_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/dir_test.go b/internal/view/dir_test.go index b47f12b12c..7757eb8abf 100644 --- a/internal/view/dir_test.go +++ b/internal/view/dir_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/dp.go b/internal/view/dp.go index 5f473ed6e7..8f56948711 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go index 9c006652e4..5434db4bfc 100644 --- a/internal/view/dp_test.go +++ b/internal/view/dp_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/drain_dialog.go b/internal/view/drain_dialog.go index ed56a99e2d..722c89cedf 100644 --- a/internal/view/drain_dialog.go +++ b/internal/view/drain_dialog.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/ds.go b/internal/view/ds.go index da89b998ba..7276e74f9e 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go index d43fe84bff..17e8ed9dce 100644 --- a/internal/view/ds_test.go +++ b/internal/view/ds_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/env.go b/internal/view/env.go index 733beefe0f..8ac7155882 100644 --- a/internal/view/env.go +++ b/internal/view/env.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/env_test.go b/internal/view/env_test.go index 2dab6cb7dd..e00744f4ea 100644 --- a/internal/view/env_test.go +++ b/internal/view/env_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/event.go b/internal/view/event.go index f2bb023040..6de78f30bf 100644 --- a/internal/view/event.go +++ b/internal/view/event.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/exec.go b/internal/view/exec.go index e80dca0a05..691afc8a0a 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/group.go b/internal/view/group.go index ad074a002d..503b190c65 100644 --- a/internal/view/group.go +++ b/internal/view/group.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/helm.go b/internal/view/helm.go index 6e6e296880..73e6df89a6 100644 --- a/internal/view/helm.go +++ b/internal/view/helm.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/help.go b/internal/view/help.go index fb95384c5e..d5ff15cb4d 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/help_test.go b/internal/view/help_test.go index 2f29a6b765..818002ae72 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/helpers.go b/internal/view/helpers.go index da65c794e8..dea7a10137 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index a2c182e202..70699ae15c 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/image_extender.go b/internal/view/image_extender.go index 64f953a948..b67764c6a5 100644 --- a/internal/view/image_extender.go +++ b/internal/view/image_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/job.go b/internal/view/job.go index e9421d49dc..60f391b3dc 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/live_view.go b/internal/view/live_view.go index d9d6af892b..40163d504e 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/live_view_test.go b/internal/view/live_view_test.go index d3b801cc3e..045cfd4594 100644 --- a/internal/view/live_view_test.go +++ b/internal/view/live_view_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/log.go b/internal/view/log.go index a5f9e34355..2b6732465f 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index bd6859103b..ee95f97fc3 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/log_indicator_test.go b/internal/view/log_indicator_test.go index 2432a938ca..0c793f594d 100644 --- a/internal/view/log_indicator_test.go +++ b/internal/view/log_indicator_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/log_int_test.go b/internal/view/log_int_test.go index ac0a101afe..f6b5e4aeab 100644 --- a/internal/view/log_int_test.go +++ b/internal/view/log_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/log_test.go b/internal/view/log_test.go index 945aae3eb5..f5ae47a35a 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/logger.go b/internal/view/logger.go index 19446dea65..a3da224bd7 100644 --- a/internal/view/logger.go +++ b/internal/view/logger.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index d390c3654d..151d28adfa 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/node.go b/internal/view/node.go index d70b2b517b..82e9d33f80 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/ns.go b/internal/view/ns.go index 0b99de0e15..263018d5ad 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/ns_test.go b/internal/view/ns_test.go index 093e2e57ed..3c06101feb 100644 --- a/internal/view/ns_test.go +++ b/internal/view/ns_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/ofaas.go b/internal/view/ofaas.go index 5925a2e8f2..d2bc27f286 100644 --- a/internal/view/ofaas.go +++ b/internal/view/ofaas.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view // BOZO!! revamp with latest... diff --git a/internal/view/page_stack.go b/internal/view/page_stack.go index b7c4d80c96..03721e9573 100644 --- a/internal/view/page_stack.go +++ b/internal/view/page_stack.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf.go b/internal/view/pf.go index c4063b9674..bef01c0683 100644 --- a/internal/view/pf.go +++ b/internal/view/pf.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index 134fed724f..a80e8d8ad9 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf_dialog_test.go b/internal/view/pf_dialog_test.go index e59ccaec7a..e38fcb12f5 100644 --- a/internal/view/pf_dialog_test.go +++ b/internal/view/pf_dialog_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 42120c127a..37040a8a3b 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf_extender_test.go b/internal/view/pf_extender_test.go index c7cf4c2148..20a7214ef3 100644 --- a/internal/view/pf_extender_test.go +++ b/internal/view/pf_extender_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pf_test.go b/internal/view/pf_test.go index 505cc60b69..245e593933 100644 --- a/internal/view/pf_test.go +++ b/internal/view/pf_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/picker.go b/internal/view/picker.go index c789a37037..bca0386746 100644 --- a/internal/view/picker.go +++ b/internal/view/picker.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pod.go b/internal/view/pod.go index a526b0cca4..d2fd2b43e5 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pod_int_test.go b/internal/view/pod_int_test.go index e359924bfb..3a84f7272f 100644 --- a/internal/view/pod_int_test.go +++ b/internal/view/pod_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index 1f942588d7..101bd5da6d 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/policy.go b/internal/view/policy.go index 7b5c8b04fa..35081e99de 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/popeye.go b/internal/view/popeye.go index 8a92eb6d36..ca5c273766 100644 --- a/internal/view/popeye.go +++ b/internal/view/popeye.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/priorityclass.go b/internal/view/priorityclass.go index 4325bca8d1..78a0686681 100644 --- a/internal/view/priorityclass.go +++ b/internal/view/priorityclass.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/priorityclass_test.go b/internal/view/priorityclass_test.go index 1c258fb9f4..60cef2abf3 100644 --- a/internal/view/priorityclass_test.go +++ b/internal/view/priorityclass_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/pulse.go b/internal/view/pulse.go index 1f060142aa..d8acc0d0c1 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pvc.go b/internal/view/pvc.go index ca82465c4d..075ff4efca 100644 --- a/internal/view/pvc.go +++ b/internal/view/pvc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/pvc_test.go b/internal/view/pvc_test.go index 1559549882..cdaaaaf2bb 100644 --- a/internal/view/pvc_test.go +++ b/internal/view/pvc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/rbac.go b/internal/view/rbac.go index d3b4e8af7c..0205ee2295 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/rbac_test.go b/internal/view/rbac_test.go index 2c0972c81d..5d72d3588d 100644 --- a/internal/view/rbac_test.go +++ b/internal/view/rbac_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/reference.go b/internal/view/reference.go index b8ed13bdf1..d9a76d803a 100644 --- a/internal/view/reference.go +++ b/internal/view/reference.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/reference_test.go b/internal/view/reference_test.go index c7e17f3719..0e3578f7f7 100644 --- a/internal/view/reference_test.go +++ b/internal/view/reference_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 1d6b375f47..c5a226e590 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go index 18f25e7ae6..629e68f029 100644 --- a/internal/view/restart_extender.go +++ b/internal/view/restart_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/rs.go b/internal/view/rs.go index 14fd11ef54..9a0e64fc9e 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/sa.go b/internal/view/sa.go index 622fccf58d..d4a94a088e 100644 --- a/internal/view/sa.go +++ b/internal/view/sa.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index 2a63d47ec2..2b71d7d46c 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index 15754d1c71..86bc7809c9 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 5f02fd39a9..9f12c30029 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/screen_dump_test.go b/internal/view/screen_dump_test.go index 0e191f1b54..b812435b43 100644 --- a/internal/view/screen_dump_test.go +++ b/internal/view/screen_dump_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/secret.go b/internal/view/secret.go index bec084d0f8..73a1580513 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/secret_test.go b/internal/view/secret_test.go index c2d9d4df4f..fc07d6825f 100644 --- a/internal/view/secret_test.go +++ b/internal/view/secret_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/sts.go b/internal/view/sts.go index ee400f5788..d8b72d7f0a 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go index 6b7374906c..889b025fda 100644 --- a/internal/view/sts_test.go +++ b/internal/view/sts_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/svc.go b/internal/view/svc.go index 82a18d900b..dbbfccfb0e 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index fef2e960fa..babf102ac5 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view_test import ( diff --git a/internal/view/table.go b/internal/view/table.go index ab45e5fe74..4db21ff17f 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index e72e0cfd71..3833655954 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index cebdc3f5f0..bb20ce490c 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/types.go b/internal/view/types.go index c54372de75..6a1316f8c2 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/user.go b/internal/view/user.go index ab7d9bea50..12477d9cae 100644 --- a/internal/view/user.go +++ b/internal/view/user.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/xray.go b/internal/view/xray.go index 74fe8f8c54..d2ffd581e8 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/yaml.go b/internal/view/yaml.go index d95d7dc79b..a1998a26e3 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/view/yaml_test.go b/internal/view/yaml_test.go index 4c01a6a08f..c65d1a0e55 100644 --- a/internal/view/yaml_test.go +++ b/internal/view/yaml_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 5d358e6d63..70fbe5fb7f 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package watch import ( diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index 453df6054b..94abc1622c 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package watch import ( diff --git a/internal/watch/forwarders_test.go b/internal/watch/forwarders_test.go index aa061ba066..f6a22b5b83 100644 --- a/internal/watch/forwarders_test.go +++ b/internal/watch/forwarders_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package watch_test import ( diff --git a/internal/watch/helper.go b/internal/watch/helper.go index d75169a079..2381f4b425 100644 --- a/internal/watch/helper.go +++ b/internal/watch/helper.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package watch import ( diff --git a/internal/xray/container.go b/internal/xray/container.go index 67b8e013e2..1f87336ccf 100644 --- a/internal/xray/container.go +++ b/internal/xray/container.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/container_test.go b/internal/xray/container_test.go index d635525aeb..09d51b7345 100644 --- a/internal/xray/container_test.go +++ b/internal/xray/container_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/dp.go b/internal/xray/dp.go index f650f68349..bfc47ef077 100644 --- a/internal/xray/dp.go +++ b/internal/xray/dp.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/dp_test.go b/internal/xray/dp_test.go index c394a92a0a..7286e9dd7f 100644 --- a/internal/xray/dp_test.go +++ b/internal/xray/dp_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/ds.go b/internal/xray/ds.go index 3ce8e70713..fe55dc7158 100644 --- a/internal/xray/ds.go +++ b/internal/xray/ds.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/ds_test.go b/internal/xray/ds_test.go index 4c28cca2e0..40c4db113b 100644 --- a/internal/xray/ds_test.go +++ b/internal/xray/ds_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/generic.go b/internal/xray/generic.go index af73301964..7684385095 100644 --- a/internal/xray/generic.go +++ b/internal/xray/generic.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/generic_test.go b/internal/xray/generic_test.go index 24175b9a0d..61b1dc8910 100644 --- a/internal/xray/generic_test.go +++ b/internal/xray/generic_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/ns.go b/internal/xray/ns.go index 006e78c744..647ada3d35 100644 --- a/internal/xray/ns.go +++ b/internal/xray/ns.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/ns_test.go b/internal/xray/ns_test.go index c8fac72abc..ab214e9250 100644 --- a/internal/xray/ns_test.go +++ b/internal/xray/ns_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/pod.go b/internal/xray/pod.go index 1e3daadb70..ddac6d9193 100644 --- a/internal/xray/pod.go +++ b/internal/xray/pod.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/pod_test.go b/internal/xray/pod_test.go index 7d14430489..23dd18bc89 100644 --- a/internal/xray/pod_test.go +++ b/internal/xray/pod_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/rs.go b/internal/xray/rs.go index f920b3fea0..9dfda4c653 100644 --- a/internal/xray/rs.go +++ b/internal/xray/rs.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/rs_test.go b/internal/xray/rs_test.go index fab86f43dc..52e739f143 100644 --- a/internal/xray/rs_test.go +++ b/internal/xray/rs_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/sa.go b/internal/xray/sa.go index abc1359320..a39ae06285 100644 --- a/internal/xray/sa.go +++ b/internal/xray/sa.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/sa_test.go b/internal/xray/sa_test.go index 0cfadf2013..7afdf0deeb 100644 --- a/internal/xray/sa_test.go +++ b/internal/xray/sa_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/section.go b/internal/xray/section.go index 4cd377eb68..c458b5fdb7 100644 --- a/internal/xray/section.go +++ b/internal/xray/section.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/sts.go b/internal/xray/sts.go index 81ecf9c06f..0917c1965e 100644 --- a/internal/xray/sts.go +++ b/internal/xray/sts.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/sts_test.go b/internal/xray/sts_test.go index c939653a83..7d0444718e 100644 --- a/internal/xray/sts_test.go +++ b/internal/xray/sts_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/svc.go b/internal/xray/svc.go index d8bac3f92a..af535c7c1d 100644 --- a/internal/xray/svc.go +++ b/internal/xray/svc.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/svc_test.go b/internal/xray/svc_test.go index 8c6f28f31f..14be39e5eb 100644 --- a/internal/xray/svc_test.go +++ b/internal/xray/svc_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go index 9a962300f2..fb1a820f48 100644 --- a/internal/xray/tree_node.go +++ b/internal/xray/tree_node.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray import ( diff --git a/internal/xray/tree_node_test.go b/internal/xray/tree_node_test.go index fbce2e10c7..cd3442c7fb 100644 --- a/internal/xray/tree_node_test.go +++ b/internal/xray/tree_node_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package xray_test import ( diff --git a/main.go b/main.go index dd4d99ad38..13ce9520c7 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package main import ( From 6842392fa2a14bbbdb99fd5e46217174278c5b88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 08:05:23 -0700 Subject: [PATCH 006/169] Bump k8s.io/client-go from 0.28.3 to 0.28.4 (#2305) Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.28.3 to 0.28.4. - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.28.3...v0.28.4) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7ae9eb40a6..3746803863 100644 --- a/go.mod +++ b/go.mod @@ -23,11 +23,11 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.13.1 - k8s.io/api v0.28.3 + k8s.io/api v0.28.4 k8s.io/apiextensions-apiserver v0.28.3 - k8s.io/apimachinery v0.28.3 + k8s.io/apimachinery v0.28.4 k8s.io/cli-runtime v0.28.3 - k8s.io/client-go v0.28.3 + k8s.io/client-go v0.28.4 k8s.io/klog/v2 v2.110.1 k8s.io/kubectl v0.28.3 k8s.io/kubernetes v1.28.3 @@ -149,7 +149,7 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.28.3 // indirect diff --git a/go.sum b/go.sum index e4b479dc3c..4644ca004a 100644 --- a/go.sum +++ b/go.sum @@ -579,8 +579,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= @@ -607,18 +607,18 @@ helm.sh/helm/v3 v3.13.1 h1:DG+XLGzBJeZvMLlMbm6bPDLV1dGaVW9eZsDoUd1/LM0= helm.sh/helm/v3 v3.13.1/go.mod h1:TdQRMiq46CSWcc68Hb0uVhvAWusaN90YwAV54cz6JzU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= -k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= -k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= -k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w= k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM= k8s.io/cli-runtime v0.28.3 h1:lvuJYVkwCqHEvpS6KuTZsUVwPePFjBfSGvuaLl2SxzA= k8s.io/cli-runtime v0.28.3/go.mod h1:jeX37ZPjIcENVuXDDTskG3+FnVuZms5D9omDXS/2Jjc= -k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= -k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= From e5912e7041d7b85a9f552422c20cfa74a90c9cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 08:14:06 -0700 Subject: [PATCH 007/169] Bump k8s.io/cli-runtime from 0.28.3 to 0.28.4 (#2306) Bumps [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) from 0.28.3 to 0.28.4. - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.28.3...v0.28.4) --- updated-dependencies: - dependency-name: k8s.io/cli-runtime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3746803863..f43906bc22 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( k8s.io/api v0.28.4 k8s.io/apiextensions-apiserver v0.28.3 k8s.io/apimachinery v0.28.4 - k8s.io/cli-runtime v0.28.3 + k8s.io/cli-runtime v0.28.4 k8s.io/client-go v0.28.4 k8s.io/klog/v2 v2.110.1 k8s.io/kubectl v0.28.3 diff --git a/go.sum b/go.sum index 4644ca004a..b491f20482 100644 --- a/go.sum +++ b/go.sum @@ -615,8 +615,8 @@ k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w= k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM= -k8s.io/cli-runtime v0.28.3 h1:lvuJYVkwCqHEvpS6KuTZsUVwPePFjBfSGvuaLl2SxzA= -k8s.io/cli-runtime v0.28.3/go.mod h1:jeX37ZPjIcENVuXDDTskG3+FnVuZms5D9omDXS/2Jjc= +k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= +k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= From a208059e29af99cd6c6d79f508a427ac42a754d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 08:14:26 -0700 Subject: [PATCH 008/169] Bump helm.sh/helm/v3 from 3.13.1 to 3.13.2 (#2293) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.13.1 to 3.13.2. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.13.1...v3.13.2) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f43906bc22..d8f48f21d1 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.13.1 + helm.sh/helm/v3 v3.13.2 k8s.io/api v0.28.4 k8s.io/apiextensions-apiserver v0.28.3 k8s.io/apimachinery v0.28.4 diff --git a/go.sum b/go.sum index b491f20482..934ba4f465 100644 --- a/go.sum +++ b/go.sum @@ -603,8 +603,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.1 h1:DG+XLGzBJeZvMLlMbm6bPDLV1dGaVW9eZsDoUd1/LM0= -helm.sh/helm/v3 v3.13.1/go.mod h1:TdQRMiq46CSWcc68Hb0uVhvAWusaN90YwAV54cz6JzU= +helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= +helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= From 2d8fb9999317e74d6ac95b6d6a18b3ff14522cec Mon Sep 17 00:00:00 2001 From: ClementLachaussee <32433081+ClementLachaussee@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:03:27 +0100 Subject: [PATCH 009/169] feat: add imagePullSecrets and imagePullPolicy configuration for shellpod (#2301) * feat: add imagePullSecrets and imagePullPolicy in shell_pod for internal registry use cases * docs: add imagePullPolicy and imagePullSecrets configuration example * docs: remove comments * docs: use same wording * docs: remove useless phrase * fix: truncated comment * fix: use correct type, remove useless if * add: ImagePullPolicy on container variable --------- Co-authored-by: clementlachaussee --- README.md | 11 +++++++++++ internal/config/shell_pod.go | 14 ++++++++------ internal/view/exec.go | 6 ++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b3c0ad1394..3e78f7b8ab 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,11 @@ K9s uses aliases to navigate most K8s resources. image: killerAdmin # The namespace to launch to shell pod into. namespace: fred + # imagePullPolicy defaults to Always + imagePullPolicy: Always + # imagePullSecrets defaults to no secret + imagePullSecrets: + - name: my-regcred # The resource limit to set on the shell pod. limits: cpu: 100m @@ -429,6 +434,12 @@ k9s: shellPod: image: cool_kid_admin:42 namespace: blee + # imagePullPolicy defaults to Always + imagePullPolicy: Always + # imagePullSecrets defaults to no secret + imagePullSecrets: + - name: my-regcred + # The resource limit to set on the shell pod. limits: cpu: 100m memory: 100Mi diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index f1e21692fb..f4efe0bbb5 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -15,12 +15,14 @@ type Limits map[v1.ResourceName]string // ShellPod represents k9s shell configuration. type ShellPod struct { - Image string `json:"image"` - Command []string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` - Namespace string `json:"namespace"` - Limits Limits `json:"resources,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Image string `json:"image"` + ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"` + ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"` + Command []string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` + Namespace string `json:"namespace"` + Limits Limits `json:"resources,omitempty"` + Labels map[string]string `json:"labels,omitempty"` } // NewShellPod returns a new instance. diff --git a/internal/view/exec.go b/internal/view/exec.go index 691afc8a0a..2a74dd1eb8 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -360,8 +360,9 @@ func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod { log.Debug().Msgf("Shell Config %#v", cfg) c := v1.Container{ - Name: k9sShell, - Image: cfg.Image, + Name: k9sShell, + Image: cfg.Image, + ImagePullPolicy: cfg.ImagePullPolicy, VolumeMounts: []v1.VolumeMount{ { Name: "root-vol", @@ -393,6 +394,7 @@ func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod { RestartPolicy: v1.RestartPolicyNever, HostPID: true, HostNetwork: true, + ImagePullSecrets: cfg.ImagePullSecrets, TerminationGracePeriodSeconds: &grace, Volumes: []v1.Volume{ { From 3fc80f1005e431ec008952158617cff0a6729efb Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Fri, 24 Nov 2023 23:21:56 +0800 Subject: [PATCH 010/169] should not be clear screen when exeuting plugin (#2313) --- internal/view/actions.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/view/actions.go b/internal/view/actions.go index 8410911d40..713778175e 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -137,7 +137,6 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { cb := func() { opts := shellOpts{ - clear: true, binary: p.Command, background: p.Background, pipes: p.Pipes, From ac8395dded12d07ff417e935d4e311a66c0a7890 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Fri, 24 Nov 2023 23:28:51 +0800 Subject: [PATCH 011/169] chore: use k8s.io/kubernetes as a dependency is unsupported and not recommended (#2310) --- go.mod | 1 - go.sum | 6 ++---- internal/render/pod.go | 11 ++++++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d8f48f21d1..b53c95cc3f 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( k8s.io/client-go v0.28.4 k8s.io/klog/v2 v2.110.1 k8s.io/kubectl v0.28.3 - k8s.io/kubernetes v1.28.3 k8s.io/metrics v0.28.3 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index 934ba4f465..3e931e2c9d 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= -github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= @@ -627,8 +627,6 @@ k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5Ohx k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/kubectl v0.28.3 h1:H1Peu1O3EbN9zHkJCcvhiJ4NUj6lb88sGPO5wrWIM6k= k8s.io/kubectl v0.28.3/go.mod h1:RDAudrth/2wQ3Sg46fbKKl4/g+XImzvbsSRZdP2RiyE= -k8s.io/kubernetes v1.28.3 h1:XTci6gzk+JR51UZuZQCFJ4CsyUkfivSjLI4O1P9z6LY= -k8s.io/kubernetes v1.28.3/go.mod h1:NhAysZWvHtNcJFFHic87ofxQN7loylCQwg3ZvXVDbag= k8s.io/metrics v0.28.3 h1:w2s3kVi7HulXqCVDFkF4hN/OsL1tXTTb4Biif995h/g= k8s.io/metrics v0.28.3/go.mod h1:OZZ23AHFojPzU6r3xoHGRUcV3I9pauLua+07sAUbwLc= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= diff --git a/internal/render/pod.go b/internal/render/pod.go index 46d118e4f5..dd33265842 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -15,12 +15,17 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/util/node" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" "github.com/derailed/k9s/internal/client" ) +const ( + // NodeUnreachablePodReason is reason and message set on a pod when its state + // cannot be confirmed as kubelet is unresponsive on the node it is (was) running. + NodeUnreachablePodReason = "NodeLost" // k8s.io/kubernetes/pkg/util/node.NodeUnreachablePodReason +) + const ( PhaseTerminating = "Terminating" PhaseInitialized = "Initialized" @@ -309,7 +314,7 @@ func (*Pod) Statuses(ss []v1.ContainerStatus) (cr, ct, rc int) { func (p *Pod) Phase(po *v1.Pod) string { status := string(po.Status.Phase) if po.Status.Reason != "" { - if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" { + if po.DeletionTimestamp != nil && po.Status.Reason == NodeUnreachablePodReason { return "Unknown" } status = po.Status.Reason @@ -456,7 +461,7 @@ func PodStatus(pod *v1.Pod) string { } } - if pod.DeletionTimestamp != nil && pod.Status.Reason == node.NodeUnreachablePodReason { + if pod.DeletionTimestamp != nil && pod.Status.Reason == NodeUnreachablePodReason { reason = PhaseUnknown } else if pod.DeletionTimestamp != nil { reason = PhaseTerminating From 38275b25ea988efb60852de3a609867823650897 Mon Sep 17 00:00:00 2001 From: Alexandru Placinta Date: Sun, 26 Nov 2023 15:45:03 +0100 Subject: [PATCH 012/169] Allow both .yaml and .yml yaml config files (#2284) --- internal/config/alias.go | 2 +- internal/config/config.go | 24 ++++++++++++++++++++++++ internal/config/hotkey.go | 2 +- internal/config/plugin.go | 9 ++------- internal/config/styles.go | 2 +- internal/config/views.go | 2 +- internal/dao/popeye.go | 4 ++-- internal/ui/config.go | 2 +- 8 files changed, 33 insertions(+), 14 deletions(-) diff --git a/internal/config/alias.go b/internal/config/alias.go index 1b5848c2f1..f8900a018a 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -13,7 +13,7 @@ import ( ) // K9sAlias manages K9s aliases. -var K9sAlias = filepath.Join(K9sHome(), "alias.yml") +var K9sAlias = YamlExtension(filepath.Join(K9sHome(), "alias.yml")) // Alias tracks shortname to GVR mappings. type Alias map[string]string diff --git a/internal/config/config.go b/internal/config/config.go index 7342c1df8a..9ba4a61be7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/adrg/xdg" "github.com/derailed/k9s/internal/client" @@ -274,9 +275,32 @@ func (c *Config) Dump(msg string) { } } +// YamlExtension tries to find the correct extension for a YAML file +func YamlExtension(path string) string { + if !isYamlFile(path) { + log.Error().Msgf("Config: File %s is not a yaml file", path) + return path + } + + // Strip any extension, if there is no extension the path will remain unchanged + path = strings.TrimSuffix(path, filepath.Ext(path)) + result := path + ".yml" + + if _, err := os.Stat(result); os.IsNotExist(err) { + return path + ".yaml" + } + + return result +} + // ---------------------------------------------------------------------------- // Helpers... func isSet(s *string) bool { return s != nil && len(*s) > 0 } + +func isYamlFile(file string) bool { + ext := filepath.Ext(file) + return ext == ".yml" || ext == ".yaml" +} diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index ea7bd715a4..ef3041de59 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -11,7 +11,7 @@ import ( ) // K9sHotKeys manages K9s hotKeys. -var K9sHotKeys = filepath.Join(K9sHome(), "hotkey.yml") +var K9sHotKeys = YamlExtension(filepath.Join(K9sHome(), "hotkey.yml")) // HotKeys represents a collection of plugins. type HotKeys struct { diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 2deca3a4b2..b7b2fa67e0 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -15,7 +15,7 @@ import ( ) // K9sPluginsFilePath manages K9s plugins. -var K9sPluginsFilePath = filepath.Join(K9sHome(), "plugin.yml") +var K9sPluginsFilePath = YamlExtension(filepath.Join(K9sHome(), "plugin.yml")) var K9sPluginDirectory = filepath.Join("k9s", "plugins") // Plugins represents a collection of plugins. @@ -73,7 +73,7 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { continue } for _, file := range pluginFiles { - if file.IsDir() || !isYamlFile(file) { + if file.IsDir() || !isYamlFile(file.Name()) { continue } pluginFile, err := os.ReadFile(filepath.Join(pluginDir, file.Name())) @@ -94,8 +94,3 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { return nil } - -func isYamlFile(file os.DirEntry) bool { - ext := filepath.Ext(file.Name()) - return ext == ".yml" || ext == ".yaml" -} diff --git a/internal/config/styles.go b/internal/config/styles.go index 26bb653350..62a192a0d3 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -14,7 +14,7 @@ import ( ) // K9sStylesFile represents K9s skins file location. -var K9sStylesFile = filepath.Join(K9sHome(), "skin.yml") +var K9sStylesFile = YamlExtension(filepath.Join(K9sHome(), "skin.yml")) // StyleListener represents a skin's listener. type StyleListener interface { diff --git a/internal/config/views.go b/internal/config/views.go index 929c14b284..74907e4347 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -11,7 +11,7 @@ import ( ) // K9sViewConfigFile represents the location for the views configuration. -var K9sViewConfigFile = filepath.Join(K9sHome(), "views.yml") +var K9sViewConfigFile = YamlExtension(filepath.Join(K9sHome(), "views.yml")) // ViewConfigListener represents a view config listener. type ViewConfigListener interface { diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 962f0816eb..2f50906c74 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -68,9 +68,9 @@ func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) flags.Sections = §ions flags.ActiveNamespace = &ns } - spinach := filepath.Join(cfg.K9sHome(), "spinach.yml") + spinach := cfg.YamlExtension(filepath.Join(cfg.K9sHome(), "spinach.yml")) if c, err := p.GetFactory().Client().Config().CurrentContextName(); err == nil { - spinach = filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yml", c)) + spinach = cfg.YamlExtension(filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yml", c))) } if _, err := os.Stat(spinach); err == nil { flags.Spinach = &spinach diff --git a/internal/ui/config.go b/internal/ui/config.go index cc177fa6f8..6ab5af1f50 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -130,7 +130,7 @@ func BenchConfig(context string) string { func (c *Configurator) RefreshStyles(context string) { c.BenchFile = BenchConfig(context) - clusterSkins := filepath.Join(config.K9sHome(), fmt.Sprintf("%s_skin.yml", context)) + clusterSkins := config.YamlExtension(filepath.Join(config.K9sHome(), fmt.Sprintf("%s_skin.yml", context))) if c.Styles == nil { c.Styles = config.NewStyles() } else { From fcfff5701ea56c71f5f0997ea1c7cdd11f7cbd13 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Mon, 27 Nov 2023 08:17:46 +0800 Subject: [PATCH 013/169] add suggestions for context and resources on the command bar (#2285) * add suggestions for context and resources on the command bar * instead strings.Fields * cacheable and provide test cases --- internal/view/app.go | 87 ++++++++++++++++++++++++++++++++++++--- internal/view/app_test.go | 33 +++++++++++++-- 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/internal/view/app.go b/internal/view/app.go index 89a4e147e8..4d1d2fc4ce 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -151,6 +151,16 @@ func (a *App) initSignals() { } func (a *App) suggestCommand() model.SuggestionFunc { + namespaceNames, err := a.namespaceNames() + if err != nil { + log.Error().Err(err).Msg("failed to list namespaces") + } + + contextNames, err := a.contextNames() + if err != nil { + log.Error().Err(err).Msg("failed to list contexts") + } + return func(s string) (entries sort.StringSlice) { if s == "" { if a.cmdHistory.Empty() { @@ -161,13 +171,12 @@ func (a *App) suggestCommand() model.SuggestionFunc { s = strings.ToLower(s) for _, k := range a.command.alias.Aliases.Keys() { - if k == s { - continue - } - if strings.HasPrefix(k, s) { - entries = append(entries, strings.Replace(k, s, "", 1)) + if suggest, ok := shouldAddSuggest(s, k); ok { + entries = append(entries, suggest) } } + + entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { return nil } @@ -176,6 +185,32 @@ func (a *App) suggestCommand() model.SuggestionFunc { } } +func (a *App) namespaceNames() ([]string, error) { + namespaces, err := a.factory.Client().ValidNamespaces() + if err != nil { + return nil, err + } + + namespaceNames := make([]string, 0, len(namespaces)) + for _, namespace := range namespaces { + namespaceNames = append(namespaceNames, namespace.Name) + } + return namespaceNames, nil +} + +func (a *App) contextNames() ([]string, error) { + contexts, err := a.factory.Client().Config().Contexts() + if err != nil { + return nil, err + } + + contextNames := make([]string, 0, len(contexts)) + for ctxName := range contexts { + contextNames = append(contextNames, ctxName) + } + return contextNames, nil +} + func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey { if k, ok := a.HasAction(ui.AsKey(evt)); ok && !a.Content.IsTopDialog() { return k.Action(evt) @@ -671,3 +706,45 @@ func (a *App) clusterInfo() *ClusterInfo { func (a *App) statusIndicator() *ui.StatusIndicator { return a.Views()["statusIndicator"].(*ui.StatusIndicator) } + +// ---------------------------------------------------------------------------- +// Helpers + +func suggestSubCommand(command string, namespaces, contexts []string) []string { + cmds := strings.Fields(command) + if len(cmds[0]) == 0 || len(cmds) != 2 { + return nil + } + + var suggests []string + switch strings.ToLower(cmds[0]) { + case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir": + return nil // ignore special commands + case "ctx", "context", "contexts": + for _, ctxName := range contexts { + if suggest, ok := shouldAddSuggest(cmds[1], ctxName); ok { + suggests = append(suggests, suggest) + } + } + default: + if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok { + suggests = append(suggests, suggest) + } + + for _, ns := range namespaces { + if suggest, ok := shouldAddSuggest(cmds[1], ns); ok { + suggests = append(suggests, suggest) + } + } + } + + return suggests +} + +func shouldAddSuggest(command, suggest string) (string, bool) { + if command != suggest && strings.HasPrefix(suggest, command) { + return strings.TrimPrefix(suggest, command), true + } + + return "", false +} diff --git a/internal/view/app_test.go b/internal/view/app_test.go index e214d7c4db..42555251d9 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -1,19 +1,46 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package view_test +package view import ( "testing" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" ) func TestAppNew(t *testing.T) { - a := view.NewApp(config.NewConfig(ks{})) + a := NewApp(config.NewConfig(ks{})) _ = a.Init("blee", 10) assert.Equal(t, 11, len(a.GetActions())) } + +func Test_suggestSubCommand(t *testing.T) { + namespaceNames := []string{"kube-system", "kube-public", "default", "nginx-ingress"} + contextNames := []string{"develop", "test", "pre", "prod"} + + tests := []struct { + Command string + Suggestions []string + }{ + {Command: "q", Suggestions: nil}, + {Command: "xray dp", Suggestions: nil}, + {Command: "help k", Suggestions: nil}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx pr", Suggestions: []string{"e", "od"}}, + {Command: "context d", Suggestions: []string{"evelop"}}, + {Command: "contexts t", Suggestions: []string{"est"}}, + {Command: "po ", Suggestions: nil}, + {Command: "po x", Suggestions: nil}, + {Command: "po k", Suggestions: []string{"ube-system", "ube-public"}}, + {Command: "po kube-", Suggestions: []string{"system", "public"}}, + } + + for _, tt := range tests { + got := suggestSubCommand(tt.Command, namespaceNames, contextNames) + assert.Equal(t, tt.Suggestions, got) + } +} From ce147ebc8cc5d22b51373cc32e393eed23a29969 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:48:31 -0700 Subject: [PATCH 014/169] Bump k8s.io/kubectl from 0.28.3 to 0.28.4 (#2318) Bumps [k8s.io/kubectl](https://github.com/kubernetes/kubectl) from 0.28.3 to 0.28.4. - [Commits](https://github.com/kubernetes/kubectl/compare/v0.28.3...v0.28.4) --- updated-dependencies: - dependency-name: k8s.io/kubectl dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b53c95cc3f..f1ea9c39ba 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( k8s.io/cli-runtime v0.28.4 k8s.io/client-go v0.28.4 k8s.io/klog/v2 v2.110.1 - k8s.io/kubectl v0.28.3 - k8s.io/metrics v0.28.3 + k8s.io/kubectl v0.28.4 + k8s.io/metrics v0.28.4 sigs.k8s.io/yaml v1.4.0 ) @@ -152,7 +152,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.28.3 // indirect - k8s.io/component-base v0.28.3 // indirect + k8s.io/component-base v0.28.4 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect oras.land/oras-go v1.2.4 // indirect diff --git a/go.sum b/go.sum index 3e931e2c9d..c56fff5519 100644 --- a/go.sum +++ b/go.sum @@ -619,16 +619,16 @@ k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= -k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= +k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubectl v0.28.3 h1:H1Peu1O3EbN9zHkJCcvhiJ4NUj6lb88sGPO5wrWIM6k= -k8s.io/kubectl v0.28.3/go.mod h1:RDAudrth/2wQ3Sg46fbKKl4/g+XImzvbsSRZdP2RiyE= -k8s.io/metrics v0.28.3 h1:w2s3kVi7HulXqCVDFkF4hN/OsL1tXTTb4Biif995h/g= -k8s.io/metrics v0.28.3/go.mod h1:OZZ23AHFojPzU6r3xoHGRUcV3I9pauLua+07sAUbwLc= +k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ= +k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c= +k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk= +k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= From b360de3565ed8826f779bca9ef88324ef83e7bf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:49:25 -0700 Subject: [PATCH 015/169] Bump github.com/derailed/tview from 0.8.1 to 0.8.2 (#2316) Bumps [github.com/derailed/tview](https://github.com/derailed/tview) from 0.8.1 to 0.8.2. - [Commits](https://github.com/derailed/tview/compare/v0.8.1...v0.8.2) --- updated-dependencies: - dependency-name: github.com/derailed/tview dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f1ea9c39ba..02e8554801 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.1 github.com/derailed/tcell/v2 v2.3.1-rc.3 - github.com/derailed/tview v0.8.1 + github.com/derailed/tview v0.8.2 github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/fvbommel/sortorder v1.1.0 diff --git a/go.sum b/go.sum index c56fff5519..9d21d6ee8b 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/derailed/popeye v0.11.1 h1:bjt5mXkcXY696ipuJqwY1sa5s3i431L9BlkQc6Euaq github.com/derailed/popeye v0.11.1/go.mod h1:NkvjHH1F94tE7Ui17PlYiagQcFt7yXUV2hIhPzSK+0w= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= -github.com/derailed/tview v0.8.1 h1:hvNR3LLrWEuaQbPYfBnRn7bYkxCP26K6nX9J+MGlhyw= -github.com/derailed/tview v0.8.1/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw= +github.com/derailed/tview v0.8.2 h1:8b+QwVECV1lZ6VV7Vf1tergpJxJ+ReA/JhIBYyUVSFI= +github.com/derailed/tview v0.8.2/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= From c639b6a5cf77a3f1df33c9f8d79c6e4e109ebd4a Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Tue, 28 Nov 2023 22:46:44 +0800 Subject: [PATCH 016/169] fix namespace suggestion error on context switch (#2315) --- internal/view/app.go | 10 ++++----- internal/view/app_int_test.go | 38 +++++++++++++++++++++++++++++++++++ internal/view/app_test.go | 33 +++--------------------------- 3 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 internal/view/app_int_test.go diff --git a/internal/view/app.go b/internal/view/app.go index 4d1d2fc4ce..214f349c65 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -151,11 +151,6 @@ func (a *App) initSignals() { } func (a *App) suggestCommand() model.SuggestionFunc { - namespaceNames, err := a.namespaceNames() - if err != nil { - log.Error().Err(err).Msg("failed to list namespaces") - } - contextNames, err := a.contextNames() if err != nil { log.Error().Err(err).Msg("failed to list contexts") @@ -176,6 +171,11 @@ func (a *App) suggestCommand() model.SuggestionFunc { } } + namespaceNames, err := a.namespaceNames() + if err != nil { + log.Error().Err(err).Msg("failed to list namespaces") + } + entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { return nil diff --git a/internal/view/app_int_test.go b/internal/view/app_int_test.go new file mode 100644 index 0000000000..f20a26c098 --- /dev/null +++ b/internal/view/app_int_test.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_suggestSubCommand(t *testing.T) { + namespaceNames := []string{"kube-system", "kube-public", "default", "nginx-ingress"} + contextNames := []string{"develop", "test", "pre", "prod"} + + tests := []struct { + Command string + Suggestions []string + }{ + {Command: "q", Suggestions: nil}, + {Command: "xray dp", Suggestions: nil}, + {Command: "help k", Suggestions: nil}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx pr", Suggestions: []string{"e", "od"}}, + {Command: "context d", Suggestions: []string{"evelop"}}, + {Command: "contexts t", Suggestions: []string{"est"}}, + {Command: "po ", Suggestions: nil}, + {Command: "po x", Suggestions: nil}, + {Command: "po k", Suggestions: []string{"ube-system", "ube-public"}}, + {Command: "po kube-", Suggestions: []string{"system", "public"}}, + } + + for _, tt := range tests { + got := suggestSubCommand(tt.Command, namespaceNames, contextNames) + assert.Equal(t, tt.Suggestions, got) + } +} diff --git a/internal/view/app_test.go b/internal/view/app_test.go index 42555251d9..e214d7c4db 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -1,46 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package view +package view_test import ( "testing" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" ) func TestAppNew(t *testing.T) { - a := NewApp(config.NewConfig(ks{})) + a := view.NewApp(config.NewConfig(ks{})) _ = a.Init("blee", 10) assert.Equal(t, 11, len(a.GetActions())) } - -func Test_suggestSubCommand(t *testing.T) { - namespaceNames := []string{"kube-system", "kube-public", "default", "nginx-ingress"} - contextNames := []string{"develop", "test", "pre", "prod"} - - tests := []struct { - Command string - Suggestions []string - }{ - {Command: "q", Suggestions: nil}, - {Command: "xray dp", Suggestions: nil}, - {Command: "help k", Suggestions: nil}, - {Command: "ctx p", Suggestions: []string{"re", "rod"}}, - {Command: "ctx p", Suggestions: []string{"re", "rod"}}, - {Command: "ctx pr", Suggestions: []string{"e", "od"}}, - {Command: "context d", Suggestions: []string{"evelop"}}, - {Command: "contexts t", Suggestions: []string{"est"}}, - {Command: "po ", Suggestions: nil}, - {Command: "po x", Suggestions: nil}, - {Command: "po k", Suggestions: []string{"ube-system", "ube-public"}}, - {Command: "po kube-", Suggestions: []string{"system", "public"}}, - } - - for _, tt := range tests { - got := suggestSubCommand(tt.Command, namespaceNames, contextNames) - assert.Equal(t, tt.Suggestions, got) - } -} From 0c642c6786e18dbf6483106dce3930f938cc3421 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 29 Nov 2023 23:56:27 +0800 Subject: [PATCH 017/169] proper handling of help commands (fixes #2154) (#2319) --- internal/ui/prompt.go | 8 ++++++++ internal/ui/prompt_test.go | 13 +++++++++++++ internal/view/app.go | 6 +++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index fd4051956a..bcc7271035 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -122,6 +122,14 @@ func (p *Prompt) SendStrokes(s string) { } } +// Deactivate sets the prompt as inactive. +func (p *Prompt) Deactivate() { + if p.model != nil { + p.model.ClearText(true) + p.model.SetActive(false) + } +} + // SetModel sets the prompt buffer model. func (p *Prompt) SetModel(m PromptModel) { if p.model != nil { diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go index bbe456a374..1348ca7301 100644 --- a/internal/ui/prompt_test.go +++ b/internal/ui/prompt_test.go @@ -51,6 +51,19 @@ func TestCmdMode(t *testing.T) { } } +func TestPrompt_Deactivate(t *testing.T) { + model := model.NewFishBuff(':', model.CommandBuffer) + v := ui.NewPrompt(&ui.App{}, true, config.NewStyles()) + v.SetModel(model) + model.AddListener(v) + + model.SetActive(true) + if assert.True(t, v.InCmdMode()) { + v.Deactivate() + assert.False(t, v.InCmdMode()) + } +} + // Tests that, when active, the prompt has the appropriate color func TestPromptColor(t *testing.T) { styles := config.NewStyles() diff --git a/internal/view/app.go b/internal/view/app.go index 214f349c65..7de20d7888 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -641,12 +641,11 @@ func (a *App) dirCmd(path string) error { } func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { - top := a.Content.Top() - - if a.CmdBuff().InCmdMode() || (top != nil && top.InCmdMode()) { + if evt != nil && evt.Rune() == '?' && a.Prompt().InCmdMode() { return evt } + top := a.Content.Top() if top != nil && top.Name() == "help" { a.Content.Pop() return nil @@ -656,6 +655,7 @@ func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { a.Flash().Err(err) } + a.Prompt().Deactivate() return nil } From 4e37faf38347d35bbbdbb598e2326278979399c4 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sat, 2 Dec 2023 06:51:40 +0800 Subject: [PATCH 018/169] check if the service provides selectors (#2322) --- internal/view/svc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/view/svc.go b/internal/view/svc.go index dbbfccfb0e..b47bd01295 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -66,6 +66,10 @@ func (s *Service) showPods(a *App, _ ui.Tabular, gvr, path string) { a.Flash().Warnf("No matching pods. Service %s is an external service.", path) return } + if svc.Spec.Selector == nil { + a.Flash().Warnf("No matching pods. Service %s does not provide any selectors", path) + return + } showPodsWithLabels(a, path, svc.Spec.Selector) } From a44cb6135c1fec94e3777af2cb6aedcb8f18207d Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 6 Dec 2023 19:19:44 -0700 Subject: [PATCH 019/169] K9s Release v0.29.0 (#2326) * Feat: Move shell pod cluster config to general config > BREAKING CHANGE! K9s configuration breaking change! Shellpod specification will no longer reside with a cluster configuration. It is now part of the global K9s configuration object. Shellpod configuration should be part of k9s config. Clusters admins will most likely use the same image and config to run shells on their nodes. Each cluster in turn will have the option to either enable/disable shelling into nodes. This not only DRYs up the k9s config but also allows user to consolidate their shell pod configuration in one central place. * Fix #2290 - Add freebsd assets * Maintenance cleaning up * Fix #2166 - Add taint tracking column to node view * Fix #2009: Update screendump file names to contain resource info * Maintenance: Cleanup errror messages * Fix #1513: Change log default to tail vs last 5min * Fix #2166: Add taint indicator on node view * Fix #2165: Track init co restarts * Fix #2308: Fix rbac auth checks * Fix #2036: Fix npe on filtering CRDs * Fix #2219: Turn on TTY option on shellpod * Fix #2167: Update color escape sequence on copy * Fix #2297: Enable multi select on nodes * Cleanup headers * Fix #2162: Allow edit when describing/viewing * Feat: Add helm release history support * Fix #2039: Command Arrow up/down + enter support * Small refactor * Add img vulenerability scans support * Change skin loading and support - Move skin specification to k9s cluster config section - Load skins for skins dir * Release v0.29.0 docs --- .golangci.yml | 4 +- .goreleaser.yml | 55 +- Makefile | 2 +- README.md | 84 +- change_logs/release_v0.29.0.md | 212 +++ go.mod | 208 ++- go.sum | 1392 ++++++++++++++++- internal/client/client.go | 5 +- internal/client/metrics.go | 2 +- internal/client/types.go | 3 + internal/config/cluster.go | 8 +- internal/config/color.go | 61 + internal/config/config.go | 4 + internal/config/config_test.go | 54 +- internal/config/helpers.go | 2 +- internal/config/k9s.go | 8 + internal/config/logger.go | 4 +- internal/config/plugin.go | 11 +- internal/config/shell_pod.go | 17 +- internal/config/styles.go | 52 - internal/dao/benchmark.go | 3 - internal/dao/cronjob.go | 35 +- internal/dao/dp.go | 25 +- internal/dao/ds.go | 12 + internal/dao/generic.go | 3 - internal/dao/{helm.go => helm_chart.go} | 58 +- internal/dao/helm_history.go | 136 ++ internal/dao/img_scan.go | 67 + internal/dao/job.go | 33 +- internal/dao/log_item.go | 3 - internal/dao/node.go | 3 - internal/dao/pod.go | 27 +- internal/dao/registry.go | 100 +- internal/dao/rs.go | 19 +- internal/dao/sts.go | 12 + internal/dao/types.go | 6 + internal/keys.go | 54 +- internal/model/describe.go | 5 + internal/model/registry.go | 20 +- internal/model/types.go | 1 + internal/model/values.go | 9 +- internal/model/yaml.go | 5 + internal/render/benchmark.go | 8 +- internal/render/container.go | 6 +- internal/render/cr.go | 2 +- internal/render/crb.go | 4 +- internal/render/crd.go | 8 +- internal/render/cronjob.go | 20 +- internal/render/dp.go | 19 +- internal/render/ds.go | 19 +- internal/render/ep.go | 4 +- internal/render/generic.go | 3 - internal/render/helm.go | 97 -- internal/render/helm/chart.go | 91 ++ internal/render/helm/history.go | 68 + internal/render/helpers.go | 18 +- internal/render/helpers_test.go | 2 +- internal/render/img_scan.go | 107 ++ internal/render/job.go | 19 +- internal/render/node.go | 6 +- internal/render/node_test.go | 4 +- internal/render/np.go | 4 +- internal/render/ns.go | 6 +- internal/render/ofaas.go | 116 -- internal/render/ofaas_test.go | 39 - internal/render/pdb.go | 6 +- internal/render/pod.go | 37 +- internal/render/pv.go | 6 +- internal/render/pvc.go | 6 +- internal/render/ro.go | 4 +- internal/render/rob.go | 4 +- internal/render/row.go | 28 +- internal/render/row_event.go | 47 - internal/render/rs.go | 19 +- internal/render/sa.go | 4 +- internal/render/sc.go | 4 +- internal/render/screen_dump.go | 3 +- internal/render/sts.go | 19 +- internal/render/subject.go | 2 +- internal/render/svc.go | 6 +- internal/ui/config.go | 40 +- internal/ui/dialog/error.go | 2 +- internal/ui/prompt.go | 11 +- internal/ui/table_helper.go | 8 +- internal/view/app.go | 25 +- internal/view/benchmark.go | 2 +- internal/view/browser.go | 51 +- internal/view/command.go | 27 +- internal/view/cow.go | 2 +- internal/view/cronjob.go | 2 +- internal/view/details.go | 35 +- internal/view/dir.go | 8 +- internal/view/dp.go | 24 +- internal/view/ds.go | 8 +- internal/view/exec.go | 26 +- internal/view/{helm.go => helm_chart.go} | 56 +- internal/view/helm_history.go | 109 ++ internal/view/helpers.go | 6 +- internal/view/img_scan.go | 85 + internal/view/job.go | 2 +- internal/view/live_view.go | 51 +- internal/view/live_view_test.go | 16 + internal/view/logs_extender.go | 2 +- internal/view/node.go | 8 +- internal/view/pod.go | 6 +- internal/view/registrar.go | 9 +- internal/view/rs.go | 2 +- internal/view/secret.go | 2 +- internal/view/sts.go | 10 +- internal/view/vul_extender.go | 50 + internal/view/xray.go | 8 +- internal/view/yaml.go | 7 +- internal/vul/scan.go | 81 + internal/vul/scanner.go | 220 +++ internal/vul/scorer.go | 45 + internal/vul/scorer_test.go | 96 ++ internal/vul/table.go | 184 +++ internal/vul/table_test.go | 86 + internal/vul/tally.go | 74 + internal/vul/tally_test.go | 57 + internal/vul/testdata/sort/dups/sc1.text | 9 + internal/vul/testdata/sort/dups/sc2.text | 6 + internal/vul/testdata/sort/full/sc1.text | 56 + internal/vul/testdata/sort/full/sc2.text | 29 + internal/vul/testdata/sort/no_dups/sc1.text | 2 + internal/vul/testdata/sort/no_dups/sc2.text | 2 + internal/vul/testdata/sort_sev/dups/sc1.text | 9 + internal/vul/testdata/sort_sev/dups/sc2.text | 6 + internal/vul/testdata/sort_sev/full/sc1.text | 56 + internal/vul/testdata/sort_sev/full/sc2.text | 29 + .../vul/testdata/sort_sev/no_dups/sc1.text | 2 + .../vul/testdata/sort_sev/no_dups/sc2.text | 2 + internal/vul/types.go | 24 + internal/watch/factory.go | 4 +- internal/xray/container.go | 2 +- internal/xray/dp.go | 2 +- internal/xray/ds.go | 2 +- internal/xray/ns.go | 2 +- internal/xray/pod.go | 2 +- internal/xray/rs.go | 2 +- internal/xray/section.go | 2 +- internal/xray/sts.go | 2 +- internal/xray/svc.go | 2 +- main.go | 6 - 144 files changed, 4506 insertions(+), 986 deletions(-) create mode 100644 change_logs/release_v0.29.0.md create mode 100644 internal/config/color.go rename internal/dao/{helm.go => helm_chart.go} (56%) create mode 100644 internal/dao/helm_history.go create mode 100644 internal/dao/img_scan.go delete mode 100644 internal/render/helm.go create mode 100644 internal/render/helm/chart.go create mode 100644 internal/render/helm/history.go create mode 100644 internal/render/img_scan.go delete mode 100644 internal/render/ofaas.go delete mode 100644 internal/render/ofaas_test.go rename internal/view/{helm.go => helm_chart.go} (50%) create mode 100644 internal/view/helm_history.go create mode 100644 internal/view/img_scan.go create mode 100644 internal/view/vul_extender.go create mode 100644 internal/vul/scan.go create mode 100644 internal/vul/scanner.go create mode 100644 internal/vul/scorer.go create mode 100644 internal/vul/scorer_test.go create mode 100644 internal/vul/table.go create mode 100644 internal/vul/table_test.go create mode 100644 internal/vul/tally.go create mode 100644 internal/vul/tally_test.go create mode 100644 internal/vul/testdata/sort/dups/sc1.text create mode 100644 internal/vul/testdata/sort/dups/sc2.text create mode 100644 internal/vul/testdata/sort/full/sc1.text create mode 100644 internal/vul/testdata/sort/full/sc2.text create mode 100644 internal/vul/testdata/sort/no_dups/sc1.text create mode 100644 internal/vul/testdata/sort/no_dups/sc2.text create mode 100644 internal/vul/testdata/sort_sev/dups/sc1.text create mode 100644 internal/vul/testdata/sort_sev/dups/sc2.text create mode 100644 internal/vul/testdata/sort_sev/full/sc1.text create mode 100644 internal/vul/testdata/sort_sev/full/sc2.text create mode 100644 internal/vul/testdata/sort_sev/no_dups/sc1.text create mode 100644 internal/vul/testdata/sort_sev/no_dups/sc2.text create mode 100644 internal/vul/types.go diff --git a/.golangci.yml b/.golangci.yml index 78a2a5885f..f880f84a8d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,10 +1,10 @@ # options for analysis running run: # default concurrency is a available CPU number - concurrency: 4 + concurrency: 8 # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 1m + timeout: 5m # exit code when at least one issue was found, default is 1 issues-exit-code: 1 diff --git a/.goreleaser.yml b/.goreleaser.yml index dc7a1d2c56..ae771143bb 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,13 +5,15 @@ before: - go generate ./... release: prerelease: false + +env: + - CGO_ENABLED=0 + builds: - - env: - - CGO_ENABLED=0 + - id: linux goos: - linux - - darwin - - windows + - freebsd goarch: - amd64 - arm64 @@ -27,15 +29,40 @@ builds: - -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}} - -s -w -X github.com/derailed/k9s/cmd.date={{.Date}} + - id: osx + goos: + - darwin + goarch: + - amd64 + - arm64 + flags: + - -trimpath + ldflags: + - -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}} + - -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}} + - -s -w -X github.com/derailed/k9s/cmd.date={{.Date}} + + - id: windows + goos: + - windows + goarch: + - amd64 + - arm64 + flags: + - -trimpath + ldflags: + - -s -w -X github.com/derailed/k9s/cmd.version=v{{.Version}} + - -s -w -X github.com/derailed/k9s/cmd.commit={{.Commit}} + - -s -w -X github.com/derailed/k9s/cmd.date={{.Date}} + archives: - - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" - replacements: - darwin: Darwin - linux: Linux - windows: Windows - bit: Arm - bitv6: Arm6 - bitv7: Arm7 + - name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}amd64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} format_overrides: - goos: windows format: zip @@ -55,7 +82,7 @@ changelog: brews: - name: k9s - tap: + repository: owner: derailed name: homebrew-k9s commit_author: @@ -69,8 +96,6 @@ brews: nfpms: - file_name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' - replacements: - linux: Linux maintainer: Fernand Galiana homepage: https://k9scli.io description: Kubernetes CLI To Manage Your Clusters In Style! diff --git a/Makefile b/Makefile index e59570082b..7409077e08 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.28.2 +VERSION ?= v0.29.0 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index 3e78f7b8ab..ae3a89c0ee 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ K9s uses aliases to navigate most K8s resources. ## K9s Configuration - K9s keeps its configurations inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from. + K9s keeps its configurations as YAML files inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from. | Unix | macOS | Windows | |-----------------|------------------------------------|-----------------------| @@ -321,6 +321,8 @@ K9s uses aliases to navigate most K8s resources. > NOTE: This is still in flux and will change while in pre-release stage! + > NOTE! Thanks to [Mr Alexandru Placenta](https://github.com/placintaalexandru) the config files can now use either `.yml` or `.yaml` mimes. + ```yaml # $XDG_CONFIG_HOME/k9s/config.yml k9s: @@ -350,8 +352,8 @@ K9s uses aliases to navigate most K8s resources. tail: 200 # Defines the total number of log lines to allow in the view. Default 1000 buffer: 500 - # Represents how far to go back in the log timeline in seconds. Setting to -1 will show all available logs. Default is 5min. - sinceSeconds: 300 + # Represents how far to go back in the log timeline in seconds. Setting to -1 will tail logs. Default is -1. + sinceSeconds: 300 # => tail the last 5 mins. # Go full screen while displaying logs. Default false fullScreenLogs: false # Toggles log line wrap. Default false @@ -364,6 +366,18 @@ K9s uses aliases to navigate most K8s resources. currentCluster: minikube # KeepMissingClusters will keep clusters in the config if they are missing from the current kubeconfig file. Default false KeepMissingClusters: false + # Provide shell pod customization when nodeShell feature gate is enabled! + shellPod: + # The shell pod image to use. + image: killerAdmin + # The namespace to launch to shell pod into. + namespace: default + # The resource limit to set on the shell pod. + limits: + cpu: 100m + memory: 100Mi + # Enable TTY + tty: true # Persists per cluster preferences for favorite namespaces and view. clusters: coolio: @@ -378,22 +392,7 @@ K9s uses aliases to navigate most K8s resources. active: po featureGates: # Toggles NodeShell support. Allow K9s to shell into nodes if needed. Default false. - nodeShell: false - # Provide shell pod customization of feature gate is enabled - shellPod: - # The shell pod image to use. - image: killerAdmin - # The namespace to launch to shell pod into. - namespace: fred - # imagePullPolicy defaults to Always - imagePullPolicy: Always - # imagePullSecrets defaults to no secret - imagePullSecrets: - - name: my-regcred - # The resource limit to set on the shell pod. - limits: - cpu: 100m - memory: 100Mi + nodeShell: true # The IP Address to use when launching a port-forward. portForwardAddress: 1.2.3.4 kind: @@ -424,25 +423,19 @@ By enabling the nodeShell feature gate on a given cluster, K9s allows you to she ```yaml # $XDG_CONFIG_HOME/k9s/config.yml k9s: + # You can also further tune the shell pod specification + shellPod: + image: cool_kid_admin:42 + namespace: blee + limits: + cpu: 100m + memory: 100Mi clusters: # Configures node shell on cluster blee blee: featureGates: # You must enable the nodeShell feature gate to enable shelling into nodes nodeShell: true - # You can also further tune the shell pod specification - shellPod: - image: cool_kid_admin:42 - namespace: blee - # imagePullPolicy defaults to Always - imagePullPolicy: Always - # imagePullSecrets defaults to no secret - imagePullSecrets: - - name: my-regcred - # The resource limit to set on the shell pod. - limits: - cpu: 100m - memory: 100Mi ``` --- @@ -809,21 +802,36 @@ Example: Dracula Skin ;) Dracula Skin -You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s skins are loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin would be loaded if not the current stock skin remains in effect. +You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s default skin is loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin will be loaded if not the current stock skin remains in effect. -You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify the skin file name as `$XDG_CONFIG_HOME/k9s/mycontext_skin.yml` -Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your user's home dir as `skin.yml`. +You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin!) and copy this repo skins/dracula.yml to `$XDG_CONFIG_HOME/k9s/skins` directory. +Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your k9s home dir as `skin.yml`. Colors can be defined by name or using a hex representation. Of recent, we've added a color named `default` to indicate a transparent background color to preserve your terminal background color settings if so desired. > NOTE: This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly! - - > NOTE: Please see [K9s Skins](https://k9scli.io/topics/skins/) for a list of available colors. +```yaml +# Make cluster fred display in_the_navy skin when loaded... +k9s: + ... + clusters: + fred: + # Override the default skin and use this skin for this cluster. + # NOTE: Just the skin file name to extension! + skin: in_the_navy # -> Look for a skin file in ~/.config/k9s/skins/in_the_navy.yml + namespace: + ... + view: + active: pod + featureGates: + nodeShell: false + portForwardAddress: localhost +``` ```yaml -# Skin InTheNavy... +# in_the_navy.yml: Skin InTheNavy... k9s: # General K9s styles body: diff --git a/change_logs/release_v0.29.0.md b/change_logs/release_v0.29.0.md new file mode 100644 index 0000000000..927d84a794 --- /dev/null +++ b/change_logs/release_v0.29.0.md @@ -0,0 +1,212 @@ + + +# Release v0.29.0 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +## ♫ Sounds Behind The Release ♭ + +* [Snowbound - Donald Fagen](https://www.youtube.com/watch?v=bj8ZdBdKsfo) +* [Pilgrim - Eric Clapton](https://www.youtube.com/watch?v=8V9tSQuIzbQ) +* [Lucky Number - Lene Lovich](https://www.youtube.com/watch?v=KnIJOO__jVo) + +--- + +## 🦃 Happy (Belated!) ThanksGiving To All! 🦃 + +Hope you and yours had a wonderful holiday!! +Hopefully this drop won't be a cold turkey 😳 + +I'd like to take this opportunity to honor two very special folks: + +* [Alexandru Placinta](https://github.com/placintaalexandru) +* [Jayson Wang](https://github.com/wjiec) + +These guys have been relentless in fishing out bugs, helping out with support and addressing issues, not to mention enduring my code! 🙀 +They dedicate a lot of their time to make `k9s` better for all of us! +So if you happen to run into them live/virtual, please be sure to `Thank` them and give them a huge hug! 🤗 + +I am thankful for all of you for being kind, patient, understanding and one of the coolest OSS community on the web!! + +Feeling blessed and ever so humbled to be part of it. + +Thank you!! + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Marco Stuurman](https://github.com/fe-ax) +* [Paul Sweeney](https://github.com/Kolossi) +* [Cayla Fauver](https://github.com/cayla) +* [alemanek](https://github.com/alemanek) +* [Danske Commodities A/S](https://github.com/DanskeCommodities) + +> Sponsorship cancellations since the last release: **8** ;( + +--- + +## 🎉 Feature Release 🎈👯 + +--- + +### Breaking Bad! + +WARNING! There are breaking change on this drop! + +1. NodeShell configuration has moved up in the k9s config file from the context section to the top level config. +More than likely, one uses the same nodeShell image with all the fixins to introspect nodes no matter the cluster. This update DRY's up k9s config and still allows one to opt in/out of nodeShell via the context specific feature gate. +Please see README for the details. + + > NOTE: If you haven't customize the shellPod images on your contexts, the app will move the nodeShell config section to + > it's new location and update your clusters information accordingly. + > If not, you will need to edit the nodeShell section and manage it from a single location! + +1. Log view used to default to the last 5mins aka `sinceSeconds: 300`. + Changed the default to tail logs instead aka `sinceSeconds: -1` + +1. Skins loading changed! In this release, we do away with the context specific skin files. You can now directly specify the skin to use for a given cluster directly in the k9s config file under the cluster configuration. K9s now expects a skins directory in the k9s config home with your skin files. You can use your custom skins and copy them to the `skins` directory or use the contributes skins found on this repo root. +Specify the name of the skin in the config file and now your cluster will load the specified skin. + +For example: create a `skins` dir your k9s config home and add one_dark.yml skin file from this repo. Then edit your k9s config file as follows: + +```yaml +k9s: + ... + clusters: + fred: + # Override the default skin and use this skin for this cluster. + skin: one_dark # -> Look for a skin file in ~/.config/k9s/skins/one_dark.yml + namespace: + ... + view: + active: pod + featureGates: + nodeShell: false + portForwardAddress: localhost +``` + +The `fred` cluster will now load with the specified skin name. Rinse and repeat for other clusters of your liking. In the case where neither the skin dir or skin file are present, k9s will still honor the global skin aka `skin.yml` in your k9s config home directory to skin all your clusters. + +--- + +### Walk Of SHelm... + +Added a `Releases` view to Helm! + +This provides the ability for Helm users to manage their releases directly from k9s. +You can now press `enter` on a selected Helm install and view all associated releases. +While in the releases view, you can also rollback an install to a previous revision. + +--- + +### Spock! Are You Out Of Your VulScan Mind? + +Tired of having malignent folks shoot holes in your prod clusters or failing compliance testing? + +Added ability to run image vulnerability scans directly from k9s. You can now monitor your security stance in dev/staging/... clusters +prior to proclaiming `It's Open Season...` in prod! + +As it stands Pod, Deployment, StatefulSet, DaemonSet, CronJob, Job views will feature a new column for Vulnerability Scan aka `VS`. + +> NOTE! This feature is gated so you'll need to manually opt in/out by modifying your k9s config file like so: + +```yaml +k9s: + liveViewAutoRefresh: false + enableImageScan: true # <- Yes Please!! + headless: false + ... +``` + +Once enabled, a new column `VS` (aka Vulnerability Score) should be present on the aforementioned views where you will see your vulnerability scores (*Still work in progress!!*). +The `VS` column displays a bit vector aka Sev-1|Sev-2|Sev-3|Sev-4|Sev-5|Sev-Unknown. When the bit is high it indicate the presence of the severity in the scans. Higher order bits = Higher severity +For instance, the following vector `110001` indicates the presence of both critical (Sev-1) and high (Sev-2) and an unclassified severity (aka Sev-Unknown) issues in the scan. Sev-U indicates no classification currently exist in our vulnerability database. + +The image scans are run async, rendering the views eventually consistent, hence you may have to give the scores a few cycles for the dust to settle... +Once the caches are primed, subsequent loads should be faster 🤞 + +You can sort the views by vulnerability score using `ShiftV`. +Additionally, you can view the full scans report by pressing `v` on a selected resource. + +I've synced my entire Thanksgiving holiday break on this ding dang deal, so hopefully it works for most of you?? +Also if you dig this new feature, please make some noise! 😍 + +💘 This is an experimental feature and likely will require additional TLC 💘 + +> NOTE! The lib we use to scan for vulnerabilities only supports macOS and Linux!! +> NOTE: I have yet to test this feature on larger clusters, so likely this may break?? +> Please take these reports with a grain of salt as likely your mileage will vary and help us +> validate the accuracy of the report ie if we cry `Wolf`, is it actually there? + +The paint is still fresh on this deal!! + +### Do You Tube? + +My plan is to begin (again!) putting out short k9s episodes with how-tos, tips, tricks and features previews. + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +The first drop should be up by the time you read this! + +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2308](https://github.com/derailed/k9s/issues/2308) Unable to list CRs for crd with only list and get verb without watch verb +* [#2301](https://github.com/derailed/k9s/issues/2301) Add imagePullPolicy and imagePullSecrets on shell_pod for internal registry uses +* [#2298](https://github.com/derailed/k9s/issues/2298) Weird color after plugin usage +* [#2297](https://github.com/derailed/k9s/issues/2297) Select nodes with space does not work anymore +* [#2290](https://github.com/derailed/k9s/issues/2290) Provide release assets for freebsd amd64/arm64 +* [#2283](https://github.com/derailed/k9s/issues/2283) Adding auto complete in search bar +* [#2219](https://github.com/derailed/k9s/issues/2219) Add tty: true to the node shell pod manifest +* [#2167](https://github.com/derailed/k9s/issues/2167) Show wrong Configmap data +* [#2166](https://github.com/derailed/k9s/issues/2166) Taint count for the nodes view +* [#2165](https://github.com/derailed/k9s/issues/2165) Restart counter for init containers +* [#2162](https://github.com/derailed/k9s/issues/2162) Make edit work when describing a resource +* [#2154](https://github.com/derailed/k9s/issues/2154) Help and h command does not work if typed into cmdbuff +* [#2036](https://github.com/derailed/k9s/issues/2036) Crashed while do filtering +* [#2009](https://github.com/derailed/k9s/issues/2009) Ctrl-s: Name of file (Describe-....) +* [#1513](https://github.com/derailed/k9s/issues/1513) Problem regarding showing the logs - it hangs/slow on pods which are running for long time + NOTE: Better but not cured! Perf improvements while viewing large cm (7k lines) from 26s->9s +* [#568](https://github.com/derailed/k9s/issues/568) Allow both .yaml and .yml yaml config files + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2322](https://github.com/derailed/k9s/pull/2322) Check if the service provides selectors +* [#2319](https://github.com/derailed/k9s/pull/2319) Proper handling of help commands (fixes #2154) +* [#2315](https://github.com/derailed/k9s/pull/2315) Fix namespace suggestion error on context switch +* [#2313](https://github.com/derailed/k9s/pull/2313) Should not clear screen when executing plugin command +* [#2310](https://github.com/derailed/k9s/pull/2310) chore: Mot recommended to use k8s.io/kubernetes as a dependency +* [#2303](https://github.com/derailed/k9s/pull/2303) Clean up items +* [#2301](https://github.com/derailed/k9s/pull/2301) feat: Add imagePullSecrets and imagePullPolicy configuration for shellpod +* [#2289](https://github.com/derailed/k9s/pull/2289) Clean up issues introduced in #2125 +* [#2288](https://github.com/derailed/k9s/pull/2288) Fix merge issues from PR #2168 +* [#2284](https://github.com/derailed/k9s/issues/2284) Allow both .yaml and .yml yaml config files + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/go.mod b/go.mod index 02e8554801..89eb331940 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,13 @@ module github.com/derailed/k9s -go 1.21 +go 1.21.1 + +toolchain go1.21.4 require ( github.com/adrg/xdg v0.4.0 + github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc + github.com/anchore/grype v0.73.4 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.1 @@ -13,7 +17,8 @@ require ( github.com/fsnotify/fsnotify v1.7.0 github.com/fvbommel/sortorder v1.1.0 github.com/mattn/go-colorable v0.1.13 - github.com/mattn/go-runewidth v0.0.14 + github.com/mattn/go-runewidth v0.0.15 + github.com/olekukonko/tablewriter v0.0.5 github.com/petergtz/pegomock v2.9.0+incompatible github.com/rakyll/hey v0.1.4 github.com/rs/zerolog v1.31.0 @@ -35,126 +40,281 @@ require ( ) require ( + cloud.google.com/go v0.110.10 // indirect + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/storage v1.35.1 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect + github.com/CycloneDX/cyclonedx-go v0.7.2 // indirect + github.com/DataDog/zstd v1.4.5 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/Microsoft/hcsshim v0.11.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/acobaugh/osrelease v0.1.0 // indirect + github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 // indirect + github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a // indirect + github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect + github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect + github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect + github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect + github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 // indirect + github.com/anchore/syft v0.98.0 // indirect + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect + github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/aws/aws-sdk-go v1.38.49 // indirect + github.com/aws/aws-sdk-go v1.44.288 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/containerd v1.7.6 // indirect + github.com/charmbracelet/lipgloss v0.9.1 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/containerd/cgroups v1.1.0 // indirect + github.com/containerd/containerd v1.7.8 // indirect + github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/fifo v1.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/containerd/ttrpc v1.2.2 // indirect + github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect + github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v24.0.6+incompatible // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect + github.com/felixge/fgprof v0.9.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gdamore/encoding v1.0.0 // indirect + github.com/github/go-spdx/v2 v2.2.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.10.0 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.10.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-restruct/restruct v1.2.0-alpha // indirect + github.com/go-test/deep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-containerregistry v0.16.1 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/licensecheck v0.3.1 // indirect + github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect + github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/gookit/color v1.5.4 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jinzhu/copier v0.4.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect + github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect + github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect + github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mholt/archiver/v3 v3.5.1 // indirect + github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/signal v0.7.0 // indirect github.com/moby/term 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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/gomega v1.27.6 // indirect + github.com/nwaples/rardecode v1.1.0 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/gomega v1.27.10 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/runc v1.1.5 // indirect + github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect + github.com/opencontainers/selinux v1.11.0 // indirect + github.com/openvex/go-vex v0.2.5 // indirect + github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect + github.com/package-url/packageurl-go v0.1.1 // indirect + github.com/pborman/indent v1.2.1 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pkg/profile v1.7.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/saferwall/pe v1.4.7 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect + github.com/sassoftware/go-rpmutils v0.2.0 // indirect + github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect + github.com/sergi/go-diff v1.3.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spdx/tools-golang v0.5.3 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.17.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/sylabs/sif/v2 v2.11.5 // indirect + github.com/sylabs/squashfs v0.6.1 // indirect + github.com/therootcompany/xz v1.0.1 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/vbatts/go-mtree v0.5.3 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect + github.com/vifraa/gopom v1.0.0 // indirect + github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect + github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b // indirect + github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + github.com/zclconf/go-cty v1.14.0 // indirect + go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.152.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/grpc v1.56.3 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/gorm v1.25.5 // indirect k8s.io/apiserver v0.28.3 // indirect k8s.io/component-base v0.28.4 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + modernc.org/libc v1.29.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/sqlite v1.27.0 // indirect oras.land/oras-go v1.2.4 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/go.sum b/go.sum index 9d21d6ee8b..1156338289 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,219 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ= +github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= @@ -19,28 +221,90 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTBdiM= -github.com/Microsoft/hcsshim v0.11.0/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= +github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= +github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= +github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ= +github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU= +github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 h1:3jHs159SUguPb0YMH/mKN2+eKKme76r+6iwAZ1xlu7c= +github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4/go.mod h1:yPsN3NUGhU5dcBtYBa1dMNzGu1yT5ZAfSjKq9DY4aV8= +github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw= +github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= +github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= +github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8= +github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= +github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= +github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= +github.com/anchore/grype v0.73.4 h1:j8HzRHbXLLZ6U2lmDDRFILd+VZtWbsfg/RYhatRZW9E= +github.com/anchore/grype v0.73.4/go.mod h1:5kJSAsHPoK47DsGZLHHArCfhHVGFGRkCfL2H87GdrdY= +github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= +github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= +github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 h1:mxGIOmj+asEm8LUkPTG3/v0hi27WIlDVjiEVsUB9eqY= +github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83/go.mod h1:GKAnytSVV1hoqB5r5Gd9M5Ph3Rzqq0zPdEJesewjC2w= +github.com/anchore/syft v0.98.0 h1:mPDah48zZCFeSiGweqPd2C2++rOUh3/cAZylEy1VPwU= +github.com/anchore/syft v0.98.0/go.mod h1:FMj8zZFF3mP4IAuTxb6n14CZ6ouWXpI9RZqXpnkLK+Y= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4= +github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg= +github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M= +github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 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/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go v1.38.49 h1:E31vxjCe6a5I+mJLmUGaZobiWmg9KdWaud9IfceYeYQ= -github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.288 h1:Ln7fIao/nl0ACtelgR1I4AiEw/GLNkKcXfCaHupUW5Q= +github.com/aws/aws-sdk-go v1.44.288/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= +github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= +github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= @@ -49,45 +313,92 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.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/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= -github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.7.8 h1:RkwgOW3AVUT3H/dyT0W03Dc8AzlpMG65lX48KftOFSM= +github.com/containerd/containerd v1.7.8/go.mod h1:L/Hn9qylJtUFT7cPeM0Sr3fATj+WjHwRQ0lyrYk3OPY= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs= +github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= +github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= +github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= +github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/derailed/popeye v0.11.1 h1:bjt5mXkcXY696ipuJqwY1sa5s3i431L9BlkQc6EuaqE= github.com/derailed/popeye v0.11.1/go.mod h1:NkvjHH1F94tE7Ui17PlYiagQcFt7yXUV2hIhPzSK+0w= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= github.com/derailed/tview v0.8.2 h1:8b+QwVECV1lZ6VV7Vf1tergpJxJ+ReA/JhIBYyUVSFI= github.com/derailed/tview v0.8.2/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw= +github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= @@ -98,41 +409,103 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= +github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ= +github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4= 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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= +github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/github/go-spdx/v2 v2.2.0 h1:yBBLMasHA70Ujd35OpL/OjJOWWVNXcJGbars0GinGRI= +github.com/github/go-spdx/v2 v2.2.0/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w= +github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8= +github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.4.12 h1:YeMgKOm0XW3f/Pt2rYpUlpyF8nG6lYGe9oXFJw5LdME= +github.com/gkampitakis/go-snaps v0.4.12/go.mod h1:PpnF1KPXQAHBdb/DHoi/1VmlwE+ZkVHzl+QHmgzMSz8= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= 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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= +github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -146,12 +519,15 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= +github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -161,16 +537,31 @@ github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXs github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -178,11 +569,20 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -191,21 +591,80 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ= +github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= +github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 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.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -215,22 +674,75 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= +github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= +github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -241,17 +753,41 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y= +github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= +github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= +github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c= +github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= +github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b h1:boYyvL3tbUuKcMN029mpCl7oYYJ7yIXujLj+fiW4Alc= +github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -270,10 +806,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= +github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= @@ -282,27 +822,67 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ= +github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ= +github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= +github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= -github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= +github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 h1:tQRHcLQwnwrPq2j2Qra/NnyjyESBGwdeBeVdAE9kXYg= +github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -310,8 +890,13 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -325,89 +910,184 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w= +github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= +github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= +github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= +github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 h1:FvA4bwjKpPqik5WsQ8+4z4DKWgA1tO1RTTtNKr5oYNA= +github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554/go.mod h1:n73K/hcuJ50MiVznXyN4rde6fZY7naGKWBXOLFTyc94= +github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= +github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= +github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 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/petergtz/pegomock v2.9.0+incompatible h1:BKfb5XfkJfehe5T+O1xD4Zm26Sb9dnRj7tHxLYwUPiI= github.com/petergtz/pegomock v2.9.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rakyll/hey v0.1.4 h1:hhc8GIqHN4+rPFZvkM9lkCQGi7da0sINM83xxpFkbPA= github.com/rakyll/hey v0.1.4/go.mod h1:nAOTOo+L52KB9SZq/M6J18kxjto4yVtXQDjU2HgjUPI= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/saferwall/pe v1.4.7 h1:A+G3DxX49paJ5OsxBfHKskhyDtmTjShlDmBd81IsHlQ= +github.com/saferwall/pe v1.4.7/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= +github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI= +github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= +github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= +github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= +github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -415,13 +1095,61 @@ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM= +github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y= +github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ= +github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= +github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= +github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= +github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w= +github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= +github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA= +github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20= +github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b h1:uWNQ0khA6RdFzODOMwKo9XXu7fuewnnkHykUtuKru8s= +github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b/go.mod h1:ewlIKbKV8l+jCj8rkdXIs361ocR5x3qGyoCSca47Gx8= +github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8= +github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -429,10 +1157,17 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= @@ -440,134 +1175,610 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc= +github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= +go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 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.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY= +google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -576,37 +1787,58 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= @@ -631,8 +1863,19 @@ k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk= k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= +modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= +modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= @@ -641,5 +1884,6 @@ sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/client/client.go b/internal/client/client.go index 6bbdffdd9b..fc5c9f2877 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -22,7 +22,6 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - cmdutil "k8s.io/kubectl/pkg/cmd/util" metricsapi "k8s.io/metrics/pkg/apis/metrics" "k8s.io/metrics/pkg/client/clientset/versioned" ) @@ -457,9 +456,7 @@ func (a *APIClient) supportsMetricsResources() error { a.cache.Add(cacheMXAPIKey, supported, cacheExpiry) }() - cfg := cmdutil.NewMatchVersionFlags(a.config.flags) - f := cmdutil.NewFactory(cfg) - dial, err := f.ToDiscoveryClient() + dial, err := a.CachedDiscovery() if err != nil { log.Warn().Err(err).Msgf("Unable to dial discovery API") return err diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 79f34eeb13..8ba7fd3568 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -228,7 +228,7 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be if entry, ok := m.cache.Get(key); ok { mxList, ok := entry.(*mv1beta1.PodMetricsList) if !ok { - return mx, fmt.Errorf("expected podmetricslist but got %T", entry) + return mx, fmt.Errorf("expected PodMetricsList but got %T", entry) } return mxList, nil } diff --git a/internal/client/types.go b/internal/client/types.go index fbeb657d77..fd814572da 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -58,10 +58,13 @@ const ( var ( // GetAccess reads a resource. GetAccess = []string{GetVerb} + // ListAccess list resources. ListAccess = []string{ListVerb} + // MonitorAccess monitors a collection of resources. MonitorAccess = []string{ListVerb, WatchVerb} + // ReadAllAccess represents an all read access to a resource. ReadAllAccess = []string{GetVerb, ListVerb, WatchVerb} ) diff --git a/internal/config/cluster.go b/internal/config/cluster.go index e95150191b..1fe5dccf9b 100644 --- a/internal/config/cluster.go +++ b/internal/config/cluster.go @@ -12,8 +12,8 @@ const DefaultPFAddress = "localhost" type Cluster struct { Namespace *Namespace `yaml:"namespace"` View *View `yaml:"view"` + Skin string `yaml:"skin,omitempty"` FeatureGates *FeatureGates `yaml:"featureGates"` - ShellPod *ShellPod `yaml:"shellPod"` PortForwardAddress string `yaml:"portForwardAddress"` } @@ -24,7 +24,6 @@ func NewCluster() *Cluster { View: NewView(), PortForwardAddress: DefaultPFAddress, FeatureGates: NewFeatureGates(), - ShellPod: NewShellPod(), } } @@ -49,9 +48,4 @@ func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) { c.View = NewView() } c.View.Validate() - - if c.ShellPod == nil { - c.ShellPod = NewShellPod() - } - c.ShellPod.Validate(conn, ks) } diff --git a/internal/config/color.go b/internal/config/color.go new file mode 100644 index 0000000000..59fd4d0a0f --- /dev/null +++ b/internal/config/color.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "fmt" + + "github.com/derailed/tcell/v2" +) + +const ( + // DefaultColor represents a default color. + DefaultColor Color = "default" + + // TransparentColor represents the terminal bg color. + TransparentColor Color = "-" +) + +// NewColor returns a new color. +func NewColor(c string) Color { + return Color(c) +} + +// String returns color as string. +func (c Color) String() string { + if c.isHex() { + return string(c) + } + if c == DefaultColor { + return "-" + } + col := c.Color().TrueColor().Hex() + if col < 0 { + return "-" + } + + return fmt.Sprintf("#%06x", col) +} + +func (c Color) isHex() bool { + return len(c) == 7 && c[0] == '#' +} + +// Color returns a view color. +func (c Color) Color() tcell.Color { + if c == DefaultColor { + return tcell.ColorDefault + } + + return tcell.GetColor(string(c)).TrueColor() +} + +// Colors converts series string colors to colors. +func (c Colors) Colors() []tcell.Color { + cc := make([]tcell.Color, 0, len(c)) + for _, color := range c { + cc = append(cc, color.Color()) + } + return cc +} diff --git a/internal/config/config.go b/internal/config/config.go index 9ba4a61be7..f7674c7777 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,10 @@ const K9sConfig = "K9SCONFIG" var ( // K9sConfigFile represents K9s config file location. K9sConfigFile = filepath.Join(K9sHome(), "config.yml") + + // K9sSkinDir represent K9s skin dir + K9sSkinDir = filepath.Join(K9sHome(), "skins") + // K9sDefaultScreenDumpDir represents a default directory where K9s screen dumps will be persisted. K9sDefaultScreenDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser())) ) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f33b8aecfe..bef14aeab5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -284,17 +284,24 @@ var expectedConfig = `k9s: refreshRate: 100 maxConnRetry: 5 enableMouse: false + enableImageScan: false headless: false logoless: false crumbsless: false readOnly: true noExitOnCtrlC: false noIcons: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi skipLatestRevCheck: false logger: tail: 500 buffer: 800 - sinceSeconds: 300 + sinceSeconds: -1 fullScreenLogs: false textWrap: false showTime: false @@ -312,15 +319,6 @@ var expectedConfig = `k9s: active: po featureGates: nodeShell: false - shellPod: - image: busybox:1.35.0 - command: [] - args: [] - namespace: default - limits: - cpu: 100m - memory: 100Mi - labels: {} portForwardAddress: localhost fred: namespace: @@ -336,15 +334,6 @@ var expectedConfig = `k9s: active: po featureGates: nodeShell: false - shellPod: - image: busybox:1.35.0 - command: [] - args: [] - namespace: default - limits: - cpu: 100m - memory: 100Mi - labels: {} portForwardAddress: localhost minikube: namespace: @@ -360,15 +349,6 @@ var expectedConfig = `k9s: active: ctx featureGates: nodeShell: false - shellPod: - image: busybox:1.35.0 - command: [] - args: [] - namespace: default - limits: - cpu: 100m - memory: 100Mi - labels: {} portForwardAddress: localhost thresholds: cpu: @@ -386,17 +366,24 @@ var resetConfig = `k9s: refreshRate: 2 maxConnRetry: 5 enableMouse: false + enableImageScan: false headless: false logoless: false crumbsless: false readOnly: false noExitOnCtrlC: false noIcons: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi skipLatestRevCheck: false logger: tail: 200 buffer: 2000 - sinceSeconds: 300 + sinceSeconds: -1 fullScreenLogs: false textWrap: false showTime: false @@ -414,15 +401,6 @@ var resetConfig = `k9s: active: po featureGates: nodeShell: false - shellPod: - image: busybox:1.35.0 - command: [] - args: [] - namespace: default - limits: - cpu: 100m - memory: 100Mi - labels: {} portForwardAddress: localhost thresholds: cpu: diff --git a/internal/config/helpers.go b/internal/config/helpers.go index f3668370af..bc14a34898 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -20,7 +20,7 @@ const ( DefaultFileMod os.FileMode = 0600 ) -var invalidPathCharsRX = regexp.MustCompile(`[:]+`) +var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) // SanitizeFilename sanitizes the dump filename. func SanitizeFilename(name string) string { diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 0f1f2ddadb..8dbaacb638 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -18,12 +18,14 @@ type K9s struct { RefreshRate int `yaml:"refreshRate"` MaxConnRetry int `yaml:"maxConnRetry"` EnableMouse bool `yaml:"enableMouse"` + EnableImageScan bool `yaml:"enableImageScan"` Headless bool `yaml:"headless"` Logoless bool `yaml:"logoless"` Crumbsless bool `yaml:"crumbsless"` ReadOnly bool `yaml:"readOnly"` NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"` NoIcons bool `yaml:"noIcons"` + ShellPod *ShellPod `yaml:"shellPod"` SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"` Logger *Logger `yaml:"logger"` CurrentContext string `yaml:"currentContext"` @@ -51,6 +53,7 @@ func NewK9s() *K9s { Clusters: make(map[string]*Cluster), Thresholds: NewThreshold(), ScreenDumpDir: K9sDefaultScreenDumpDir, + ShellPod: NewShellPod(), } } @@ -237,6 +240,11 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) { } k.validateClusters(c, ks) + if k.ShellPod == nil { + k.ShellPod = NewShellPod() + } + k.ShellPod.Validate(c, ks) + if k.Logger == nil { k.Logger = NewLogger() } else { diff --git a/internal/config/logger.go b/internal/config/logger.go index 93f6bbd418..13cb96b108 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -10,10 +10,12 @@ import ( const ( // DefaultLoggerTailCount tracks default log tail size. DefaultLoggerTailCount = 100 + // MaxLogThreshold sets the max value for log size. MaxLogThreshold = 5000 + // DefaultSinceSeconds tracks default log age. - DefaultSinceSeconds = 300 // all logs + DefaultSinceSeconds = -1 // tail logs by default ) // Logger tracks logger options. diff --git a/internal/config/plugin.go b/internal/config/plugin.go index b7b2fa67e0..99d9c0cf85 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/adrg/xdg" - "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" ) @@ -52,6 +51,7 @@ func (p Plugins) Load() error { for _, dataDir := range xdg.DataDirs { pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory)) } + return p.LoadPlugins(K9sPluginsFilePath, pluginDirs) } @@ -61,15 +61,18 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { if err != nil { return err } + var pp Plugins if err := yaml.Unmarshal(f, &pp); err != nil { return err } + for k, v := range pp.Plugin { + p.Plugin[k] = v + } for _, pluginDir := range pluginDirs { pluginFiles, err := os.ReadDir(pluginDir) if err != nil { - log.Warn().Msgf("Failed reading plugin path %s; %s", pluginDir, err) continue } for _, file := range pluginFiles { @@ -88,9 +91,5 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { } } - for k, v := range pp.Plugin { - p.Plugin[k] = v - } - return nil } diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index f4efe0bbb5..919cedae76 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -15,14 +15,15 @@ type Limits map[v1.ResourceName]string // ShellPod represents k9s shell configuration. type ShellPod struct { - Image string `json:"image"` - ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"` - ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"` - Command []string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` - Namespace string `json:"namespace"` - Limits Limits `json:"resources,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Image string `yaml:"image"` + Command []string `yaml:"command,omitempty"` + Args []string `yaml:"args,omitempty"` + Namespace string `yaml:"namespace"` + Limits Limits `yaml:"limits,omitempty"` + Labels map[string]string `yaml:"labels,omitempty"` + ImagePullSecrets []v1.LocalObjectReference `yaml:"imagePullSecrets,omitempty"` + ImagePullPolicy v1.PullPolicy `yaml:"imagePullPolicy,omitempty"` + TTY bool `yaml:"tty,omitempty"` } // NewShellPod returns a new instance. diff --git a/internal/config/styles.go b/internal/config/styles.go index 62a192a0d3..cefefcf085 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -4,7 +4,6 @@ package config import ( - "fmt" "os" "path/filepath" @@ -224,57 +223,6 @@ type ( } ) -const ( - // DefaultColor represents a default color. - DefaultColor Color = "default" - - // TransparentColor represents the terminal bg color. - TransparentColor Color = "-" -) - -// NewColor returns a new color. -func NewColor(c string) Color { - return Color(c) -} - -// String returns color as string. -func (c Color) String() string { - if c.isHex() { - return string(c) - } - if c == DefaultColor { - return "-" - } - col := c.Color().TrueColor().Hex() - if col < 0 { - return "-" - } - - return fmt.Sprintf("#%06x", col) -} - -func (c Color) isHex() bool { - return len(c) == 7 && c[0] == '#' -} - -// Color returns a view color. -func (c Color) Color() tcell.Color { - if c == DefaultColor { - return tcell.ColorDefault - } - - return tcell.GetColor(string(c)).TrueColor() -} - -// Colors converts series string colors to colors. -func (c Colors) Colors() []tcell.Color { - cc := make([]tcell.Color, 0, len(c)) - for _, color := range c { - cc = append(cc, color.Color()) - } - return cc -} - func newStyle() Style { return Style{ Body: newBody(), diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go index b5dbef1cba..75dcdc6a81 100644 --- a/internal/dao/benchmark.go +++ b/internal/dao/benchmark.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 201f771369..290e1bb9ea 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( @@ -12,6 +9,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,8 +25,9 @@ const ( ) var ( - _ Accessor = (*CronJob)(nil) - _ Runnable = (*CronJob)(nil) + _ Accessor = (*CronJob)(nil) + _ Runnable = (*CronJob)(nil) + _ ImageLister = (*CronJob)(nil) ) // CronJob represents a cronjob K8s resource. @@ -36,6 +35,16 @@ type CronJob struct { Generic } +// ListImages lists container images. +func (c *CronJob) ListImages(ctx context.Context, fqn string) ([]string, error) { + cj, err := c.GetInstance(fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&cj.Spec.JobTemplate.Spec.Template.Spec), nil +} + // Run a CronJob. func (c *CronJob) Run(path string) error { ns, _ := client.Namespaced(path) @@ -116,6 +125,22 @@ func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, erro return refs, nil } +// GetInstance fetch a matching cronjob. +func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) { + o, err := c.GetFactory().Get(c.GVR(), fqn, true, labels.Everything()) + if err != nil { + return nil, err + } + + var cj batchv1.CronJob + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cj) + if err != nil { + return nil, errors.New("expecting cronjob resource") + } + + return &cj, nil +} + // ToggleSuspend toggles suspend/resume on a CronJob. func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error { ns, n := client.Namespaced(path) diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 541415b180..db1680aa2b 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( @@ -12,6 +9,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -33,6 +31,7 @@ var ( _ Scalable = (*Deployment)(nil) _ Controller = (*Deployment)(nil) _ ContainsPodSpec = (*Deployment)(nil) + _ ImageLister = (*Deployment)(nil) ) // Deployment represents a deployment K8s resource. @@ -40,6 +39,16 @@ type Deployment struct { Resource } +// ListImages lists container images. +func (d *Deployment) ListImages(ctx context.Context, fqn string) ([]string, error) { + dp, err := d.GetInstance(fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&dp.Spec.Template.Spec), nil +} + // IsHappy check for happy deployments. func (d *Deployment) IsHappy(dp appsv1.Deployment) bool { return dp.Status.Replicas == dp.Status.AvailableReplicas @@ -121,7 +130,7 @@ func (d *Deployment) Restart(ctx context.Context, path string) error { // TailLogs tail logs for all pods represented by this Deployment. func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) { - dp, err := d.GetInstance(d.Factory, opts.Path) + dp, err := d.GetInstance(opts.Path) if err != nil { return nil, err } @@ -134,7 +143,7 @@ func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, // Pod returns a pod victim by name. func (d *Deployment) Pod(fqn string) (string, error) { - dp, err := d.GetInstance(d.Factory, fqn) + dp, err := d.GetInstance(fqn) if err != nil { return "", err } @@ -143,8 +152,8 @@ func (d *Deployment) Pod(fqn string) (string, error) { } // GetInstance fetch a matching deployment. -func (*Deployment) GetInstance(f Factory, fqn string) (*appsv1.Deployment, error) { - o, err := f.Get("apps/v1/deployments", fqn, true, labels.Everything()) +func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) { + o, err := d.Factory.Get(d.GVR(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -246,7 +255,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs // GetPodSpec returns a pod spec given a resource. func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) { - dp, err := d.GetInstance(d.Factory, path) + dp, err := d.GetInstance(path) if err != nil { return nil, err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index d970afd274..44c84e3915 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/watch" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" @@ -32,6 +33,7 @@ var ( _ Restartable = (*DaemonSet)(nil) _ Controller = (*DaemonSet)(nil) _ ContainsPodSpec = (*DaemonSet)(nil) + _ ImageLister = (*DaemonSet)(nil) ) // DaemonSet represents a K8s daemonset. @@ -39,6 +41,16 @@ type DaemonSet struct { Resource } +// ListImages lists container images. +func (d *DaemonSet) ListImages(ctx context.Context, fqn string) ([]string, error) { + ds, err := d.GetInstance(fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&ds.Spec.Template.Spec), nil +} + // IsHappy check for happy deployments. func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool { return ds.Status.DesiredNumberScheduled == ds.Status.CurrentNumberScheduled diff --git a/internal/dao/generic.go b/internal/dao/generic.go index e205e1f525..6d3e7f411d 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( diff --git a/internal/dao/helm.go b/internal/dao/helm_chart.go similarity index 56% rename from internal/dao/helm.go rename to internal/dao/helm_chart.go index 5ec675bd89..8aa26dcafe 100644 --- a/internal/dao/helm.go +++ b/internal/dao/helm_chart.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( @@ -12,7 +9,7 @@ import ( "os" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/render/helm" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" @@ -21,19 +18,19 @@ import ( ) var ( - _ Accessor = (*Helm)(nil) - _ Nuker = (*Helm)(nil) - _ Describer = (*Helm)(nil) + _ Accessor = (*HelmChart)(nil) + _ Nuker = (*HelmChart)(nil) + _ Describer = (*HelmChart)(nil) ) -// Helm represents a helm chart. -type Helm struct { +// HelmChart represents a helm chart. +type HelmChart struct { NonResource } // List returns a collection of resources. -func (h *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) { - cfg, err := h.EnsureHelmConfig(ns) +func (h *HelmChart) List(ctx context.Context, ns string) ([]runtime.Object, error) { + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return nil, err } @@ -48,16 +45,16 @@ func (h *Helm) List(ctx context.Context, ns string) ([]runtime.Object, error) { oo := make([]runtime.Object, 0, len(rr)) for _, r := range rr { - oo = append(oo, render.HelmRes{Release: r}) + oo = append(oo, helm.ReleaseRes{Release: r}) } return oo, nil } // Get returns a resource. -func (h *Helm) Get(_ context.Context, path string) (runtime.Object, error) { +func (h *HelmChart) Get(_ context.Context, path string) (runtime.Object, error) { ns, n := client.Namespaced(path) - cfg, err := h.EnsureHelmConfig(ns) + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return nil, err } @@ -66,13 +63,13 @@ func (h *Helm) Get(_ context.Context, path string) (runtime.Object, error) { return nil, err } - return render.HelmRes{Release: resp}, nil + return helm.ReleaseRes{Release: resp}, nil } // GetValues returns values for a release -func (h *Helm) GetValues(path string, allValues bool) ([]byte, error) { +func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) { ns, n := client.Namespaced(path) - cfg, err := h.EnsureHelmConfig(ns) + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return nil, err } @@ -87,9 +84,9 @@ func (h *Helm) GetValues(path string, allValues bool) ([]byte, error) { } // Describe returns the chart notes. -func (h *Helm) Describe(path string) (string, error) { +func (h *HelmChart) Describe(path string) (string, error) { ns, n := client.Namespaced(path) - cfg, err := h.EnsureHelmConfig(ns) + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return "", err } @@ -102,9 +99,9 @@ func (h *Helm) Describe(path string) (string, error) { } // ToYAML returns the chart manifest. -func (h *Helm) ToYAML(path string, showManaged bool) (string, error) { +func (h *HelmChart) ToYAML(path string, showManaged bool) (string, error) { ns, n := client.Namespaced(path) - cfg, err := h.EnsureHelmConfig(ns) + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return "", err } @@ -116,15 +113,20 @@ func (h *Helm) ToYAML(path string, showManaged bool) (string, error) { return resp.Manifest, nil } -// Delete uninstall a Helm. -func (h *Helm) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error { +// Delete uninstall a HelmChart. +func (h *HelmChart) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error { + return h.Uninstall(path, false) +} + +// Uninstall uninstalls a HelmChart. +func (h *HelmChart) Uninstall(path string, keepHist bool) error { ns, n := client.Namespaced(path) - cfg, err := h.EnsureHelmConfig(ns) + cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return err } u := action.NewUninstall(cfg) - u.KeepHistory = true + u.KeepHistory = keepHist res, err := u.Run(n) if err != nil { return err @@ -136,10 +138,10 @@ func (h *Helm) Delete(_ context.Context, path string, _ *metav1.DeletionPropagat return nil } -// EnsureHelmConfig return a new configuration. -func (h *Helm) EnsureHelmConfig(ns string) (*action.Configuration, error) { +// ensureHelmConfig return a new configuration. +func ensureHelmConfig(c client.Connection, ns string) (*action.Configuration, error) { cfg := new(action.Configuration) - err := cfg.Init(h.Client().Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger) + err := cfg.Init(c.Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger) return cfg, err } diff --git a/internal/dao/helm_history.go b/internal/dao/helm_history.go new file mode 100644 index 0000000000..38fda22ff9 --- /dev/null +++ b/internal/dao/helm_history.go @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao + +import ( + "context" + "fmt" + "strconv" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render/helm" + "helm.sh/helm/v3/pkg/action" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*HelmHistory)(nil) + _ Nuker = (*HelmHistory)(nil) + _ Describer = (*HelmHistory)(nil) +) + +// HelmHistory represents a helm chart. +type HelmHistory struct { + NonResource +} + +// List returns a collection of resources. +func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, error) { + path, ok := ctx.Value(internal.KeyFQN).(string) + if !ok { + return nil, fmt.Errorf("expecting FQN in context") + } + ns, n := client.Namespaced(path) + + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return nil, err + } + + hh, err := action.NewHistory(cfg).Run(n) + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, 0, len(hh)) + for _, r := range hh { + oo = append(oo, helm.ReleaseRes{Release: r}) + } + + return oo, nil +} + +// Get returns a resource. +func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) { + ns, n := client.Namespaced(path) + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return nil, err + } + resp, err := action.NewGet(cfg).Run(n) + if err != nil { + return nil, err + } + + return helm.ReleaseRes{Release: resp}, nil +} + +// Describe returns the chart notes. +func (h *HelmHistory) Describe(path string) (string, error) { + ns, n := client.Namespaced(path) + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return "", err + } + resp, err := action.NewGet(cfg).Run(n) + if err != nil { + return "", err + } + + return resp.Info.Notes, nil +} + +// ToYAML returns the chart manifest. +func (h *HelmHistory) ToYAML(path string, showManaged bool) (string, error) { + ns, n := client.Namespaced(path) + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return "", err + } + resp, err := action.NewGet(cfg).Run(n) + if err != nil { + return "", err + } + + return resp.Manifest, nil +} + +func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error { + ns, n := client.Namespaced(path) + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return err + } + + ver, err := strconv.Atoi(rev) + if err != nil { + return fmt.Errorf("could not convert revision to a number: %w", err) + } + client := action.NewRollback(cfg) + client.Version = ver + + return client.Run(n) +} + +// Delete uninstall a Helm. +func (h *HelmHistory) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error { + ns, n := client.Namespaced(path) + cfg, err := ensureHelmConfig(h.Client(), ns) + if err != nil { + return err + } + + res, err := action.NewUninstall(cfg).Run(n) + if err != nil { + return err + } + + if res != nil && res.Info != "" { + return fmt.Errorf("%s", res.Info) + } + + return nil +} diff --git a/internal/dao/img_scan.go b/internal/dao/img_scan.go new file mode 100644 index 0000000000..77cdd7b10d --- /dev/null +++ b/internal/dao/img_scan.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao + +import ( + "context" + "fmt" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/vul" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*ImageScan)(nil) +) + +// ImageScan represents vulnerability scans. +type ImageScan struct { + NonResource +} + +func (is *ImageScan) listImages(ctx context.Context, gvr client.GVR, path string) ([]string, error) { + res, err := AccessorFor(is.Factory, gvr) + if err != nil { + return nil, err + } + s, ok := res.(ImageLister) + if !ok { + return nil, fmt.Errorf("resource %s is not image lister: %T", gvr, res) + } + + return s.ListImages(ctx, path) +} + +// List returns a collection of scans. +func (is *ImageScan) List(ctx context.Context, _ string) ([]runtime.Object, error) { + fqn, ok := ctx.Value(internal.KeyPath).(string) + if !ok { + return nil, fmt.Errorf("no context path for %q", is.gvr) + } + gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) + if !ok { + return nil, fmt.Errorf("no context gvr for %q", is.gvr) + } + + ii, err := is.listImages(ctx, gvr, fqn) + if err != nil { + return nil, err + } + + res := make([]runtime.Object, 0, len(ii)) + for _, img := range ii { + s, ok := vul.ImgScanner.GetScan(img) + if !ok { + continue + } + for _, r := range s.Table.Rows { + res = append(res, render.ImageScanRes{Image: img, Row: r}) + } + } + + return res, nil +} diff --git a/internal/dao/job.go b/internal/dao/job.go index 4b9311b3fd..4f70acc700 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -18,9 +19,10 @@ import ( ) var ( - _ Accessor = (*Job)(nil) - _ Nuker = (*Job)(nil) - _ Loggable = (*Job)(nil) + _ Accessor = (*Job)(nil) + _ Nuker = (*Job)(nil) + _ Loggable = (*Job)(nil) + _ ImageLister = (*Deployment)(nil) ) // Job represents a K8s job resource. @@ -28,6 +30,16 @@ type Job struct { Resource } +// ListImages lists container images. +func (j *Job) ListImages(ctx context.Context, fqn string) ([]string, error) { + job, err := j.GetInstance(fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&job.Spec.Template.Spec), nil +} + // List returns a collection of resources. func (j *Job) List(ctx context.Context, ns string) ([]runtime.Object, error) { oo, err := j.Resource.List(ctx, ns) @@ -79,6 +91,21 @@ func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) return podLogs(ctx, job.Spec.Selector.MatchLabels, opts) } +func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) { + o, err := j.GetFactory().Get(j.gvr.String(), fqn, true, labels.Everything()) + if err != nil { + return nil, err + } + + var job batchv1.Job + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job) + if err != nil { + return nil, errors.New("expecting a job resource") + } + + return &job, nil +} + // ScanSA scans for serviceaccount refs. func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) diff --git a/internal/dao/log_item.go b/internal/dao/log_item.go index 2ef1ba3d43..ff90bbff3b 100644 --- a/internal/dao/log_item.go +++ b/internal/dao/log_item.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( diff --git a/internal/dao/node.go b/internal/dao/node.go index 67b14f2feb..6d2984679d 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 70b7f3dc5e..05ef713e6a 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package dao import ( @@ -36,6 +33,7 @@ var ( _ Loggable = (*Pod)(nil) _ Controller = (*Pod)(nil) _ ContainsPodSpec = (*Pod)(nil) + _ ImageLister = (*Pod)(nil) ) const ( @@ -79,6 +77,16 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) { return &render.PodWithMetrics{Raw: u, MX: pmx}, nil } +// ListImages lists container images. +func (p *Pod) ListImages(ctx context.Context, path string) ([]string, error) { + pod, err := p.GetInstance(path) + if err != nil { + return nil, err + } + + return render.ExtractImages(&pod.Spec), nil +} + // List returns a collection of nodes. func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { oo, err := p.Resource.List(ctx, ns) @@ -526,12 +534,19 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { } log.Debug().Msgf("Pod status: %q", render.PodStatus(&pod)) switch render.PodStatus(&pod) { - case render.PhaseCompleted, render.PhaseCrashLoop, render.PhaseError, render.PhaseImagePullBackOff, render.PhaseOOMKilled: + case render.PhaseCompleted: + fallthrough + case render.PhaseCrashLoop: + fallthrough + case render.PhaseError: + fallthrough + case render.PhaseImagePullBackOff: + fallthrough + case render.PhaseOOMKilled: log.Debug().Msgf("Sanitizing %s:%s", pod.Namespace, pod.Name) fqn := client.FQN(pod.Namespace, pod.Name) if err := p.Resource.Delete(ctx, fqn, nil, NowGrace); err != nil { - log.Warn().Err(err).Msgf("Pod %s deletion failed", fqn) - continue + return count, err } count++ } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 8a3eb212f0..54fdbce53e 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -17,8 +17,11 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// CRD identifies a CRD. -const CRD = "crd" +const ( + crdCat = "crd" + k9sCat = "k9s" + helmCat = "helm" +) // MetaAccess tracks resources metadata. var MetaAccess = NewMeta() @@ -82,6 +85,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { m := Accessors{ client.NewGVR("contexts"): &Context{}, client.NewGVR("containers"): &Container{}, + client.NewGVR("scans"): &ImageScan{}, client.NewGVR("screendumps"): &ScreenDump{}, client.NewGVR("benchmarks"): &Benchmark{}, client.NewGVR("portforwards"): &PortForward{}, @@ -91,21 +95,19 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { client.NewGVR("apps/v1/deployments"): &Deployment{}, client.NewGVR("apps/v1/daemonsets"): &DaemonSet{}, client.NewGVR("apps/v1/statefulsets"): &StatefulSet{}, + client.NewGVR("apps/v1/replicasets"): &ReplicaSet{}, client.NewGVR("batch/v1/cronjobs"): &CronJob{}, client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, client.NewGVR("batch/v1/jobs"): &Job{}, client.NewGVR("v1/namespaces"): &Namespace{}, - // BOZO!! Revamp with latest... - // client.NewGVR("openfaas"): &OpenFaas{}, - client.NewGVR("popeye"): &Popeye{}, - client.NewGVR("sanitizer"): &Popeye{}, - client.NewGVR("helm"): &Helm{}, - client.NewGVR("dir"): &Dir{}, + client.NewGVR("popeye"): &Popeye{}, + client.NewGVR("helm"): &HelmChart{}, + client.NewGVR("dir"): &Dir{}, } r, ok := m[gvr] if !ok { - r = &Generic{} + r = new(Generic) log.Debug().Msgf("No DAO registry entry for %q. Using generics!", gvr) } r.Init(f, gvr) @@ -138,7 +140,7 @@ func (m *Meta) AllGVRs() client.GVRs { // IsCRD checks if resource represents a CRD func IsCRD(r metav1.APIResource) bool { for _, c := range r.Categories { - if c == CRD { + if c == crdCat { return true } } @@ -160,7 +162,7 @@ func (m *Meta) MetaFor(gvr client.GVR) (metav1.APIResource, error) { // IsK8sMeta checks for non resource meta. func IsK8sMeta(m metav1.APIResource) bool { for _, c := range m.Categories { - if c == "k9s" || c == "helm" || c == "faas" { + if c == k9sCat || c == helmCat { return false } } @@ -171,7 +173,7 @@ func IsK8sMeta(m metav1.APIResource) bool { // IsK9sMeta checks for non resource meta. func IsK9sMeta(m metav1.APIResource) bool { for _, c := range m.Categories { - if c == "k9s" { + if c == k9sCat { return true } } @@ -199,10 +201,6 @@ func loadNonResource(m ResourceMetas) { loadK9s(m) loadRBAC(m) loadHelm(m) - // BOZO!! Revamp with latest... - // if IsOpenFaasEnabled() { - // loadOpenFaas(m) - // } } func loadK9s(m ResourceMetas) { @@ -211,33 +209,33 @@ func loadK9s(m ResourceMetas) { Kind: "Pulse", SingularName: "pulses", ShortNames: []string{"hz", "pu"}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("dir")] = metav1.APIResource{ Name: "dir", Kind: "Dir", SingularName: "dir", - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("xrays")] = metav1.APIResource{ Name: "xray", Kind: "XRays", SingularName: "xray", - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("references")] = metav1.APIResource{ Name: "references", Kind: "References", SingularName: "reference", Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("aliases")] = metav1.APIResource{ Name: "aliases", Kind: "Aliases", SingularName: "alias", Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("popeye")] = metav1.APIResource{ Name: "popeye", @@ -245,14 +243,14 @@ func loadK9s(m ResourceMetas) { SingularName: "popeye", Namespaced: true, Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("sanitizer")] = metav1.APIResource{ Name: "sanitizer", Kind: "Sanitizer", SingularName: "sanitizer", Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("contexts")] = metav1.APIResource{ Name: "contexts", @@ -260,7 +258,7 @@ func loadK9s(m ResourceMetas) { SingularName: "context", ShortNames: []string{"ctx"}, Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("screendumps")] = metav1.APIResource{ Name: "screendumps", @@ -268,7 +266,7 @@ func loadK9s(m ResourceMetas) { SingularName: "screendump", ShortNames: []string{"sd"}, Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("benchmarks")] = metav1.APIResource{ Name: "benchmarks", @@ -276,7 +274,7 @@ func loadK9s(m ResourceMetas) { SingularName: "benchmark", ShortNames: []string{"be"}, Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("portforwards")] = metav1.APIResource{ Name: "portforwards", @@ -285,60 +283,62 @@ func loadK9s(m ResourceMetas) { SingularName: "portforward", ShortNames: []string{"pf"}, Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("containers")] = metav1.APIResource{ Name: "containers", Kind: "Containers", SingularName: "container", Verbs: []string{}, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, + } + m[client.NewGVR("scans")] = metav1.APIResource{ + Name: "scans", + Kind: "Scans", + SingularName: "scan", + Verbs: []string{}, + Categories: []string{k9sCat}, } } func loadHelm(m ResourceMetas) { m[client.NewGVR("helm")] = metav1.APIResource{ - Name: "helm", - Kind: "Helm", + Name: "chart", + Kind: "Chart", Namespaced: true, Verbs: []string{"delete"}, - Categories: []string{"helm"}, + Categories: []string{helmCat}, + } + m[client.NewGVR("helm-history")] = metav1.APIResource{ + Name: "history", + Kind: "History", + Namespaced: true, + Verbs: []string{"delete"}, + Categories: []string{k9sCat}, } } -// BOZO!! revamp with latest... -// func loadOpenFaas(m ResourceMetas) { -// m[client.NewGVR("openfaas")] = metav1.APIResource{ -// Name: "openfaas", -// Kind: "OpenFaaS", -// ShortNames: []string{"ofaas", "ofa"}, -// Namespaced: true, -// Verbs: []string{"delete"}, -// Categories: []string{"faas"}, -// } -// } - func loadRBAC(m ResourceMetas) { m[client.NewGVR("rbac")] = metav1.APIResource{ Name: "rbacs", Kind: "Rules", - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("policy")] = metav1.APIResource{ Name: "policies", Kind: "Rules", Namespaced: true, - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("users")] = metav1.APIResource{ Name: "users", Kind: "User", - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } m[client.NewGVR("groups")] = metav1.APIResource{ Name: "groups", Kind: "Group", - Categories: []string{"k9s"}, + Categories: []string{k9sCat}, } } @@ -367,7 +367,7 @@ func loadPreferred(f Factory, m ResourceMetas) error { res.SingularName = strings.ToLower(res.Kind) } if !isStandardGroup(res.Group) { - res.Categories = append(res.Categories, CRD) + res.Categories = append(res.Categories, crdCat) } m[gvr] = res } @@ -412,7 +412,7 @@ func loadCRDs(f Factory, m ResourceMetas) { log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs)) continue } - meta.Categories = append(meta.Categories, CRD) + meta.Categories = append(meta.Categories, crdCat) gvr := client.NewGVRFromMeta(meta) m[gvr] = meta } @@ -426,7 +426,7 @@ func extractMeta(o runtime.Object) (metav1.APIResource, []error) { crd, ok := o.(*unstructured.Unstructured) if !ok { - return m, append(errs, fmt.Errorf("Expected Unstructured, but got %T", o)) + return m, append(errs, fmt.Errorf("expected unstructured, but got %T", o)) } var spec map[string]interface{} diff --git a/internal/dao/rs.go b/internal/dao/rs.go index 06d91c9158..fea9d3265d 100644 --- a/internal/dao/rs.go +++ b/internal/dao/rs.go @@ -4,12 +4,14 @@ package dao import ( + "context" "errors" "fmt" "strconv" "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -19,11 +21,25 @@ import ( "k8s.io/kubectl/pkg/polymorphichelpers" ) +var ( + _ ImageLister = (*ReplicaSet)(nil) +) + // ReplicaSet represents a replicaset K8s resource. type ReplicaSet struct { Resource } +// ListImages lists container images. +func (r *ReplicaSet) ListImages(ctx context.Context, fqn string) ([]string, error) { + rs, err := r.Load(r.Factory, fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&rs.Spec.Template.Spec), nil +} + // Load returns a given instance. func (r *ReplicaSet) Load(f Factory, path string) (*appsv1.ReplicaSet, error) { o, err := f.Get("apps/v1/replicasets", path, true, labels.Everything()) @@ -98,7 +114,8 @@ func (r *ReplicaSet) Rollback(fqn string) error { } var ddp Deployment - dp, err := ddp.GetInstance(r.Factory, client.FQN(rs.Namespace, name)) + ddp.Init(r.Factory, client.NewGVR("apps/v1/deployments")) + dp, err := ddp.GetInstance(client.FQN(rs.Namespace, name)) if err != nil { return err } diff --git a/internal/dao/sts.go b/internal/dao/sts.go index d9ce1e1e6c..6e4b2fc822 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -31,6 +32,7 @@ var ( _ Scalable = (*StatefulSet)(nil) _ Controller = (*StatefulSet)(nil) _ ContainsPodSpec = (*StatefulSet)(nil) + _ ImageLister = (*StatefulSet)(nil) ) // StatefulSet represents a K8s sts. @@ -38,6 +40,16 @@ type StatefulSet struct { Resource } +// ListImages lists container images. +func (s *StatefulSet) ListImages(ctx context.Context, fqn string) ([]string, error) { + sts, err := s.GetInstance(s.Factory, fqn) + if err != nil { + return nil, err + } + + return render.ExtractImages(&sts.Spec.Template.Spec), nil +} + // IsHappy check for happy sts. func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool { return sts.Status.Replicas == sts.Status.ReadyReplicas diff --git a/internal/dao/types.go b/internal/dao/types.go index 9af913985c..e24cb22bf3 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -51,6 +51,12 @@ type Factory interface { Forwarders() watch.Forwarders } +// ImageLister tracks resources with container images. +type ImageLister interface { + // ListImages lists container images. + ListImages(ctx context.Context, path string) ([]string, error) +} + // Getter represents a resource getter. type Getter interface { // Get return a given resource. diff --git a/internal/keys.go b/internal/keys.go index 1b53cfece6..d18bc11d36 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -8,30 +8,32 @@ type ContextKey string // A collection of context keys. const ( - KeyFactory ContextKey = "factory" - KeyLabels ContextKey = "labels" - KeyFields ContextKey = "fields" - KeyTable ContextKey = "table" - KeyDir ContextKey = "dir" - KeyPath ContextKey = "path" - KeySubject ContextKey = "subject" - KeyGVR ContextKey = "gvr" - KeyForwards ContextKey = "forwards" - KeyContainers ContextKey = "containers" - KeyBenchCfg ContextKey = "benchcfg" - KeyAliases ContextKey = "aliases" - KeyUID ContextKey = "uid" - KeySubjectKind ContextKey = "subjectKind" - KeySubjectName ContextKey = "subjectName" - KeyNamespace ContextKey = "namespace" - KeyCluster ContextKey = "cluster" - KeyApp ContextKey = "app" - KeyStyles ContextKey = "styles" - KeyMetrics ContextKey = "metrics" - KeyHasMetrics ContextKey = "has-metrics" - KeyToast ContextKey = "toast" - KeyWithMetrics ContextKey = "withMetrics" - KeyViewConfig ContextKey = "viewConfig" - KeyWait ContextKey = "wait" - KeyPodCounting ContextKey = "podCounting" + KeyFactory ContextKey = "factory" + KeyLabels ContextKey = "labels" + KeyFields ContextKey = "fields" + KeyTable ContextKey = "table" + KeyDir ContextKey = "dir" + KeyPath ContextKey = "path" + KeySubject ContextKey = "subject" + KeyGVR ContextKey = "gvr" + KeyFQN ContextKey = "fqn" + KeyForwards ContextKey = "forwards" + KeyContainers ContextKey = "containers" + KeyBenchCfg ContextKey = "benchcfg" + KeyAliases ContextKey = "aliases" + KeyUID ContextKey = "uid" + KeySubjectKind ContextKey = "subjectKind" + KeySubjectName ContextKey = "subjectName" + KeyNamespace ContextKey = "namespace" + KeyCluster ContextKey = "cluster" + KeyApp ContextKey = "app" + KeyStyles ContextKey = "styles" + KeyMetrics ContextKey = "metrics" + KeyHasMetrics ContextKey = "has-metrics" + KeyToast ContextKey = "toast" + KeyWithMetrics ContextKey = "withMetrics" + KeyViewConfig ContextKey = "viewConfig" + KeyWait ContextKey = "wait" + KeyPodCounting ContextKey = "podCounting" + KeyEnableImgScan ContextKey = "vulScan" ) diff --git a/internal/model/describe.go b/internal/model/describe.go index 96b5a6df00..c86c4001bb 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -39,6 +39,11 @@ func NewDescribe(gvr client.GVR, path string) *Describe { } } +// GVR returns the resource gvr. +func (d *Describe) GVR() client.GVR { + return d.gvr +} + // GetPath returns the active resource path. func (d *Describe) GetPath() string { return d.path diff --git a/internal/model/registry.go b/internal/model/registry.go index cb4259792a..8b7e238116 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -6,6 +6,7 @@ package model import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/render/helm" "github.com/derailed/k9s/internal/xray" ) @@ -25,19 +26,22 @@ var Registry = map[string]ResourceMeta{ DAO: &dao.Pulse{}, }, "helm": { - DAO: &dao.Helm{}, - Renderer: &render.Helm{}, - }, - // BOZO!! revamp with latest... - // "openfaas": { - // DAO: &dao.OpenFaas{}, - // Renderer: &render.OpenFaas{}, - // }, + DAO: &dao.HelmChart{}, + Renderer: &helm.Chart{}, + }, + "helm-history": { + DAO: &dao.HelmHistory{}, + Renderer: &helm.History{}, + }, "containers": { DAO: &dao.Container{}, Renderer: &render.Container{}, TreeRenderer: &xray.Container{}, }, + "scans": { + DAO: &dao.ImageScan{}, + Renderer: &render.ImageScan{}, + }, "contexts": { DAO: &dao.Context{}, Renderer: &render.Context{}, diff --git a/internal/model/types.go b/internal/model/types.go index 3c9c8e3c2e..00ab43d7d8 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -33,6 +33,7 @@ type ViewerToggleOpts map[string]bool type ResourceViewer interface { GetPath() string Filter(string) + GVR() client.GVR ClearFilter() Peek() []string SetOptions(context.Context, ViewerToggleOpts) diff --git a/internal/model/values.go b/internal/model/values.go index ef1a898122..7dce96c02d 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -39,8 +39,8 @@ func NewValues(gvr client.GVR, path string) *Values { } } -func getHelmDao() *dao.Helm { - return Registry["helm"].DAO.(*dao.Helm) +func getHelmDao() *dao.HelmChart { + return Registry["helm"].DAO.(*dao.HelmChart) } func getValues(path string, allValues bool) []string { @@ -51,6 +51,11 @@ func getValues(path string, allValues bool) []string { return strings.Split(string(vals), "\n") } +// GVR returns the resource gvr. +func (v *Values) GVR() client.GVR { + return v.gvr +} + // ToggleValues toggles between user supplied values and computed values. func (v *Values) ToggleValues() { v.allValues = !v.allValues diff --git a/internal/model/yaml.go b/internal/model/yaml.go index dd914462d3..332af49af0 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -43,6 +43,11 @@ func NewYAML(gvr client.GVR, path string) *YAML { } } +// GVR returns the resource gvr. +func (y *YAML) GVR() client.GVR { + return y.gvr +} + // GetPath returns the active resource path. func (y *YAML) GetPath() string { return y.path diff --git a/internal/render/benchmark.go b/internal/render/benchmark.go index 9124e0d15a..0f60d4decf 100644 --- a/internal/render/benchmark.go +++ b/internal/render/benchmark.go @@ -61,12 +61,12 @@ func (Benchmark) Header(ns string) Header { func (b Benchmark) Render(o interface{}, ns string, r *Row) error { bench, ok := o.(BenchInfo) if !ok { - return fmt.Errorf("No benchmarks available %T", o) + return fmt.Errorf("no benchmarks available %T", o) } data, err := b.readFile(bench.Path) if err != nil { - return fmt.Errorf("Unable to load bench file %s", bench.Path) + return fmt.Errorf("unable to load bench file %s", bench.Path) } r.ID = bench.Path @@ -75,7 +75,7 @@ func (b Benchmark) Render(o interface{}, ns string, r *Row) error { return err } b.augmentRow(r.Fields, data) - r.Fields[8] = asStatus(b.diagnose(ns, r.Fields)) + r.Fields[8] = AsStatus(b.diagnose(ns, r.Fields)) return nil } @@ -111,7 +111,7 @@ func (Benchmark) readFile(file string) (string, error) { func (b Benchmark) initRow(row Fields, f os.FileInfo) error { tokens := strings.Split(f.Name(), "_") if len(tokens) < 2 { - return fmt.Errorf("Invalid file name %s", f.Name()) + return fmt.Errorf("invalid file name %s", f.Name()) } row[0] = tokens[0] row[1] = tokens[1] diff --git a/internal/render/container.go b/internal/render/container.go index c32b64d9aa..662441c0e6 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -99,7 +99,7 @@ func (Container) Header(ns string) Header { func (c Container) Render(o interface{}, name string, r *Row) error { co, ok := o.(ContainerRes) if !ok { - return fmt.Errorf("Expected ContainerRes, but got %T", o) + return fmt.Errorf("expected ContainerRes, but got %T", o) } cur, res := gatherMetrics(co.Container, co.MX) @@ -127,8 +127,8 @@ func (c Container) Render(o interface{}, name string, r *Row) error { client.ToPercentageStr(cur.mem, res.mem), client.ToPercentageStr(cur.mem, res.lmem), ToContainerPorts(co.Container.Ports), - asStatus(c.diagnose(state, ready)), - toAge(co.Age), + AsStatus(c.diagnose(state, ready)), + ToAge(co.Age), } return nil diff --git a/internal/render/cr.go b/internal/render/cr.go index 925b75350f..5a0a84fdbd 100644 --- a/internal/render/cr.go +++ b/internal/render/cr.go @@ -42,7 +42,7 @@ func (ClusterRole) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ cr.Name, mapToStr(cr.Labels), - toAge(cr.GetCreationTimestamp()), + ToAge(cr.GetCreationTimestamp()), } return nil diff --git a/internal/render/crb.go b/internal/render/crb.go index 04842356a3..e051337dcf 100644 --- a/internal/render/crb.go +++ b/internal/render/crb.go @@ -33,7 +33,7 @@ func (ClusterRoleBinding) Header(string) Header { func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected ClusterRoleBinding, but got %T", o) + return fmt.Errorf("expected ClusterRoleBinding, but got %T", o) } var crb rbacv1.ClusterRoleBinding err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &crb) @@ -50,7 +50,7 @@ func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error { kind, ss, mapToStr(crb.Labels), - toAge(crb.GetCreationTimestamp()), + ToAge(crb.GetCreationTimestamp()), } return nil diff --git a/internal/render/crd.go b/internal/render/crd.go index b51f90b033..bed197fbc3 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -35,7 +35,7 @@ func (CustomResourceDefinition) Header(string) Header { func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected CustomResourceDefinition, but got %T", o) + return fmt.Errorf("expected CustomResourceDefinition, but got %T", o) } var crd v1.CustomResourceDefinition @@ -63,8 +63,8 @@ func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error crd.GetName(), naStrings(versions), mapToIfc(crd.GetLabels()), - asStatus(c.diagnose(crd.GetName(), crd.Spec.Versions)), - toAge(crd.GetCreationTimestamp()), + AsStatus(c.diagnose(crd.GetName(), crd.Spec.Versions)), + ToAge(crd.GetCreationTimestamp()), } return nil @@ -87,7 +87,7 @@ func (c CustomResourceDefinition) diagnose(n string, vv []v1.CustomResourceDefin if v.DeprecationWarning != nil { ee = append(ee, fmt.Errorf("%s", *v.DeprecationWarning)) } else { - ee = append(ee, fmt.Errorf("%s[%s] is deprecated!", n, v.Name)) + ee = append(ee, fmt.Errorf("%s[%s] is deprecated", n, v.Name)) } } } diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go index efaab7b355..aaf2e8fa4d 100644 --- a/internal/render/cronjob.go +++ b/internal/render/cronjob.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -22,9 +23,10 @@ type CronJob struct { // Header returns a header row. func (CronJob) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "SCHEDULE"}, HeaderColumn{Name: "SUSPEND"}, HeaderColumn{Name: "ACTIVE"}, @@ -36,13 +38,19 @@ func (CronJob) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h + } // Render renders a K8s resource to screen. func (c CronJob) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected CronJob, but got %T", o) + return fmt.Errorf("expected CronJob, but got %T", o) } var cj batchv1.CronJob err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cj) @@ -52,13 +60,14 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { lastScheduled := "" if cj.Status.LastScheduleTime != nil { - lastScheduled = toAge(*cj.Status.LastScheduleTime) + lastScheduled = ToAge(*cj.Status.LastScheduleTime) } r.ID = client.MetaFQN(cj.ObjectMeta) r.Fields = Fields{ cj.Namespace, cj.Name, + computeVulScore(&cj.Spec.JobTemplate.Spec.Template.Spec), cj.Spec.Schedule, boolPtrToStr(cj.Spec.Suspend), strconv.Itoa(len(cj.Status.Active)), @@ -68,7 +77,10 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { podImageNames(cj.Spec.JobTemplate.Spec.Template.Spec, true), mapToStr(cj.Labels), "", - toAge(cj.GetCreationTimestamp()), + ToAge(cj.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/dp.go b/internal/render/dp.go index 1e1b1738bc..3d1b2b5534 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -26,9 +27,10 @@ func (d Deployment) ColorerFunc() ColorerFunc { // Header returns a header row. func (Deployment) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, @@ -36,13 +38,18 @@ func (Deployment) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h } // Render renders a K8s resource to screen. func (d Deployment) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Deployment, but got %T", o) + return fmt.Errorf("expected Deployment, but got %T", o) } var dp appsv1.Deployment @@ -55,12 +62,16 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ dp.Namespace, dp.Name, + computeVulScore(&dp.Spec.Template.Spec), strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(dp.Status.Replicas)), strconv.Itoa(int(dp.Status.UpdatedReplicas)), strconv.Itoa(int(dp.Status.AvailableReplicas)), mapToStr(dp.Labels), - asStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)), - toAge(dp.GetCreationTimestamp()), + AsStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)), + ToAge(dp.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/ds.go b/internal/render/ds.go index dea0da35f2..87f92494ae 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,9 +22,10 @@ type DaemonSet struct { // Header returns a header row. func (DaemonSet) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, @@ -33,13 +35,18 @@ func (DaemonSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h } // Render renders a K8s resource to screen. func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected DaemonSet, but got %T", o) + return fmt.Errorf("expected DaemonSet, but got %T", o) } var ds appsv1.DaemonSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ds) @@ -51,14 +58,18 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ ds.Namespace, ds.Name, + computeVulScore(&ds.Spec.Template.Spec), strconv.Itoa(int(ds.Status.DesiredNumberScheduled)), strconv.Itoa(int(ds.Status.CurrentNumberScheduled)), strconv.Itoa(int(ds.Status.NumberReady)), strconv.Itoa(int(ds.Status.UpdatedNumberScheduled)), strconv.Itoa(int(ds.Status.NumberAvailable)), mapToStr(ds.Labels), - asStatus(d.diagnose(ds.Status.DesiredNumberScheduled, ds.Status.NumberReady)), - toAge(ds.GetCreationTimestamp()), + AsStatus(d.diagnose(ds.Status.DesiredNumberScheduled, ds.Status.NumberReady)), + ToAge(ds.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/ep.go b/internal/render/ep.go index 4a6b3640f2..15af70d1b4 100644 --- a/internal/render/ep.go +++ b/internal/render/ep.go @@ -33,7 +33,7 @@ func (Endpoints) Header(ns string) Header { func (e Endpoints) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Endpoints, but got %T", o) + return fmt.Errorf("expected Endpoints, but got %T", o) } var ep v1.Endpoints err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ep) @@ -47,7 +47,7 @@ func (e Endpoints) Render(o interface{}, ns string, r *Row) error { ep.Namespace, ep.Name, missing(toEPs(ep.Subsets)), - toAge(ep.GetCreationTimestamp()), + ToAge(ep.GetCreationTimestamp()), } return nil diff --git a/internal/render/generic.go b/internal/render/generic.go index f72ae0dcfc..b8c02a9f9c 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" ) @@ -99,8 +98,6 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error { } if d, ok := duration.(string); ok { r.Fields = append(r.Fields, d) - } else { - log.Warn().Msgf("No Duration detected on age field") } return nil diff --git a/internal/render/helm.go b/internal/render/helm.go deleted file mode 100644 index 6ec905a6ac..0000000000 --- a/internal/render/helm.go +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render - -import ( - "fmt" - "strconv" - - "github.com/derailed/k9s/internal/client" - "github.com/derailed/tcell/v2" - "helm.sh/helm/v3/pkg/release" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// Helm renders a helm chart to screen. -type Helm struct{} - -// IsGeneric identifies a generic handler. -func (Helm) IsGeneric() bool { - return false -} - -// ColorerFunc colors a resource row. -func (Helm) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { - return ErrColor - } - - return tcell.ColorMediumSpringGreen - } -} - -// Header returns a header row. -func (Helm) Header(_ string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "REVISION"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "CHART"}, - HeaderColumn{Name: "APP VERSION"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, - } -} - -// Render renders a chart to screen. -func (c Helm) Render(o interface{}, ns string, r *Row) error { - h, ok := o.(HelmRes) - if !ok { - return fmt.Errorf("expected HelmRes, but got %T", o) - } - - r.ID = client.FQN(h.Release.Namespace, h.Release.Name) - r.Fields = Fields{ - h.Release.Namespace, - h.Release.Name, - strconv.Itoa(h.Release.Version), - h.Release.Info.Status.String(), - h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version, - h.Release.Chart.Metadata.AppVersion, - asStatus(c.diagnose(h.Release.Info.Status.String())), - toAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}), - } - - return nil -} - -func (c Helm) diagnose(s string) error { - if s != "deployed" { - return fmt.Errorf("chart is in an invalid state") - } - - return nil -} - -// ---------------------------------------------------------------------------- -// Helpers... - -// HelmRes represents an helm chart resource. -type HelmRes struct { - Release *release.Release -} - -// GetObjectKind returns a schema object. -func (HelmRes) GetObjectKind() schema.ObjectKind { - return nil -} - -// DeepCopyObject returns a container copy. -func (h HelmRes) DeepCopyObject() runtime.Object { - return h -} diff --git a/internal/render/helm/chart.go b/internal/render/helm/chart.go new file mode 100644 index 0000000000..b41d51f0b5 --- /dev/null +++ b/internal/render/helm/chart.go @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package helm + +import ( + "fmt" + "strconv" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "helm.sh/helm/v3/pkg/release" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Chart renders a helm chart to screen. +type Chart struct{} + +// IsGeneric identifies a generic handler. +func (Chart) IsGeneric() bool { + return false +} + +// ColorerFunc colors a resource row. +func (Chart) ColorerFunc() render.ColorerFunc { + return render.DefaultColorer +} + +// Header returns a header row. +func (Chart) Header(_ string) render.Header { + return render.Header{ + render.HeaderColumn{Name: "NAMESPACE"}, + render.HeaderColumn{Name: "NAME"}, + render.HeaderColumn{Name: "REVISION"}, + render.HeaderColumn{Name: "STATUS"}, + render.HeaderColumn{Name: "CHART"}, + render.HeaderColumn{Name: "APP VERSION"}, + render.HeaderColumn{Name: "VALID", Wide: true}, + render.HeaderColumn{Name: "AGE", Time: true}, + } +} + +// Render renders a chart to screen. +func (c Chart) Render(o interface{}, ns string, r *render.Row) error { + h, ok := o.(ReleaseRes) + if !ok { + return fmt.Errorf("expected ReleaseRes, but got %T", o) + } + + r.ID = client.FQN(h.Release.Namespace, h.Release.Name) + r.Fields = render.Fields{ + h.Release.Namespace, + h.Release.Name, + strconv.Itoa(h.Release.Version), + h.Release.Info.Status.String(), + h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version, + h.Release.Chart.Metadata.AppVersion, + render.AsStatus(c.diagnose(h.Release.Info.Status.String())), + render.ToAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}), + } + + return nil +} + +func (c Chart) diagnose(s string) error { + if s != "deployed" { + return fmt.Errorf("chart is in an invalid state") + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +// ReleaseRes represents an helm chart resource. +type ReleaseRes struct { + Release *release.Release +} + +// GetObjectKind returns a schema object. +func (ReleaseRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (h ReleaseRes) DeepCopyObject() runtime.Object { + return h +} diff --git a/internal/render/helm/history.go b/internal/render/helm/history.go new file mode 100644 index 0000000000..5fbbaf6d09 --- /dev/null +++ b/internal/render/helm/history.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package helm + +import ( + "context" + "fmt" + "strconv" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" +) + +// History renders a History chart to screen. +type History struct{} + +// Healthy checks component health. +func (History) Healthy(ctx context.Context, o interface{}) error { + return nil +} + +// IsGeneric identifies a generic handler. +func (History) IsGeneric() bool { + return false +} + +// ColorerFunc colors a resource row. +func (History) ColorerFunc() render.ColorerFunc { + return render.DefaultColorer +} + +// Header returns a header row. +func (History) Header(_ string) render.Header { + return render.Header{ + render.HeaderColumn{Name: "REVISION"}, + render.HeaderColumn{Name: "STATUS"}, + render.HeaderColumn{Name: "CHART"}, + render.HeaderColumn{Name: "APP VERSION"}, + render.HeaderColumn{Name: "DESCRIPTION"}, + render.HeaderColumn{Name: "VALID", Wide: true}, + } +} + +// Render renders a chart to screen. +func (c History) Render(o interface{}, ns string, r *render.Row) error { + h, ok := o.(ReleaseRes) + if !ok { + return fmt.Errorf("expected HistoryRes, but got %T", o) + } + + r.ID = client.FQN(h.Release.Namespace, h.Release.Name) + r.ID += ":" + strconv.Itoa(h.Release.Version) + r.Fields = render.Fields{ + strconv.Itoa(h.Release.Version), + h.Release.Info.Status.String(), + h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version, + h.Release.Chart.Metadata.AppVersion, + h.Release.Info.Description, + render.AsStatus(c.diagnose(h.Release.Info.Status.String())), + } + + return nil +} + +func (c History) diagnose(s string) error { + return nil +} diff --git a/internal/render/helpers.go b/internal/render/helpers.go index cba70c9a71..2ccd503173 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -10,16 +10,28 @@ import ( "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" "github.com/rs/zerolog/log" "golang.org/x/text/language" "golang.org/x/text/message" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/duration" ) +func computeVulScore(spec *v1.PodSpec) string { + if vul.ImgScanner == nil { + return "0" + } + ii := ExtractImages(spec) + vul.ImgScanner.Enqueue(ii...) + + return vul.ImgScanner.Score(ii...) +} + func runesToNum(rr []rune) int64 { var r int64 var m int64 = 1 @@ -85,7 +97,8 @@ func Happy(ns string, h Header, r Row) bool { return strings.TrimSpace(r.Fields[validCol]) == "" } -func asStatus(err error) string { +// AsStatus returns error as string. +func AsStatus(err error) string { if err == nil { return "" } @@ -204,7 +217,8 @@ func boolToStr(b bool) string { } } -func toAge(t metav1.Time) string { +// ToAge converts time to human duration. +func ToAge(t metav1.Time) string { if t.IsZero() { return UnknownValue } diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 8b220c2174..84c19a49cb 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -103,7 +103,7 @@ func TestToAge(t *testing.T) { for k := range uu { uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t})) + assert.Equal(t, uc.e, ToAge(metav1.Time{Time: uc.t})) }) } } diff --git a/internal/render/img_scan.go b/internal/render/img_scan.go new file mode 100644 index 0000000000..03ab3c2eb9 --- /dev/null +++ b/internal/render/img_scan.go @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "strings" + + "github.com/derailed/k9s/internal/vul" + "github.com/derailed/tcell/v2" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + CVEParseIdx = 5 + sevColName = "SEVERITY" +) + +// ImageScan renders scans report table. +type ImageScan struct { + Base +} + +// ColorerFunc colors a resource row. +func (c ImageScan) ColorerFunc() ColorerFunc { + return func(ns string, h Header, re RowEvent) tcell.Color { + c := DefaultColorer(ns, h, re) + + sevCol := h.IndexOf(sevColName, true) + if sevCol == -1 { + return c + } + sev := strings.TrimSpace(re.Row.Fields[sevCol]) + switch sev { + case vul.Sev1: + c = tcell.ColorRed + case vul.Sev2: + c = tcell.ColorDarkOrange + case vul.Sev3: + c = tcell.ColorYellow + case vul.Sev4: + c = tcell.ColorDeepSkyBlue + case vul.Sev5: + c = tcell.ColorCadetBlue + default: + c = tcell.ColorDarkOliveGreen + } + + return c + } + +} + +// Header returns a header row. +func (ImageScan) Header(ns string) Header { + return Header{ + HeaderColumn{Name: "SEVERITY"}, + HeaderColumn{Name: "VULNERABILITY"}, + HeaderColumn{Name: "IMAGE"}, + HeaderColumn{Name: "LIBRARY"}, + HeaderColumn{Name: "VERSION"}, + HeaderColumn{Name: "FIXED-IN"}, + HeaderColumn{Name: "TYPE"}, + } +} + +// Render renders a K8s resource to screen. +func (is ImageScan) Render(o interface{}, name string, r *Row) error { + res, ok := o.(ImageScanRes) + if !ok { + return fmt.Errorf("expected ImageScanRes, but got %T", o) + } + + r.ID = fmt.Sprintf("%s|%s", res.Image, strings.Join(res.Row, "|")) + r.Fields = Fields{ + res.Row.Severity(), + res.Row.Vulnerability(), + res.Image, + res.Row.Name(), + res.Row.Version(), + res.Row.Fix(), + res.Row.Type(), + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +// ImageScanRes represents a container and its metrics. +type ImageScanRes struct { + Image string + Row vul.Row +} + +// GetObjectKind returns a schema object. +func (ImageScanRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (is ImageScanRes) DeepCopyObject() runtime.Object { + return is +} diff --git a/internal/render/job.go b/internal/render/job.go index c036a82d45..b6325d8cf9 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -10,6 +10,7 @@ import ( "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,9 +26,10 @@ type Job struct { // Header returns a header row. func (Job) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "COMPLETIONS"}, HeaderColumn{Name: "DURATION"}, HeaderColumn{Name: "SELECTOR", Wide: true}, @@ -36,13 +38,18 @@ func (Job) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h } // Render renders a K8s resource to screen. func (j Job) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Job, but got %T", o) + return fmt.Errorf("expected Job, but got %T", o) } var job batchv1.Job err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &job) @@ -57,13 +64,17 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ job.Namespace, job.Name, + computeVulScore(&job.Spec.Template.Spec), ready, toDuration(job.Status), jobSelector(job.Spec), cc, ii, - asStatus(j.diagnose(ready, job.Status.CompletionTime)), - toAge(job.GetCreationTimestamp()), + AsStatus(j.diagnose(ready, job.Status.CompletionTime)), + ToAge(job.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/node.go b/internal/render/node.go index 3fb2bd74ec..17c80d947f 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -35,6 +35,7 @@ func (Node) Header(_ string) Header { HeaderColumn{Name: "NAME"}, HeaderColumn{Name: "STATUS"}, HeaderColumn{Name: "ROLE"}, + HeaderColumn{Name: "TAINTS"}, HeaderColumn{Name: "VERSION"}, HeaderColumn{Name: "KERNEL", Wide: true}, HeaderColumn{Name: "INTERNAL-IP", Wide: true}, @@ -89,6 +90,7 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { no.Name, join(statuses, ","), join(roles, ","), + strconv.Itoa(len(no.Spec.Taints)), no.Status.NodeInfo.KubeletVersion, no.Status.NodeInfo.KernelVersion, iIP, @@ -101,8 +103,8 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { toMc(a.cpu), toMi(a.mem), mapToStr(no.Labels), - asStatus(n.diagnose(statuses)), - toAge(no.GetCreationTimestamp()), + AsStatus(n.diagnose(statuses)), + ToAge(no.GetCreationTimestamp()), } return nil diff --git a/internal/render/node_test.go b/internal/render/node_test.go index 584670201a..1b54ba919f 100644 --- a/internal/render/node_test.go +++ b/internal/render/node_test.go @@ -24,8 +24,8 @@ func TestNodeRender(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "minikube", r.ID) - e := render.Fields{"minikube", "Ready", "master", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} - assert.Equal(t, e, r.Fields[:14]) + e := render.Fields{"minikube", "Ready", "master", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} + assert.Equal(t, e, r.Fields[:15]) } func BenchmarkNodeRender(b *testing.B) { diff --git a/internal/render/np.go b/internal/render/np.go index 2904a353fb..26333aebfe 100644 --- a/internal/render/np.go +++ b/internal/render/np.go @@ -40,7 +40,7 @@ func (NetworkPolicy) Header(ns string) Header { func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected NetworkPolicy, but got %T", o) + return fmt.Errorf("expected NetworkPolicy, but got %T", o) } var np netv1.NetworkPolicy err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &np) @@ -63,7 +63,7 @@ func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { eb, mapToStr(np.Labels), "", - toAge(np.GetCreationTimestamp()), + ToAge(np.GetCreationTimestamp()), } return nil diff --git a/internal/render/ns.go b/internal/render/ns.go index 6dcd2a6f9d..ddf34cf6e4 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -55,7 +55,7 @@ func (Namespace) Header(string) Header { func (n Namespace) Render(o interface{}, _ string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Namespace, but got %T", o) + return fmt.Errorf("expected Namespace, but got %T", o) } var ns v1.Namespace err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ns) @@ -68,8 +68,8 @@ func (n Namespace) Render(o interface{}, _ string, r *Row) error { ns.Name, string(ns.Status.Phase), mapToStr(ns.Labels), - asStatus(n.diagnose(ns.Status.Phase)), - toAge(ns.GetCreationTimestamp()), + AsStatus(n.diagnose(ns.Status.Phase)), + ToAge(ns.GetCreationTimestamp()), } return nil diff --git a/internal/render/ofaas.go b/internal/render/ofaas.go deleted file mode 100644 index 0df5f904df..0000000000 --- a/internal/render/ofaas.go +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render - -// BOZO!! revamp with latest... - -// import ( -// "errors" -// "fmt" -// "strconv" -// "time" - -// "github.com/derailed/k9s/internal/client" -// "github.com/derailed/tview" -// "github.com/derailed/tcell/v2" - -// ofaas "github.com/openfaas/faas-provider/types" -// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// "k8s.io/apimachinery/pkg/runtime" -// "k8s.io/apimachinery/pkg/runtime/schema" -// ) - -// const ( -// fnStatusReady = "Ready" -// fnStatusNotReady = "Not Ready" -// ) - -// // OpenFaas renders an openfaas function to screen. -// type OpenFaas struct{} - -// // ColorerFunc colors a resource row. -// func (o OpenFaas) ColorerFunc() ColorerFunc { -// return func(ns string, h Header, re RowEvent) tcell.Color { -// if !Happy(ns, h, re.Row) { -// return ErrColor -// } - -// return tcell.ColorPaleTurquoise -// } -// } - -// // Header returns a header row. -// func (OpenFaas) Header(ns string) Header { -// return Header{ -// HeaderColumn{Name: "NAMESPACE"}, -// HeaderColumn{Name: "NAME"}, -// HeaderColumn{Name: "STATUS"}, -// HeaderColumn{Name: "IMAGE"}, -// HeaderColumn{Name: "LABELS"}, -// HeaderColumn{Name: "INVOCATIONS", Align: tview.AlignRight}, -// HeaderColumn{Name: "REPLICAS", Align: tview.AlignRight}, -// HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, -// HeaderColumn{Name: "VALID", Wide: true}, -// HeaderColumn{Name: "AGE", Time: true}, -// } -// } - -// // Render renders a chart to screen. -// func (o OpenFaas) Render(i interface{}, ns string, r *Row) error { -// fn, ok := i.(OpenFaasRes) -// if !ok { -// return fmt.Errorf("expected OpenFaasRes, but got %T", o) -// } - -// var labels string -// if fn.Function.Labels != nil { -// labels = mapToStr(*fn.Function.Labels) -// } -// status := fnStatusReady -// if fn.Function.AvailableReplicas == 0 { -// status = fnStatusNotReady -// } - -// r.ID = client.FQN(fn.Function.Namespace, fn.Function.Name) -// r.Fields = Fields{ -// fn.Function.Namespace, -// fn.Function.Name, -// status, -// fn.Function.Image, -// labels, -// strconv.Itoa(int(fn.Function.InvocationCount)), -// strconv.Itoa(int(fn.Function.Replicas)), -// strconv.Itoa(int(fn.Function.AvailableReplicas)), -// asStatus(o.diagnose(status)), -// toAge(metav1.Time{Time: time.Now()}), -// } - -// return nil -// } - -// func (OpenFaas) diagnose(status string) error { -// if status != "Ready" { -// return errors.New("function not ready") -// } - -// return nil -// } - -// // ---------------------------------------------------------------------------- -// // Helpers... - -// // OpenFaasRes represents an openfaas function resource. -// type OpenFaasRes struct { -// Function ofaas.FunctionStatus `json:"function"` -// } - -// // GetObjectKind returns a schema object. -// func (OpenFaasRes) GetObjectKind() schema.ObjectKind { -// return nil -// } - -// // DeepCopyObject returns a container copy. -// func (h OpenFaasRes) DeepCopyObject() runtime.Object { -// return h -// } diff --git a/internal/render/ofaas_test.go b/internal/render/ofaas_test.go deleted file mode 100644 index dbefa9d6d2..0000000000 --- a/internal/render/ofaas_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render_test - -// BOZO!! revamp with latest... - -// import ( -// "testing" - -// "github.com/derailed/k9s/internal/render" -// ofaas "github.com/openfaas/faas-provider/types" -// "github.com/stretchr/testify/assert" -// ) - -// func TestOpenFaasRender(t *testing.T) { -// c := render.OpenFaas{} -// r := render.NewRow(9) -// c.Render(makeFn("blee"), "", &r) - -// assert.Equal(t, "default/blee", r.ID) -// assert.Equal(t, render.Fields{"default", "blee", "Ready", "nginx:0", "fred=blee", "10", "1", "1"}, r.Fields[:8]) -// } - -// // Helpers... - -// func makeFn(n string) render.OpenFaasRes { -// return render.OpenFaasRes{ -// Function: ofaas.FunctionStatus{ -// Name: n, -// Namespace: "default", -// Image: "nginx:0", -// InvocationCount: 10, -// Replicas: 1, -// AvailableReplicas: 1, -// Labels: &map[string]string{"fred": "blee"}, -// }, -// } -// } diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 357133b071..3b29962d56 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -41,7 +41,7 @@ func (PodDisruptionBudget) Header(ns string) Header { func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected PodDisruptionBudget, but got %T", o) + return fmt.Errorf("expected PodDisruptionBudget, but got %T", o) } var pdb v1beta1.PodDisruptionBudget err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pdb) @@ -60,8 +60,8 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(int(pdb.Status.DesiredHealthy)), strconv.Itoa(int(pdb.Status.ExpectedPods)), mapToStr(pdb.Labels), - asStatus(p.diagnose(pdb.Spec.MinAvailable, pdb.Status.CurrentHealthy)), - toAge(pdb.GetCreationTimestamp()), + AsStatus(p.diagnose(pdb.Spec.MinAvailable, pdb.Status.CurrentHealthy)), + ToAge(pdb.GetCreationTimestamp()), } return nil diff --git a/internal/render/pod.go b/internal/render/pod.go index dd33265842..a79cafe252 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -18,12 +18,14 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" ) const ( // NodeUnreachablePodReason is reason and message set on a pod when its state // cannot be confirmed as kubelet is unresponsive on the node it is (was) running. NodeUnreachablePodReason = "NodeLost" // k8s.io/kubernetes/pkg/util/node.NodeUnreachablePodReason + vulIdx = 2 ) const ( @@ -83,9 +85,10 @@ func (p Pod) ColorerFunc() ColorerFunc { // Header returns a header row. func (Pod) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "PF"}, HeaderColumn{Name: "READY"}, HeaderColumn{Name: "STATUS"}, @@ -107,6 +110,22 @@ func (Pod) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h +} + +// ExtractImages returns a collection of container images. +// !!BOZO!! If this has any legs?? enable scans on other container types. +func ExtractImages(spec *v1.PodSpec) []string { + ii := make([]string, 0, len(spec.Containers)) + for _, c := range spec.Containers { + ii = append(ii, c.Image) + } + + return ii } // Render renders a K8s resource to screen. @@ -121,8 +140,10 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { return err } - ss := po.Status.ContainerStatuses - cr, _, rc := p.Statuses(ss) + ics := po.Status.InitContainerStatuses + _, _, irc := p.Statuses(ics) + cs := po.Status.ContainerStatuses + cr, _, rc := p.Statuses(cs) c, r := p.gatherPodMX(&po, pwm.MX) phase := p.Phase(&po) @@ -130,10 +151,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { row.Fields = Fields{ po.Namespace, po.ObjectMeta.Name, + computeVulScore(&po.Spec), "●", strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)), phase, - strconv.Itoa(rc), + strconv.Itoa(rc + irc), na(po.Status.PodIP), na(po.Spec.NodeName), asNominated(po.Status.NominatedNodeName), @@ -148,8 +170,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { client.ToPercentageStr(c.mem, r.lmem), p.mapQOS(po.Status.QOSClass), mapToStr(po.Labels), - asStatus(p.diagnose(phase, cr, len(ss))), - toAge(po.GetCreationTimestamp()), + AsStatus(p.diagnose(phase, cr, len(cs))), + ToAge(po.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/pv.go b/internal/render/pv.go index 72ee55e097..d91ea18b6e 100644 --- a/internal/render/pv.go +++ b/internal/render/pv.go @@ -70,7 +70,7 @@ func (PersistentVolume) Header(string) Header { func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected PersistentVolume, but got %T", o) + return fmt.Errorf("expected PersistentVolume, but got %T", o) } var pv v1.PersistentVolume err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pv) @@ -105,8 +105,8 @@ func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error { pv.Status.Reason, p.volumeMode(pv.Spec.VolumeMode), mapToStr(pv.Labels), - asStatus(p.diagnose(phase)), - toAge(pv.GetCreationTimestamp()), + AsStatus(p.diagnose(phase)), + ToAge(pv.GetCreationTimestamp()), } return nil diff --git a/internal/render/pvc.go b/internal/render/pvc.go index 76414e12fb..bd3cc43b36 100644 --- a/internal/render/pvc.go +++ b/internal/render/pvc.go @@ -37,7 +37,7 @@ func (PersistentVolumeClaim) Header(ns string) Header { func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected PersistentVolumeClaim, but got %T", o) + return fmt.Errorf("expected PersistentVolumeClaim, but got %T", o) } var pvc v1.PersistentVolumeClaim err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pvc) @@ -74,8 +74,8 @@ func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { accessModes, class, mapToStr(pvc.Labels), - asStatus(p.diagnose(string(phase))), - toAge(pvc.GetCreationTimestamp()), + AsStatus(p.diagnose(string(phase))), + ToAge(pvc.GetCreationTimestamp()), } return nil diff --git a/internal/render/ro.go b/internal/render/ro.go index e88e084edb..3e90164627 100644 --- a/internal/render/ro.go +++ b/internal/render/ro.go @@ -36,7 +36,7 @@ func (Role) Header(ns string) Header { func (r Role) Render(o interface{}, ns string, row *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Role, but got %T", o) + return fmt.Errorf("expected Role, but got %T", o) } var ro rbacv1.Role err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ro) @@ -53,7 +53,7 @@ func (r Role) Render(o interface{}, ns string, row *Row) error { ro.Name, mapToStr(ro.Labels), "", - toAge(ro.GetCreationTimestamp()), + ToAge(ro.GetCreationTimestamp()), ) return nil diff --git a/internal/render/rob.go b/internal/render/rob.go index 1cb56adc05..46783a88f8 100644 --- a/internal/render/rob.go +++ b/internal/render/rob.go @@ -40,7 +40,7 @@ func (RoleBinding) Header(ns string) Header { func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected RoleBinding, but got %T", o) + return fmt.Errorf("expected RoleBinding, but got %T", o) } var rb rbacv1.RoleBinding err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rb) @@ -62,7 +62,7 @@ func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { ss, mapToStr(rb.Labels), "", - toAge(rb.GetCreationTimestamp()), + ToAge(rb.GetCreationTimestamp()), ) return nil diff --git a/internal/render/row.go b/internal/render/row.go index c562bc23cd..858b11987a 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -193,19 +193,16 @@ func (s RowSorter) Less(i, j int) bool { // ---------------------------------------------------------------------------- // Helpers... -// Less return true if c1 < c2. +// Less return true if c1 <= c2. func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool { var less bool switch { case isNumber: - v1, v2 = strings.Replace(v1, ",", "", -1), strings.Replace(v2, ",", "", -1) - less = sortorder.NaturalLess(v1, v2) + less = lessNumber(v1, v2) case isDuration: - d1, d2 := durationToSeconds(v1), durationToSeconds(v2) - less = d1 <= d2 + less = lessDuration(v1, v2) case isCapacity: - c1, c2 := capacityToNumber(v1), capacityToNumber(v2) - less = c1 <= c2 + less = lessCapacity(v1, v2) default: less = sortorder.NaturalLess(v1, v2) } @@ -215,3 +212,20 @@ func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool { return less } + +func lessDuration(s1, s2 string) bool { + d1, d2 := durationToSeconds(s1), durationToSeconds(s2) + return d1 <= d2 +} + +func lessCapacity(s1, s2 string) bool { + c1, c2 := capacityToNumber(s1), capacityToNumber(s2) + + return c1 <= c2 +} + +func lessNumber(s1, s2 string) bool { + v1, v2 := strings.Replace(s1, ",", "", -1), strings.Replace(s2, ",", "", -1) + + return sortorder.NaturalLess(v1, v2) +} diff --git a/internal/render/row_event.go b/internal/render/row_event.go index e3f1230d11..7248371db0 100644 --- a/internal/render/row_event.go +++ b/internal/render/row_event.go @@ -244,50 +244,3 @@ func (r RowEventSorter) Less(i, j int) bool { return !less } - -// ---------------------------------------------------------------------------- - -// // IdSorter sorts row events by a given id. -// type IdSorter struct { -// Ids map[string]int -// Events RowEvents -// } - -// func (s IdSorter) Len() int { -// return len(s.Events) -// } - -// func (s IdSorter) Swap(i, j int) { -// s.Events[i], s.Events[j] = s.Events[j], s.Events[i] -// } - -// func (s IdSorter) Less(i, j int) bool { -// return s.Ids[s.Events[i].Row.ID] < s.Ids[s.Events[j].Row.ID] -// } - -// ---------------------------------------------------------------------------- - -// // StringSet represents a collection of unique strings. -// type StringSet []string - -// // Add adds a new item in the set. -// func (ss StringSet) Add(item string) StringSet { -// if ss.In(item) { -// return ss -// } -// return append(ss, item) -// } - -// // In checks if a string is in the set. -// func (ss StringSet) In(item string) bool { -// return ss.indexOf(item) >= 0 -// } - -// func (ss StringSet) indexOf(item string) int { -// for i, s := range ss { -// if s == item { -// return i -// } -// } -// return -1 -// } diff --git a/internal/render/rs.go b/internal/render/rs.go index 59a1a16bdb..a8287e4566 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -26,9 +27,10 @@ func (r ReplicaSet) ColorerFunc() ColorerFunc { // Header returns a header row. func (ReplicaSet) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, @@ -36,13 +38,18 @@ func (ReplicaSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h } // Render renders a K8s resource to screen. func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected ReplicaSet, but got %T", o) + return fmt.Errorf("expected ReplicaSet, but got %T", o) } var rs appsv1.ReplicaSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rs) @@ -54,12 +61,16 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { row.Fields = Fields{ rs.Namespace, rs.Name, + computeVulScore(&rs.Spec.Template.Spec), strconv.Itoa(int(*rs.Spec.Replicas)), strconv.Itoa(int(rs.Status.Replicas)), strconv.Itoa(int(rs.Status.ReadyReplicas)), mapToStr(rs.Labels), - asStatus(r.diagnose(rs)), - toAge(rs.GetCreationTimestamp()), + AsStatus(r.diagnose(rs)), + ToAge(rs.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/sa.go b/internal/render/sa.go index dfa276ad68..43f7c89861 100644 --- a/internal/render/sa.go +++ b/internal/render/sa.go @@ -34,7 +34,7 @@ func (ServiceAccount) Header(ns string) Header { func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected ServiceAccount, but got %T", o) + return fmt.Errorf("expected ServiceAccount, but got %T", o) } var sa v1.ServiceAccount err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sa) @@ -49,7 +49,7 @@ func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(len(sa.Secrets)), mapToStr(sa.Labels), "", - toAge(sa.GetCreationTimestamp()), + ToAge(sa.GetCreationTimestamp()), } return nil diff --git a/internal/render/sc.go b/internal/render/sc.go index 392c1c4e97..d5f5ecaaeb 100644 --- a/internal/render/sc.go +++ b/internal/render/sc.go @@ -37,7 +37,7 @@ func (StorageClass) Header(ns string) Header { func (s StorageClass) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected StorageClass, but got %T", o) + return fmt.Errorf("expected StorageClass, but got %T", o) } var sc storagev1.StorageClass err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sc) @@ -54,7 +54,7 @@ func (s StorageClass) Render(o interface{}, ns string, r *Row) error { boolPtrToStr(sc.AllowVolumeExpansion), mapToStr(sc.Labels), "", - toAge(sc.GetCreationTimestamp()), + ToAge(sc.GetCreationTimestamp()), } return nil diff --git a/internal/render/screen_dump.go b/internal/render/screen_dump.go index 36293fe6b7..9b237d7a07 100644 --- a/internal/render/screen_dump.go +++ b/internal/render/screen_dump.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/duration" ) // ScreenDump renders a screendumps to screen. @@ -58,7 +59,7 @@ func (b ScreenDump) Render(o interface{}, ns string, r *Row) error { // Helpers... func timeToAge(timestamp time.Time) string { - return time.Since(timestamp).String() + return duration.HumanDuration(time.Since(timestamp)) } // FileRes represents a file resource. diff --git a/internal/render/sts.go b/internal/render/sts.go index 78be0c123f..08704bfd9c 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/vul" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,9 +21,10 @@ type StatefulSet struct { // Header returns a header row. func (StatefulSet) Header(ns string) Header { - return Header{ + h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "VS"}, HeaderColumn{Name: "READY"}, HeaderColumn{Name: "SELECTOR", Wide: true}, HeaderColumn{Name: "SERVICE"}, @@ -32,13 +34,18 @@ func (StatefulSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } + if vul.ImgScanner == nil { + h = append(h[:vulIdx], h[vulIdx+1:]...) + } + + return h } // Render renders a K8s resource to screen. func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected StatefulSet, but got %T", o) + return fmt.Errorf("expected StatefulSet, but got %T", o) } var sts appsv1.StatefulSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sts) @@ -50,14 +57,18 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ sts.Namespace, sts.Name, + computeVulScore(&sts.Spec.Template.Spec), strconv.Itoa(int(sts.Status.ReadyReplicas)) + "/" + strconv.Itoa(int(sts.Status.Replicas)), asSelector(sts.Spec.Selector), na(sts.Spec.ServiceName), podContainerNames(sts.Spec.Template.Spec, true), podImageNames(sts.Spec.Template.Spec, true), mapToStr(sts.Labels), - asStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)), - toAge(sts.GetCreationTimestamp()), + AsStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)), + ToAge(sts.GetCreationTimestamp()), + } + if vul.ImgScanner == nil { + r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) } return nil diff --git a/internal/render/subject.go b/internal/render/subject.go index b61f95daaa..b58e0ba7b4 100644 --- a/internal/render/subject.go +++ b/internal/render/subject.go @@ -37,7 +37,7 @@ func (Subject) Header(ns string) Header { func (s Subject) Render(o interface{}, ns string, r *Row) error { res, ok := o.(SubjectRes) if !ok { - return fmt.Errorf("Expected SubjectRes, but got %T", s) + return fmt.Errorf("expected SubjectRes, but got %T", s) } r.ID = res.Name diff --git a/internal/render/svc.go b/internal/render/svc.go index 642c293c10..2f3cb30cf8 100644 --- a/internal/render/svc.go +++ b/internal/render/svc.go @@ -40,7 +40,7 @@ func (Service) Header(ns string) Header { func (s Service) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Service, but got %T", o) + return fmt.Errorf("expected Service, but got %T", o) } var svc v1.Service err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &svc) @@ -58,8 +58,8 @@ func (s Service) Render(o interface{}, ns string, r *Row) error { mapToStr(svc.Spec.Selector), ToPorts(svc.Spec.Ports), mapToStr(svc.Labels), - asStatus(s.diagnose()), - toAge(svc.GetCreationTimestamp()), + AsStatus(s.diagnose()), + ToAge(svc.GetCreationTimestamp()), } return nil diff --git a/internal/ui/config.go b/internal/ui/config.go index 6ab5af1f50..3d55da8f4c 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -6,7 +6,6 @@ package ui import ( "context" "errors" - "fmt" "os" "path/filepath" @@ -100,6 +99,7 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error select { case evt := <-w.Events: if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod { + log.Debug().Msgf("Skin changed: %s", c.skinFile) s.QueueUpdateDraw(func() { c.RefreshStyles(c.Config.K9s.CurrentCluster) }) @@ -117,8 +117,12 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error } }() - log.Debug().Msgf("SkinWatcher watching `%s", c.skinFile) - return w.Add(config.K9sHome()) + log.Debug().Msgf("SkinWatcher watching %q", config.K9sHome()) + if err := w.Add(config.K9sHome()); err != nil { + return err + } + log.Debug().Msgf("SkinWatcher watching %q", config.K9sSkinDir) + return w.Add(config.K9sSkinDir) } // BenchConfig location of the benchmarks configuration file. @@ -130,21 +134,35 @@ func BenchConfig(context string) string { func (c *Configurator) RefreshStyles(context string) { c.BenchFile = BenchConfig(context) - clusterSkins := config.YamlExtension(filepath.Join(config.K9sHome(), fmt.Sprintf("%s_skin.yml", context))) if c.Styles == nil { c.Styles = config.NewStyles() } else { c.Styles.Reset() } - if err := c.Styles.Load(clusterSkins); err != nil { - if errors.Is(err, os.ErrNotExist) { - log.Warn().Msgf("No context specific skin file found -- %s", clusterSkins) + + var skin string + if c.Config != nil { + cl, ok := c.Config.K9s.Clusters[context] + if !ok { + return + } + skin = cl.Skin + } + + var ( + skinFile = filepath.Join(config.K9sSkinDir, skin+".yml") + ) + if skin != "" { + if err := c.Styles.Load(skinFile); err != nil { + if errors.Is(err, os.ErrNotExist) { + log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.K9sSkinDir) + } else { + log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err) + } } else { - log.Error().Msgf("Failed to parse context specific skin file -- %s. %s.", clusterSkins, err) + c.updateStyles(skinFile) + return } - } else { - c.updateStyles(clusterSkins) - return } if err := c.Styles.Load(config.K9sStylesFile); err != nil { diff --git a/internal/ui/dialog/error.go b/internal/ui/dialog/error.go index 26fdebfa5b..1d81db5e77 100644 --- a/internal/ui/dialog/error.go +++ b/internal/ui/dialog/error.go @@ -41,7 +41,7 @@ func ShowError(styles config.Dialog, pages *ui.Pages, msg string) { } func cowTalk(says string) string { - msg := fmt.Sprintf("< Ruroh? %s >", says) + msg := fmt.Sprintf("< Ruroh? %s >", strings.TrimSuffix(says, "\n")) buff := make([]string, 0, len(cow)+3) buff = append(buff, msg) buff = append(buff, cow...) diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index bcc7271035..113f8cd531 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -149,24 +149,31 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey { switch evt.Key() { case tcell.KeyBackspace2, tcell.KeyBackspace, tcell.KeyDelete: p.model.Delete() + case tcell.KeyRune: p.model.Add(evt.Rune()) + case tcell.KeyEscape: p.model.ClearText(true) p.model.SetActive(false) + case tcell.KeyEnter, tcell.KeyCtrlE: p.model.SetText(p.model.GetText(), "") p.model.SetActive(false) + case tcell.KeyCtrlW, tcell.KeyCtrlU: p.model.ClearText(true) + case tcell.KeyUp: if s, ok := m.NextSuggestion(); ok { - p.suggest(p.model.GetText(), s) + p.model.SetText(s, "") } + case tcell.KeyDown: if s, ok := m.PrevSuggestion(); ok { - p.suggest(p.model.GetText(), s) + p.model.SetText(s, "") } + case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF: if s, ok := m.CurrentSuggestion(); ok { p.model.SetText(p.model.GetText()+s, "") diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 0e89bf890e..5575c94528 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -165,14 +165,12 @@ func rxFilter(q string, inverse bool, data *render.TableData) (*render.TableData RowEvents: make(render.RowEvents, 0, len(data.RowEvents)), Namespace: data.Namespace, } - ageIndex := -1 - if data.Header.HasAge() { - ageIndex = data.Header.IndexOf("AGE", true) - } + ageIndex := data.Header.IndexOf("AGE", true) + const spacer = " " for _, re := range data.RowEvents { ff := re.Row.Fields - if ageIndex > 0 { + if ageIndex >= 0 && ageIndex+1 <= len(ff) { ff = append(ff[0:ageIndex], ff[ageIndex+1:]...) } fields := strings.Join(ff, spacer) diff --git a/internal/view/app.go b/internal/view/app.go index 7de20d7888..374f0ba74c 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -23,6 +23,7 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/k9s/internal/watch" "github.com/derailed/tcell/v2" "github.com/derailed/tview" @@ -120,9 +121,28 @@ func (a *App) Init(version string, rate int) error { a.layout(ctx) a.initSignals() + if a.Config.K9s.EnableImageScan { + a.initImgScanner(version) + } + return nil } +func (a *App) stopImgScanner() { + if vul.ImgScanner != nil { + vul.ImgScanner.Stop() + } +} + +func (a *App) initImgScanner(version string) { + defer func(t time.Time) { + log.Debug().Msgf("Scanner init time %s", time.Since(t)) + }(time.Now()) + + vul.ImgScanner = vul.NewImageScanner() + go vul.ImgScanner.Init("k9s", version) +} + func (a *App) layout(ctx context.Context) { flash := ui.NewFlash(a.App) go flash.Watch(ctx, a.Flash().Channel()) @@ -492,6 +512,8 @@ func (a *App) BailOut() { if err := nukeK9sShell(a); err != nil { log.Error().Err(err).Msgf("nuking k9s shell pod") } + + a.stopImgScanner() a.factory.Terminate() a.App.BailOut() } @@ -686,8 +708,7 @@ func (a *App) gotoResource(cmd, path string, clearStack bool) { func (a *App) inject(c model.Component, clearStack bool) error { ctx := context.WithValue(context.Background(), internal.KeyApp, a) if err := c.Init(ctx); err != nil { - log.Error().Err(err).Msgf("component init failed for %q", c.Name()) - //dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error()) + log.Error().Err(err).Msgf("Component init failed for %q", c.Name()) return err } if clearStack { diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index db9f856c55..daa26be302 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -47,7 +47,7 @@ func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) { return } - details := NewDetails(b.App(), "Results", fileToSubject(path), false).Update(data) + details := NewDetails(b.App(), "Results", fileToSubject(path), contentYAML, false).Update(data) if err := app.inject(details, false); err != nil { app.Flash().Err(err) } diff --git a/internal/view/browser.go b/internal/view/browser.go index c5f46447d7..240f4dffb0 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -62,7 +62,7 @@ func (b *Browser) Init(ctx context.Context) error { } ns := client.CleanseNamespace(b.app.Config.ActiveNamespace()) if dao.IsK8sMeta(b.meta) && b.app.ConOK() { - if _, e := b.app.factory.CanForResource(ns, b.GVR().String(), client.MonitorAccess); e != nil { + if _, e := b.app.factory.CanForResource(ns, b.GVR().String(), client.ListAccess); e != nil { return e } } @@ -262,7 +262,7 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - v := NewLiveView(b.app, "YAML", model.NewYAML(b.GVR(), path)) + v := NewLiveView(b.app, yamlAction, model.NewYAML(b.GVR(), path)) if err := v.app.inject(v, false); err != nil { v.app.Flash().Err(err) } @@ -367,33 +367,42 @@ func (b *Browser) editCmd(evt *tcell.EventKey) *tcell.EventKey { if path == "" { return evt } + + b.Stop() + defer b.Start() + if err := editRes(b.app, b.GVR(), path); err != nil { + b.App().Flash().Err(err) + } + + return nil +} + +func editRes(app *App, gvr client.GVR, path string) error { + if path == "" { + return fmt.Errorf("nothing selected %q", path) + } ns, n := client.Namespaced(path) if client.IsClusterScoped(ns) { ns = client.AllNamespaces } - if b.GVR().String() == "v1/namespaces" { + if gvr.String() == "v1/namespaces" { ns = n } - if ok, err := b.app.Conn().CanI(ns, b.GVR().String(), []string{"patch"}); !ok || err != nil { - b.App().Flash().Errf("Current user can't edit resource %s", b.GVR()) - return nil + if ok, err := app.Conn().CanI(ns, gvr.String(), []string{"patch"}); !ok || err != nil { + return fmt.Errorf("current user can't edit resource %s", gvr) } - b.Stop() - defer b.Start() - { - args := make([]string, 0, 10) - args = append(args, "edit") - args = append(args, b.GVR().FQN(n)) - if ns != client.AllNamespaces { - args = append(args, "-n", ns) - } - if err := runK(b.app, shellOpts{clear: true, args: args}); err != nil { - b.app.Flash().Errf("Edit command failed: %s", err) - } + args := make([]string, 0, 10) + args = append(args, "edit") + args = append(args, gvr.FQN(n)) + if ns != client.AllNamespaces { + args = append(args, "-n", ns) + } + if err := runK(app, shellOpts{clear: true, args: args}); err != nil { + app.Flash().Errf("Edit command failed: %s", err) } - return evt + return nil } func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -404,7 +413,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { } ns := b.namespaces[i] - auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), client.MonitorAccess) + auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), client.ListAccess) if !auth { if err == nil { err = fmt.Errorf("current user can't access namespace %s", ns) @@ -488,7 +497,7 @@ func (b *Browser) refreshActions() { } if !dao.IsK9sMeta(b.meta) { - aa[ui.KeyY] = ui.NewKeyAction("YAML", b.viewCmd, true) + aa[ui.KeyY] = ui.NewKeyAction(yamlAction, b.viewCmd, true) aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true) } diff --git a/internal/view/command.go b/internal/view/command.go index 0fc57bf099..2baae3ac80 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -66,21 +66,17 @@ func (c *Command) Reset(clear bool) error { } func allowedXRay(gvr client.GVR) bool { - gg := []string{ - "v1/pods", - "v1/services", - "apps/v1/deployments", - "apps/v1/daemonsets", - "apps/v1/statefulsets", - "apps/v1/replicasets", - } - for _, g := range gg { - if g == gvr.String() { - return true - } + gg := map[string]struct{}{ + "v1/pods": {}, + "v1/services": {}, + "apps/v1/deployments": {}, + "apps/v1/daemonsets": {}, + "apps/v1/statefulsets": {}, + "apps/v1/replicasets": {}, } - return false + _, ok := gg[gvr.String()] + return ok } func (c *Command) xrayCmd(cmd string) error { @@ -273,10 +269,11 @@ func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) ( return fmt.Errorf("no component found for %s", gvr) } c.app.Flash().Infof("Viewing %s...", client.NewGVR(gvr).R()) + command := cmd if tokens := strings.Split(cmd, " "); len(tokens) >= 2 { - cmd = tokens[0] + command = tokens[0] } - c.app.Config.SetActiveView(cmd) + c.app.Config.SetActiveView(command) if err := c.app.Config.Save(); err != nil { log.Error().Err(err).Msg("Config save failed!") } diff --git a/internal/view/cow.go b/internal/view/cow.go index d71f3e898e..6920f4dabc 100644 --- a/internal/view/cow.go +++ b/internal/view/cow.go @@ -74,7 +74,7 @@ func cowTalk(says string, w int) string { msg := fmt.Sprintf("[red::]< [::b]Ruroh? %s[::-] >", says) buff := make([]string, 0, len(cow)+3) buff = append(buff, "[red::] "+strings.Repeat("─", len(says)+8)) - buff = append(buff, msg) + buff = append(buff, strings.TrimSuffix(msg, "\n")) buff = append(buff, " "+strings.Repeat("─", len(says)+8)) rCount := w/2 - 8 if rCount < 0 { diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index a5a5d7de15..940904111a 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -35,7 +35,7 @@ type CronJob struct { // NewCronJob returns a new viewer. func NewCronJob(gvr client.GVR) ResourceViewer { - c := CronJob{ResourceViewer: NewBrowser(gvr)} + c := CronJob{ResourceViewer: NewVulnerabilityExtender(NewBrowser(gvr))} c.AddBindKeysFn(c.bindKeys) c.GetTable().SetEnterFn(c.showJobs) diff --git a/internal/view/details.go b/internal/view/details.go index e3f96f44c7..cc366d33cd 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -17,7 +17,11 @@ import ( "github.com/sahilm/fuzzy" ) -const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " +const ( + detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " + contentTXT = "text" + contentYAML = "yaml" +) // Details represents a generic text viewer. type Details struct { @@ -32,20 +36,22 @@ type Details struct { currentRegion, maxRegions int searchable bool fullScreen bool + contentType string } // NewDetails returns a details viewer. -func NewDetails(app *App, title, subject string, searchable bool) *Details { +func NewDetails(app *App, title, subject, contentType string, searchable bool) *Details { d := Details{ - Flex: tview.NewFlex(), - text: tview.NewTextView(), - app: app, - title: title, - subject: subject, - actions: make(ui.KeyActions), - cmdBuff: model.NewFishBuff('/', model.FilterBuffer), - model: model.NewText(), - searchable: searchable, + Flex: tview.NewFlex(), + text: tview.NewTextView(), + app: app, + title: title, + subject: subject, + actions: make(ui.KeyActions), + cmdBuff: model.NewFishBuff('/', model.FilterBuffer), + model: model.NewText(), + searchable: searchable, + contentType: contentType, } d.AddItem(d.text, 0, 1, true) @@ -85,7 +91,12 @@ func (d *Details) InCmdMode() bool { // TextChanged notifies the model changed. func (d *Details) TextChanged(lines []string) { - d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) + switch d.contentType { + case contentYAML: + d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) + default: + d.text.SetText(strings.Join(lines, "\n")) + } d.text.ScrollToBeginning() } diff --git a/internal/view/dir.go b/internal/view/dir.go index 061a72f327..5d30276cd1 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -75,7 +75,7 @@ func (d *Dir) bindKeys(aa ui.KeyActions) { d.bindDangerousKeys(aa) } aa.Add(ui.KeyActions{ - ui.KeyY: ui.NewKeyAction("YAML", d.viewCmd, true), + ui.KeyY: ui.NewKeyAction(yamlAction, d.viewCmd, true), tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true), }) } @@ -96,7 +96,7 @@ func (d *Dir) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails(d.App(), "YAML", sel, true).Update(string(yaml)) + details := NewDetails(d.App(), yamlAction, sel, contentYAML, true).Update(string(yaml)) if err := d.App().inject(details, false); err != nil { d.App().Flash().Err(err) } @@ -216,7 +216,7 @@ func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey { res = "message:\n" + fmtResults(res) } - details := NewDetails(d.App(), "Applied Manifest", sel, true).Update(res) + details := NewDetails(d.App(), "Applied Manifest", sel, contentYAML, true).Update(res) if err := d.App().inject(details, false); err != nil { d.App().Flash().Err(err) } @@ -255,7 +255,7 @@ func (d *Dir) delCmd(evt *tcell.EventKey) *tcell.EventKey { } else { res = "message:\n" + fmtResults(res) } - details := NewDetails(d.App(), "Deleted Manifest", sel, true).Update(res) + details := NewDetails(d.App(), "Deleted Manifest", sel, contentYAML, true).Update(res) if err := d.App().inject(details, false); err != nil { d.App().Flash().Err(err) } diff --git a/internal/view/dp.go b/internal/view/dp.go index 8f56948711..9af1bd93bb 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -24,10 +24,12 @@ type Deploy struct { func NewDeploy(gvr client.GVR) ResourceViewer { var d Deploy d.ResourceViewer = NewPortForwardExtender( - NewRestartExtender( - NewScaleExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), d.logOptions), + NewVulnerabilityExtender( + NewRestartExtender( + NewScaleExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), d.logOptions), + ), ), ), ), @@ -89,20 +91,24 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) { return &opts, nil } -func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, path string) { +func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, fqn string) { var ddp dao.Deployment - dp, err := ddp.GetInstance(app.factory, path) + ddp.Init(d.App().factory, d.GVR()) + + dp, err := ddp.GetInstance(fqn) if err != nil { app.Flash().Err(err) return } - showPodsFromSelector(app, path, dp.Spec.Selector) + showPodsFromSelector(app, fqn, dp.Spec.Selector) } -func (d *Deploy) dp(path string) (*appsv1.Deployment, error) { +func (d *Deploy) dp(fqn string) (*appsv1.Deployment, error) { var dp dao.Deployment - return dp.GetInstance(d.App().factory, path) + dp.Init(d.App().factory, d.GVR()) + + return dp.GetInstance(fqn) } // ---------------------------------------------------------------------------- diff --git a/internal/view/ds.go b/internal/view/ds.go index 7276e74f9e..ab92227364 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -18,9 +18,11 @@ type DaemonSet struct { func NewDaemonSet(gvr client.GVR) ResourceViewer { d := DaemonSet{ ResourceViewer: NewPortForwardExtender( - NewRestartExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), nil), + NewVulnerabilityExtender( + NewRestartExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), nil), + ), ), ), ), diff --git a/internal/view/exec.go b/internal/view/exec.go index 2a74dd1eb8..7bdcef004d 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -249,19 +249,13 @@ func ssh(a *App, node string) error { if err := launchShellPod(a, node); err != nil { return err } - - cl := a.Config.K9s.ActiveCluster() - if cl == nil { - return fmt.Errorf("no active cluster detected") - } - ns := cl.ShellPod.Namespace + ns := a.Config.K9s.ShellPod.Namespace return sshIn(a, client.FQN(ns, k9sShellPodName()), k9sShell) } func sshIn(a *App, fqn, co string) error { - cl := a.Config.K9s.ActiveCluster() - cfg := cl.ShellPod + cfg := a.Config.K9s.ShellPod os, err := getPodOS(a.factory, fqn) if err != nil { return fmt.Errorf("os detect failed: %w", err) @@ -295,8 +289,7 @@ func nukeK9sShell(a *App) error { return nil } - cl := a.Config.K9s.ActiveCluster() - ns := cl.ShellPod.Namespace + ns := a.Config.K9s.ShellPod.Namespace ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() @@ -315,9 +308,11 @@ func nukeK9sShell(a *App) error { func launchShellPod(a *App, node string) error { a.Flash().Infof("Launching node shell on %s...", node) - cl := a.Config.K9s.ActiveCluster() - ns := cl.ShellPod.Namespace - spec := k9sShellPod(node, cl.ShellPod) + + var ( + spo = a.Config.K9s.ShellPod + spec = k9sShellPod(node, spo) + ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -325,13 +320,13 @@ func launchShellPod(a *App, node string) error { if err != nil { return err } - conn := dial.CoreV1().Pods(ns) + conn := dial.CoreV1().Pods(spo.Namespace) if _, err := conn.Create(ctx, spec, metav1.CreateOptions{}); err != nil { return err } for i := 0; i < k9sShellRetryCount; i++ { - o, err := a.factory.Get("v1/pods", client.FQN(ns, k9sShellPodName()), true, labels.Everything()) + o, err := a.factory.Get("v1/pods", client.FQN(spo.Namespace, k9sShellPodName()), true, labels.Everything()) if err != nil { time.Sleep(k9sShellRetryDelay) continue @@ -372,6 +367,7 @@ func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod { }, Resources: asResource(cfg.Limits), Stdin: true, + TTY: cfg.TTY, SecurityContext: &v1.SecurityContext{ Privileged: &priv, }, diff --git a/internal/view/helm.go b/internal/view/helm_chart.go similarity index 50% rename from internal/view/helm.go rename to internal/view/helm_chart.go index 73e6df89a6..e6aba216e1 100644 --- a/internal/view/helm.go +++ b/internal/view/helm_chart.go @@ -14,41 +14,71 @@ import ( "github.com/rs/zerolog/log" ) -// Helm represents a helm chart view. -type Helm struct { +// HelmChart represents a helm chart view. +type HelmChart struct { ResourceViewer Values *model.Values } // NewHelm returns a new alias view. -func NewHelm(gvr client.GVR) ResourceViewer { - c := Helm{ +func NewHelmChart(gvr client.GVR) ResourceViewer { + c := HelmChart{ ResourceViewer: NewBrowser(gvr), } c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) - c.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) + c.GetTable().SetSelectedStyle(tcell.StyleDefault. + Foreground(tcell.ColorWhite). + Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) c.AddBindKeysFn(c.bindKeys) + c.GetTable().SetEnterFn(c.viewReleases) c.SetContextFn(c.chartContext) return &c } -func (c *Helm) chartContext(ctx context.Context) context.Context { +func (c *HelmChart) chartContext(ctx context.Context) context.Context { return ctx } -func (c *Helm) bindKeys(aa ui.KeyActions) { - aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) +func (c *HelmChart) bindKeys(aa ui.KeyActions) { + aa.Delete(tcell.KeyCtrlS) aa.Add(ui.KeyActions{ - ui.KeyShiftN: ui.NewKeyAction("Sort Name", c.GetTable().SortColCmd(nameCol, true), false), + ui.KeyR: ui.NewKeyAction("Releases", c.historyCmd, true), ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(statusCol, true), false), - ui.KeyShiftA: ui.NewKeyAction("Sort Age", c.GetTable().SortColCmd(ageCol, true), false), ui.KeyV: ui.NewKeyAction("Values", c.getValsCmd(), true), }) } -func (c *Helm) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey { +func (c *HelmChart) viewReleases(app *App, model ui.Tabular, _, path string) { + v := NewHistory(client.NewGVR("helm-history")) + v.SetContextFn(c.helmContext) + if err := app.inject(v, false); err != nil { + app.Flash().Err(err) + } +} + +func (c *HelmChart) historyCmd(evt *tcell.EventKey) *tcell.EventKey { + path := c.GetTable().GetSelectedItem() + if path == "" { + return evt + } + c.viewReleases(c.App(), c.GetTable().GetModel(), c.GVR().String(), path) + + return nil +} + +func (c *HelmChart) helmContext(ctx context.Context) context.Context { + path := c.GetTable().GetSelectedItem() + if path == "" { + return ctx + } + ctx = context.WithValue(ctx, internal.KeyFQN, path) + + return context.WithValue(ctx, internal.KeyPath, path) +} + +func (c *HelmChart) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { path := c.GetTable().GetSelectedItem() if path == "" { @@ -66,7 +96,7 @@ func (c *Helm) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey { } } -func (c *Helm) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey { +func (c *HelmChart) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey { c.Values.ToggleValues() if err := c.Values.Refresh(c.defaultCtx()); err != nil { log.Error().Err(err).Msgf("helm refresh failed") @@ -76,6 +106,6 @@ func (c *Helm) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (c *Helm) defaultCtx() context.Context { +func (c *HelmChart) defaultCtx() context.Context { return context.WithValue(context.Background(), internal.KeyFactory, c.App().factory) } diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go new file mode 100644 index 0000000000..9a1b7eb86e --- /dev/null +++ b/internal/view/helm_history.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "context" + "fmt" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/render/helm" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/ui/dialog" + "github.com/derailed/tcell/v2" +) + +// History represents a helm History view. +type History struct { + ResourceViewer +} + +// NewHelm returns a new alias view. +func NewHistory(gvr client.GVR) ResourceViewer { + h := History{ + ResourceViewer: NewBrowser(gvr), + } + h.GetTable().SetColorerFn(helm.History{}.ColorerFunc()) + h.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) + h.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) + h.AddBindKeysFn(h.bindKeys) + h.SetContextFn(h.HistoryContext) + + return &h +} + +// Init initializes the vie +func (h *History) Init(ctx context.Context) error { + if err := h.ResourceViewer.Init(ctx); err != nil { + return err + } + h.GetTable().SetSortCol("REVISION", false) + + return nil +} + +func (h *History) HistoryContext(ctx context.Context) context.Context { + return ctx +} + +func (h *History) bindKeys(aa ui.KeyActions) { + if !h.App().Config.K9s.IsReadOnly() { + h.bindDangerousKeys(aa) + } + + aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace, tcell.KeyCtrlD) + aa.Add(ui.KeyActions{ + ui.KeyShiftN: ui.NewKeyAction("Sort Revision", h.GetTable().SortColCmd("REVISION", true), false), + ui.KeyShiftS: ui.NewKeyAction("Sort Status", h.GetTable().SortColCmd("STATUS", true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort Age", h.GetTable().SortColCmd("AGE", true), false), + }) +} + +func (h *History) bindDangerousKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ + ui.KeyR: ui.NewKeyAction("RollBackTo...", h.rollbackCmd, true), + }) +} + +func (h *History) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey { + path := h.GetTable().GetSelectedItem() + if path == "" { + return evt + } + + ns, nrev := client.Namespaced(path) + tt := strings.Split(nrev, ":") + n, rev := nrev, "" + if len(tt) == 2 { + n, rev = tt[0], tt[1] + } + + h.Stop() + defer h.Start() + msg := fmt.Sprintf("RollingBack chart [yellow::b]%s[-::-] to release <[orangered::b]%s[-::-]>?", n, rev) + dialog.ShowConfirmAck(h.App().App, h.App().Content.Pages, n, false, "Confirm Rollback", msg, func() { + ctx, cancel := context.WithTimeout(context.Background(), h.App().Conn().Config().CallTimeout()) + defer cancel() + if err := h.rollback(ctx, client.FQN(ns, n), rev); err != nil { + h.App().Flash().Err(err) + } else { + h.App().Flash().Infof("Rollout restart in progress for char `%s...", n) + } + }, func() {}) + + return nil +} + +func (h *History) rollback(ctx context.Context, path, rev string) error { + var hm dao.HelmHistory + hm.Init(h.App().factory, h.GVR()) + if err := hm.Rollback(ctx, path, rev); err != nil { + return err + } + h.Refresh() + + return nil +} diff --git a/internal/view/helpers.go b/internal/view/helpers.go index dea7a10137..ef1cc73544 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -26,9 +26,13 @@ func clipboardWrite(text string) error { return clipboard.WriteAll(text) } +func sanitizeEsc(s string) string { + return strings.ReplaceAll(s, "[]", "]") +} + func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { - if err := clipboardWrite(v.GetText(true)); err != nil { + if err := clipboardWrite(sanitizeEsc(v.GetText(true))); err != nil { flash.Err(err) return evt } diff --git a/internal/view/img_scan.go b/internal/view/img_scan.go new file mode 100644 index 0000000000..811e1af0e8 --- /dev/null +++ b/internal/view/img_scan.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "runtime" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tcell/v2" +) + +const ( + imgScanTitle = "Scans" + browseOSX = "open" + browseLinux = "sensible-browser" + cveGovURL = "https://nvd.nist.gov/vuln/detail/" + ghsaURL = "https://github.com/advisories/" +) + +// ImageScan represents an image vulnerability scan view. +type ImageScan struct { + ResourceViewer +} + +// NewImageScan returns a new scans view. +func NewImageScan(gvr client.GVR) ResourceViewer { + v := ImageScan{} + v.ResourceViewer = NewBrowser(gvr) + v.AddBindKeysFn(v.bindKeys) + v.GetTable().SetEnterFn(v.viewCVE) + v.GetTable().SetSortCol("SEVERITY", true) + + return &v +} + +// Name returns the component name. +func (s *ImageScan) Name() string { return imgScanTitle } + +func (c *ImageScan) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlZ, tcell.KeyCtrlW) + + aa.Add(ui.KeyActions{ + ui.KeyShiftL: ui.NewKeyAction("Sort Lib", c.GetTable().SortColCmd("LIBRARY", false), true), + ui.KeyShiftS: ui.NewKeyAction("Sort Severity", c.GetTable().SortColCmd("SEVERITY", false), true), + ui.KeyShiftF: ui.NewKeyAction("Sort Fixed-in", c.GetTable().SortColCmd("FIXED-IN", false), true), + ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerability", c.GetTable().SortColCmd("VULNERABILITY", false), true), + }) +} + +func (s *ImageScan) viewCVE(app *App, model ui.Tabular, gvr, path string) { + bin := browseLinux + if runtime.GOOS == "darwin" { + bin = browseOSX + } + + tt := strings.Split(path, "|") + if len(tt) < 7 { + app.Flash().Errf("parse path failed: %s", path) + } + cve := tt[render.CVEParseIdx] + site := cveGovURL + if strings.Index(cve, "GHSA") == 0 { + site = ghsaURL + } + site += cve + + ok, errChan := run(app, shellOpts{ + background: true, + binary: bin, + args: []string{site}, + }) + if !ok { + app.Flash().Errf("unable to run browser command") + return + } + for e := range errChan { + if e != nil { + app.Flash().Err(e) + } + } +} diff --git a/internal/view/job.go b/internal/view/job.go index 60f391b3dc..24df7d38d1 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -19,7 +19,7 @@ type Job struct { // NewJob returns a new viewer. func NewJob(gvr client.GVR) ResourceViewer { - j := Job{ResourceViewer: NewLogsExtender(NewBrowser(gvr), nil)} + j := Job{ResourceViewer: NewVulnerabilityExtender(NewLogsExtender(NewBrowser(gvr), nil))} j.GetTable().SetEnterFn(j.showPods) j.GetTable().SetSortCol("AGE", true) diff --git a/internal/view/live_view.go b/internal/view/live_view.go index 40163d504e..46f5a82734 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -8,7 +8,6 @@ import ( "fmt" "strconv" "strings" - "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" @@ -20,7 +19,10 @@ import ( "github.com/sahilm/fuzzy" ) -const liveViewTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " +const ( + liveViewTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " + yamlAction = "YAML" +) // LiveView represents a live text viewer. type LiveView struct { @@ -79,7 +81,9 @@ func (v *LiveView) Init(_ context.Context) error { v.bindKeys() v.SetInputCapture(v.keyboard) - v.model.AddListener(v) + if v.model != nil { + v.model.AddListener(v) + } return nil } @@ -102,7 +106,11 @@ func (*LiveView) linesWithRegions(lines []string, matches fuzzy.Matches) []strin offsetForLine := make(map[int]int) for i, m := range matches { loc, line := m.MatchedIndexes, ll[m.Index] + if len(loc) < 2 { + continue + } offset := offsetForLine[m.Index] + loc[0], loc[1] = loc[0]+offset, loc[1]+offset regionStr := `<<<"search_` + strconv.Itoa(i) + `">>>` + line[loc[0]:loc[1]] + `<<<"">>>` ll[m.Index] = line[:loc[0]] + regionStr + line[loc[1]:] @@ -114,10 +122,6 @@ func (*LiveView) linesWithRegions(lines []string, matches fuzzy.Matches) []strin // ResourceChanged notifies when the filter changes. func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) { v.app.QueueUpdateDraw(func() { - defer func(t time.Time) { - log.Debug().Msgf("Live view render time: %v", time.Since(t)) - }(time.Now()) - v.text.SetTextAlign(tview.AlignLeft) v.maxRegions = len(matches) @@ -126,7 +130,6 @@ func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) { } lines = v.linesWithRegions(lines, matches) - v.text.SetText(colorizeYAML(v.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) v.text.Highlight() if v.currentRegion < v.maxRegions { @@ -164,13 +167,32 @@ func (v *LiveView) bindKeys() { tcell.KeyDelete: ui.NewSharedKeyAction("Erase", v.eraseCmd, false), }) - if v.title == "YAML" { + if !v.app.Config.K9s.IsReadOnly() { + v.actions.Add(ui.KeyActions{ + ui.KeyE: ui.NewKeyAction("Edit", v.editCmd, true), + }) + } + if v.title == yamlAction { v.actions.Add(ui.KeyActions{ ui.KeyM: ui.NewKeyAction("Toggle ManagedFields", v.toggleManagedCmd, true), }) } } +func (v *LiveView) editCmd(evt *tcell.EventKey) *tcell.EventKey { + path := v.model.GetPath() + if path == "" { + return evt + } + v.Stop() + defer v.Start() + if err := editRes(v.app, v.model.GVR(), path); err != nil { + v.app.Flash().Err(err) + } + + return nil +} + // ToggleRefreshCmd is used for pausing the refreshing of data on config map and secrets. func (v *LiveView) toggleRefreshCmd(evt *tcell.EventKey) *tcell.EventKey { v.autoRefresh = !v.autoRefresh @@ -198,7 +220,6 @@ func (v *LiveView) StylesChanged(s *config.Styles) { v.SetBackgroundColor(v.app.Styles.BgColor()) v.text.SetTextColor(v.app.Styles.FgColor()) v.SetBorderFocusColor(v.app.Styles.Frame().Border.FocusColor.Color()) - v.ResourceChanged(v.model.Peek(), nil) } // Actions returns menu actions. @@ -351,10 +372,11 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentContextDir(), v.title, v.text.GetText(true)); err != nil { + name := fmt.Sprintf("%s--%s", strings.Replace(v.model.GetPath(), "/", "-", 1), strings.ToLower(v.title)) + if _, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentContextDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { v.app.Flash().Err(err) } else { - v.app.Flash().Infof("Log %s saved successfully!", path) + v.app.Flash().Infof("File %q saved successfully!", name) } return nil @@ -364,7 +386,10 @@ func (v *LiveView) updateTitle() { if v.title == "" { return } - fmat := fmt.Sprintf(liveViewTitleFmt, v.title, v.model.GetPath()) + var fmat string + if v.model != nil { + fmat = fmt.Sprintf(liveViewTitleFmt, v.title, v.model.GetPath()) + } buff := v.cmdBuff.GetText() if buff == "" { diff --git a/internal/view/live_view_test.go b/internal/view/live_view_test.go index 045cfd4594..92509a8912 100644 --- a/internal/view/live_view_test.go +++ b/internal/view/live_view_test.go @@ -4,9 +4,11 @@ package view import ( + "context" "strconv" "testing" + "github.com/derailed/k9s/internal/config" "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" ) @@ -15,6 +17,20 @@ func matchTag(i int, s string) string { return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>` } +func TestLiveViewSetText(t *testing.T) { + s := ` +apiVersion: v1 + data: + the secret name you want to quote to use tls.","title":"secretName","type":"string"}},"required":["http","class","classInSpec"],"type":"object"} +` + + v := NewLiveView(NewApp(config.NewConfig(nil)), "fred", nil) + assert.NoError(t, v.Init(context.Background())) + v.text.SetText(colorizeYAML(config.Yaml{}, s)) + + assert.Equal(t, s, sanitizeEsc(v.text.GetText(true))) +} + func TestLiveView_linesWithRegions(t *testing.T) { uu := map[string]struct { lines []string diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 151d28adfa..6c20a81848 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -58,7 +58,7 @@ func isResourcePath(p string) bool { func (l *LogsExtender) showLogs(path string, prev bool) { ns, _ := client.Namespaced(path) - _, err := l.App().factory.CanForResource(ns, "v1/pods", client.MonitorAccess) + _, err := l.App().factory.CanForResource(ns, "v1/pods", client.ListAccess) if err != nil { l.App().Flash().Err(err) return diff --git a/internal/view/node.go b/internal/view/node.go index 82e9d33f80..c1f2274f9c 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -54,14 +54,12 @@ func (n *Node) bindDangerousKeys(aa ui.KeyActions) { } func (n *Node) bindKeys(aa ui.KeyActions) { - aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace) - if !n.App().Config.K9s.IsReadOnly() { n.bindDangerousKeys(aa) } aa.Add(ui.KeyActions{ - ui.KeyY: ui.NewKeyAction("YAML", n.yamlCmd, true), + ui.KeyY: ui.NewKeyAction(yamlAction, n.yamlCmd, true), ui.KeyShiftC: ui.NewKeyAction("Sort CPU", n.GetTable().SortColCmd(cpuCol, false), false), ui.KeyShiftM: ui.NewKeyAction("Sort MEM", n.GetTable().SortColCmd(memCol, false), false), ui.KeyShift0: ui.NewKeyAction("Sort Pods", n.GetTable().SortColCmd("PODS", false), false), @@ -102,7 +100,7 @@ func drainNode(v ResourceViewer, path string, opts dao.DrainOptions) { v.Stop() defer v.Start() { - d := NewDetails(v.App(), "Drain Progress", path, true) + d := NewDetails(v.App(), "Drain Progress", path, contentYAML, true) if err := v.App().inject(d, false); err != nil { v.App().Flash().Err(err) } @@ -196,7 +194,7 @@ func (n *Node) yamlCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails(n.App(), "YAML", sel, true).Update(raw) + details := NewDetails(n.App(), yamlAction, sel, contentYAML, true).Update(raw) if err := n.App().inject(details, false); err != nil { n.App().Flash().Err(err) } diff --git a/internal/view/pod.go b/internal/view/pod.go index d2fd2b43e5..c317304ab9 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -45,8 +45,10 @@ type Pod struct { func NewPod(gvr client.GVR) ResourceViewer { var p Pod p.ResourceViewer = NewPortForwardExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), p.logOptions), + NewVulnerabilityExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), p.logOptions), + ), ), ) p.AddBindKeysFn(p.bindKeys) diff --git a/internal/view/registrar.go b/internal/view/registrar.go index c5a226e590..52532c3633 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -23,7 +23,7 @@ func loadCustomViewers() MetaViewers { func helmViewers(vv MetaViewers) { vv[client.NewGVR("helm")] = MetaViewer{ - viewerFn: NewHelm, + viewerFn: NewHelmChart, } } @@ -64,13 +64,12 @@ func miscViewers(vv MetaViewers) { vv[client.NewGVR("contexts")] = MetaViewer{ viewerFn: NewContext, } - // BOZO!! revamp with latest... - // vv[client.NewGVR("openfaas")] = MetaViewer{ - // viewerFn: NewOpenFaas, - // } vv[client.NewGVR("containers")] = MetaViewer{ viewerFn: NewContainer, } + vv[client.NewGVR("scans")] = MetaViewer{ + viewerFn: NewImageScan, + } vv[client.NewGVR("portforwards")] = MetaViewer{ viewerFn: NewPortForward, } diff --git a/internal/view/rs.go b/internal/view/rs.go index 9a0e64fc9e..739ce6a9d7 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -21,7 +21,7 @@ type ReplicaSet struct { // NewReplicaSet returns a new viewer. func NewReplicaSet(gvr client.GVR) ResourceViewer { r := ReplicaSet{ - ResourceViewer: NewBrowser(gvr), + ResourceViewer: NewVulnerabilityExtender(NewBrowser(gvr)), } r.AddBindKeysFn(r.bindKeys) r.GetTable().SetEnterFn(r.showPods) diff --git a/internal/view/secret.go b/internal/view/secret.go index 73a1580513..3cc78234c9 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -69,7 +69,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails(s.App(), "Secret Decoder", path, true).Update(string(raw)) + details := NewDetails(s.App(), "Secret Decoder", path, contentYAML, true).Update(string(raw)) if err := s.App().inject(details, false); err != nil { s.App().Flash().Err(err) } diff --git a/internal/view/sts.go b/internal/view/sts.go index d8b72d7f0a..cbb77a7cd1 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -21,10 +21,12 @@ type StatefulSet struct { func NewStatefulSet(gvr client.GVR) ResourceViewer { var s StatefulSet s.ResourceViewer = NewPortForwardExtender( - NewRestartExtender( - NewScaleExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), s.logOptions), + NewVulnerabilityExtender( + NewRestartExtender( + NewScaleExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), s.logOptions), + ), ), ), ), diff --git a/internal/view/vul_extender.go b/internal/view/vul_extender.go new file mode 100644 index 0000000000..2bd59b93a3 --- /dev/null +++ b/internal/view/vul_extender.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "context" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tcell/v2" +) + +// VulnerabilityExtender adds vul image scan extensions. +type VulnerabilityExtender struct { + ResourceViewer +} + +// NewVulnerabilityExtender returns a new extender. +func NewVulnerabilityExtender(r ResourceViewer) ResourceViewer { + v := VulnerabilityExtender{ResourceViewer: r} + v.AddBindKeysFn(v.bindKeys) + + return &v +} + +func (v *VulnerabilityExtender) bindKeys(aa ui.KeyActions) { + if v.App().Config.K9s.EnableImageScan { + aa.Add(ui.KeyActions{ + ui.KeyV: ui.NewKeyAction("Show Vulnerabilities", v.showVulCmd, true), + ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerabilities", v.GetTable().SortColCmd("VS", true), false), + }) + } +} + +func (v *VulnerabilityExtender) showVulCmd(evt *tcell.EventKey) *tcell.EventKey { + isv := NewImageScan(client.NewGVR("scans")) + isv.SetContextFn(v.selContext) + if err := v.App().inject(isv, false); err != nil { + v.App().Flash().Err(err) + } + + return nil +} + +func (v *VulnerabilityExtender) selContext(ctx context.Context) context.Context { + ctx = context.WithValue(ctx, internal.KeyPath, v.GetTable().GetSelectedItem()) + return context.WithValue(ctx, internal.KeyGVR, v.GVR()) +} diff --git a/internal/view/xray.go b/internal/view/xray.go index d2ffd581e8..f2dfb0a8b9 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -160,7 +160,7 @@ func (x *Xray) refreshActions() { aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", x.deleteCmd, true) } if !dao.IsK9sMeta(x.meta) { - aa[ui.KeyY] = ui.NewKeyAction("YAML", x.viewCmd, true) + aa[ui.KeyY] = ui.NewKeyAction(yamlAction, x.viewCmd, true) aa[ui.KeyD] = ui.NewKeyAction("Describe", x.describeCmd, true) } @@ -265,7 +265,7 @@ func (x *Xray) showLogs(spec *xray.NodeSpec, prev bool) { } ns, _ := client.Namespaced(path) - _, err := x.app.factory.CanForResource(ns, "v1/pods", client.MonitorAccess) + _, err := x.app.factory.CanForResource(ns, "v1/pods", client.ListAccess) if err != nil { x.app.Flash().Err(err) return @@ -341,7 +341,7 @@ func (x *Xray) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails(x.app, "YAML", spec.Path(), true).Update(raw) + details := NewDetails(x.app, yamlAction, spec.Path(), contentYAML, true).Update(raw) if err := x.app.inject(details, false); err != nil { x.app.Flash().Err(err) } @@ -391,7 +391,7 @@ func (x *Xray) describe(gvr, path string) { return } - details := NewDetails(x.app, "Describe", path, true).Update(yaml) + details := NewDetails(x.app, "Describe", path, contentYAML, true).Update(yaml) if err := x.app.inject(details, false); err != nil { x.app.Flash().Err(err) } diff --git a/internal/view/yaml.go b/internal/view/yaml.go index a1998a26e3..0fda208726 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -29,7 +29,6 @@ const ( func colorizeYAML(style config.Yaml, raw string) string { lines := strings.Split(tview.Escape(raw), "\n") - fullFmt := strings.Replace(yamlFullFmt, "[key", "["+style.KeyColor.String(), 1) fullFmt = strings.Replace(fullFmt, "[colon", "["+style.ColonColor.String(), 1) fullFmt = strings.Replace(fullFmt, "[val", "["+style.ValueColor.String(), 1) @@ -64,14 +63,12 @@ func enableRegion(str string) string { } func saveYAML(screenDumpDir, context, name, data string) (string, error) { - dir := filepath.Join(screenDumpDir, context) + dir := filepath.Join(screenDumpDir, config.SanitizeFilename(context)) if err := ensureDir(dir); err != nil { return "", err } - now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.yml", config.SanitizeFilename(name), now) - + fName := fmt.Sprintf("%s--%d.yml", config.SanitizeFilename(name), time.Now().Unix()) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(path, mod, 0600) diff --git a/internal/vul/scan.go b/internal/vul/scan.go new file mode 100644 index 0000000000..05819c305c --- /dev/null +++ b/internal/vul/scan.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "fmt" + "io" + "strings" + + grypeDb "github.com/anchore/grype/grype/db/v5" + "github.com/anchore/grype/grype/match" + "github.com/anchore/grype/grype/vulnerability" +) + +const ( + wontFix = "(won't fix)" + naValue = "" +) + +// Scans tracks scans per image. +type Scans map[string]*Scan + +// Dump dump reports to stdout. +func (s Scans) Dump(w io.Writer) { + for k, v := range s { + fmt.Fprintf(w, "Image: %s -- ", k) + v.Tally.Dump(w) + fmt.Fprintln(w) + v.Dump(w) + } +} + +// Scan tracks image vulnerability scan. +type Scan struct { + ID string + Table *table + Tally tally +} + +func newScan(img string) *Scan { + return &Scan{ID: img, Table: newTable()} +} + +// Dump dump report to stdout. +func (s *Scan) Dump(w io.Writer) { + s.Table.dump(w) +} + +func (s *Scan) run(mm *match.Matches, store vulnerability.MetadataProvider) error { + for m := range mm.Enumerate() { + meta, err := store.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace) + if err != nil { + return err + } + var severity string + if meta != nil { + severity = meta.Severity + } + fixVersion := strings.Join(m.Vulnerability.Fix.Versions, ", ") + switch m.Vulnerability.Fix.State { + case grypeDb.WontFixState: + fixVersion = wontFix + case grypeDb.UnknownFixState: + fixVersion = naValue + } + s.Table.addRow(newRow(m.Package.Name, m.Package.Version, fixVersion, string(m.Package.Type), m.Vulnerability.ID, severity)) + } + s.Table.dedup() + s.Tally = newTally(s.Table) + + return nil +} + +func colorize(rr []string) []string { + crr := make([]string, len(rr)) + copy(crr, rr) + + crr[len(crr)-1] = sevColor(crr[len(crr)-1]) + return crr +} diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go new file mode 100644 index 0000000000..003809dc5b --- /dev/null +++ b/internal/vul/scanner.go @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/rs/zerolog/log" + + "github.com/anchore/clio" + "github.com/anchore/grype/cmd/grype/cli/options" + "github.com/anchore/grype/grype" + "github.com/anchore/grype/grype/db" + "github.com/anchore/grype/grype/matcher" + "github.com/anchore/grype/grype/matcher/dotnet" + "github.com/anchore/grype/grype/matcher/golang" + "github.com/anchore/grype/grype/matcher/java" + "github.com/anchore/grype/grype/matcher/javascript" + "github.com/anchore/grype/grype/matcher/python" + "github.com/anchore/grype/grype/matcher/ruby" + "github.com/anchore/grype/grype/matcher/stock" + "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/store" + "github.com/anchore/grype/grype/vex" +) + +var ImgScanner *imageScanner + +type imageScanner struct { + store *store.Store + dbCloser *db.Closer + dbStatus *db.Status + opts *options.Grype + scans Scans + mx sync.RWMutex + initialized bool +} + +// NewImageScanner returns a new instance. +func NewImageScanner() *imageScanner { + return &imageScanner{ + scans: make(Scans), + } +} + +// GetScan fetch scan for a given image. Returns ok=false when not found. +func (s *imageScanner) GetScan(img string) (*Scan, bool) { + s.mx.RLock() + defer s.mx.RUnlock() + scan, ok := s.scans[img] + + return scan, ok +} + +func (s *imageScanner) SetScan(img string, sc *Scan) { + s.mx.Lock() + defer s.mx.Unlock() + s.scans[img] = sc +} + +// Init initializes image vulnerability database. +func (s *imageScanner) Init(name, version string) { + s.mx.Lock() + defer s.mx.Unlock() + + id := clio.Identification{Name: name, Version: version} + s.opts = options.DefaultGrype(id) + s.opts.GenerateMissingCPEs = true + + var err error + s.store, s.dbStatus, s.dbCloser, err = grype.LoadVulnerabilityDB( + s.opts.DB.ToCuratorConfig(), + s.opts.DB.AutoUpdate, + ) + if err != nil { + log.Error().Err(err).Msgf("VulDb load failed") + return + } + + if err := validateDBLoad(err, s.dbStatus); err != nil { + log.Error().Err(err).Msgf("VulDb validate failed") + return + } + + s.initialized = true +} + +// Stop closes scan database. +func (s *imageScanner) Stop() { + s.mx.RLock() + defer s.mx.RUnlock() + + if s.dbCloser != nil { + s.dbCloser.Close() + } +} + +func (s *imageScanner) Score(ii ...string) string { + var sc scorer + for _, i := range ii { + if scan, ok := s.GetScan(i); ok { + sc = sc.Add(newScorer(scan.Tally)) + } + } + + return sc.String() +} + +func (s *imageScanner) isInitialized() bool { + s.mx.RLock() + defer s.mx.RUnlock() + + return s.initialized +} + +func (s *imageScanner) Enqueue(images ...string) { + if !s.isInitialized() { + return + } + for _, i := range images { + go func(img string) { + if _, ok := s.GetScan(img); ok { + return + } + sc := newScan(img) + s.SetScan(img, sc) + if err := s.scan(img, sc); err != nil { + log.Warn().Err(err).Msgf("Scan failed for img %s --", img) + } + }(i) + } +} + +func (s *imageScanner) scan(img string, sc *Scan) error { + defer func(t time.Time) { + log.Debug().Msgf("Scan %s images: %v", img, time.Since(t)) + }(time.Now()) + + var errs error + packages, pkgContext, _, err := pkg.Provide(img, getProviderConfig(s.opts)) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to catalog %s: %w", img, err)) + } + + v := grype.VulnerabilityMatcher{ + Store: *s.store, + IgnoreRules: s.opts.Ignore, + NormalizeByCVE: s.opts.ByCVE, + FailSeverity: s.opts.FailOnServerity(), + Matchers: getMatchers(s.opts), + VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ + Documents: s.opts.VexDocuments, + IgnoreRules: s.opts.Ignore, + }), + } + + mm, _, err := v.FindMatches(packages, pkgContext) + if err != nil { + errs = errors.Join(errs, err) + } + if err := sc.run(mm, s.store); err != nil { + errs = errors.Join(errs, err) + } + + return errs +} + +func getProviderConfig(opts *options.Grype) pkg.ProviderConfig { + return pkg.ProviderConfig{ + SyftProviderConfig: pkg.SyftProviderConfig{ + RegistryOptions: opts.Registry.ToOptions(), + Exclusions: opts.Exclusions, + CatalogingOptions: opts.Search.ToConfig(), + Platform: opts.Platform, + Name: opts.Name, + DefaultImagePullSource: opts.DefaultImagePullSource, + }, + SynthesisConfig: pkg.SynthesisConfig{ + GenerateMissingCPEs: opts.GenerateMissingCPEs, + }, + } +} + +func getMatchers(opts *options.Grype) []matcher.Matcher { + return matcher.NewDefaultMatchers( + matcher.Config{ + Java: java.MatcherConfig{ + ExternalSearchConfig: opts.ExternalSources.ToJavaMatcherConfig(), + UseCPEs: opts.Match.Java.UseCPEs, + }, + Ruby: ruby.MatcherConfig(opts.Match.Ruby), + Python: python.MatcherConfig(opts.Match.Python), + Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet), + Javascript: javascript.MatcherConfig(opts.Match.Javascript), + Golang: golang.MatcherConfig{ + UseCPEs: opts.Match.Golang.UseCPEs, + AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib, + }, + Stock: stock.MatcherConfig(opts.Match.Stock), + }, + ) +} + +func validateDBLoad(loadErr error, status *db.Status) error { + if loadErr != nil { + return fmt.Errorf("failed to load vulnerability db: %w", loadErr) + } + if status == nil { + return fmt.Errorf("unable to determine the status of the vulnerability db") + } + if status.Err != nil { + return fmt.Errorf("db could not be loaded: %w", status.Err) + } + + return nil +} diff --git a/internal/vul/scorer.go b/internal/vul/scorer.go new file mode 100644 index 0000000000..30e949a8d9 --- /dev/null +++ b/internal/vul/scorer.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import "fmt" + +type scorer uint8 + +func (b scorer) String() string { + return fmt.Sprintf("%08b", b)[:6] +} + +func newScorer(t tally) scorer { + return fromTally(t) +} + +func (b scorer) Add(b1 scorer) scorer { + return b | b1 +} + +func fromTally(t tally) scorer { + var b scorer + for i, v := range t { + if v == 0 { + continue + } + switch i { + case sevCritical: + b |= 0x80 + case sevHigh: + b |= 0x40 + case sevMedium: + b |= 0x20 + case sevLow: + b |= 0x10 + case sevNegligible: + b |= 0x08 + case sevUnknown: + b |= 0x04 + } + } + + return b +} diff --git a/internal/vul/scorer_test.go b/internal/vul/scorer_test.go new file mode 100644 index 0000000000..ac6304cc7c --- /dev/null +++ b/internal/vul/scorer_test.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_scorerAdd(t *testing.T) { + uu := map[string]struct { + b, b1, e scorer + }{ + "zero": {}, + "same": { + b: scorer(0x80), + b1: scorer(0x80), + e: scorer(0x80), + }, + "c+h": { + b: scorer(0x80), + b1: scorer(0x40), + e: scorer(0xC0), + }, + "ch+hm": { + b: scorer(0xc0), + b1: scorer(0xa0), + e: scorer(0xe0), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.b.Add(u.b1)) + }) + } +} + +func Test_scorerFromTally(t *testing.T) { + uu := map[string]struct { + tt tally + b scorer + }{ + "zero": {}, + "critical": { + tt: tally{29, 0, 0, 0, 0, 0, 0}, + b: scorer(0x80), + }, + "high": { + tt: tally{0, 17, 0, 0, 0, 0, 0}, + b: scorer(0x40), + }, + "medium": { + tt: tally{0, 0, 5, 0, 0, 0, 0}, + b: scorer(0x20), + }, + "low": { + tt: tally{0, 0, 0, 10, 0, 0, 0}, + b: scorer(0x10), + }, + "negligible": { + tt: tally{0, 0, 0, 0, 10, 0, 0}, + b: scorer(0x08), + }, + "unknown": { + tt: tally{0, 0, 0, 0, 0, 10, 0}, + b: scorer(0x04), + }, + "c/h": { + tt: tally{10, 20, 0, 0, 0, 0, 0}, + b: scorer(0xC0), + }, + "c/m": { + tt: tally{10, 0, 20, 0, 0, 0, 0}, + b: scorer(0xA0), + }, + "c/h/l": { + tt: tally{10, 1, 20, 0, 0, 0, 0}, + b: scorer(0xE0), + }, + "n/u": { + tt: tally{0, 0, 0, 0, 10, 20, 0}, + b: scorer(0x0C), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.b, newScorer(u.tt)) + }) + } +} diff --git a/internal/vul/table.go b/internal/vul/table.go new file mode 100644 index 0000000000..6b3dbf4e5e --- /dev/null +++ b/internal/vul/table.go @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/olekukonko/tablewriter" +) + +const ( + nameIdx = iota + verIdx + fixIdx + typeIdx + vulIdx + sevIdx +) + +type Row []string + +func newRow(ss ...string) Row { + r := make(Row, 0, len(ss)) + for i, s := range ss { + if i == sevIdx { + s = toSev(s) + } + r = append(r, s) + } + return r +} + +func toSev(s string) string { + switch s { + case "Critical": + return Sev1 + case "High": + return Sev2 + case "Medium": + return Sev3 + case "Low": + return Sev4 + case "Negligible": + return Sev5 + default: + return SevU + } +} + +func (r Row) Name() string { return r[nameIdx] } +func (r Row) Version() string { return r[verIdx] } +func (r Row) Fix() string { return r[fixIdx] } +func (r Row) Type() string { return r[typeIdx] } +func (r Row) Vulnerability() string { return r[vulIdx] } +func (r Row) Severity() string { return r[sevIdx] } + +func sevColor(s string) string { + switch strings.ToLower(s) { + case "critical": + return fmt.Sprintf("[red::b]%s[-::-]", s) + case "high": + return fmt.Sprintf("[orange::b]%s[-::-]", s) + case "medium": + return fmt.Sprintf("[yellow::b]%s[-::-]", s) + case "low": + return fmt.Sprintf("[blue::b]%s[-::-]", s) + default: + return fmt.Sprintf("[gray::b]%s[-::-]", s) + } +} + +type table struct { + Rows []Row +} + +func newTable() *table { + return &table{} +} + +func (t *table) dedup() { + var ( + seen = make(map[string]struct{}, len(t.Rows)) + rr = make([]Row, 0, len(t.Rows)) + ) + for _, v := range t.Rows { + key := strings.Join(v, "|") + if _, ok := seen[key]; ok { + continue + } + rr, seen[key] = append(rr, v), struct{}{} + } + t.Rows = rr +} + +func (t *table) addRow(r Row) { + t.Rows = append(t.Rows, r) +} + +func (t *table) dump(w io.Writer) { + columns := []string{"Name", "Installed", "Fixed-In", "Type", "Vulnerability", "Severity"} + + table := tablewriter.NewWriter(w) + table.SetHeader(columns) + table.SetAutoWrapText(false) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetAutoFormatHeaders(true) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetTablePadding(" ") + table.SetNoWhiteSpace(true) + + for _, row := range t.Rows { + table.Append(colorize(row)) + } + table.Render() +} + +func (t *table) sort() { + t.dedup() + + sort.SliceStable(t.Rows, func(i, j int) bool { + if t.Rows[i][nameIdx] != t.Rows[j][nameIdx] { + return t.Rows[i][nameIdx] < t.Rows[j][nameIdx] + } + if t.Rows[i][verIdx] != t.Rows[j][verIdx] { + return t.Rows[i][verIdx] < t.Rows[j][verIdx] + } + if t.Rows[i][typeIdx] != t.Rows[j][typeIdx] { + return t.Rows[i][typeIdx] < t.Rows[j][typeIdx] + } + + if t.Rows[i][sevIdx] == t.Rows[j][sevIdx] { + return t.Rows[i][vulIdx] < t.Rows[j][vulIdx] + } + return sevToScore(t.Rows[i][sevIdx]) < sevToScore(t.Rows[j][sevIdx]) + }) +} + +func (t *table) sortSev() { + t.dedup() + + sort.SliceStable(t.Rows, func(i, j int) bool { + if s1, s2 := sevToScore(t.Rows[i][sevIdx]), sevToScore(t.Rows[j][sevIdx]); s1 != s2 { + return s1 < s2 + } + if t.Rows[i][nameIdx] != t.Rows[j][nameIdx] { + return t.Rows[i][nameIdx] < t.Rows[j][nameIdx] + } + if t.Rows[i][verIdx] != t.Rows[j][verIdx] { + return t.Rows[i][verIdx] < t.Rows[j][verIdx] + } + if t.Rows[i][typeIdx] != t.Rows[j][typeIdx] { + return t.Rows[i][typeIdx] < t.Rows[j][typeIdx] + } + + return t.Rows[i][vulIdx] < t.Rows[j][vulIdx] + }) +} + +func sevToScore(s string) int { + switch s { + case Sev1: + return 1 + case Sev2: + return 2 + case Sev3: + return 3 + case Sev4: + return 4 + case Sev5: + return 5 + default: + return 6 + } +} diff --git a/internal/vul/table_test.go b/internal/vul/table_test.go new file mode 100644 index 0000000000..b8e5b2f8e6 --- /dev/null +++ b/internal/vul/table_test.go @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "bufio" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_sort(t *testing.T) { + uu := map[string]struct { + t1, t2 *table + }{ + "simple": { + t1: makeTable(t, "testdata/sort/no_dups/sc1.text"), + t2: makeTable(t, "testdata/sort/no_dups/sc2.text"), + }, + "dups": { + t1: makeTable(t, "testdata/sort/dups/sc1.text"), + t2: makeTable(t, "testdata/sort/dups/sc2.text"), + }, + "full": { + t1: makeTable(t, "testdata/sort/full/sc1.text"), + t2: makeTable(t, "testdata/sort/full/sc2.text"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.t1.sort() + assert.Equal(t, u.t2, u.t1) + }) + } +} + +func Test_sortSev(t *testing.T) { + uu := map[string]struct { + t1, t2 *table + }{ + "simple": { + t1: makeTable(t, "testdata/sort_sev/no_dups/sc1.text"), + t2: makeTable(t, "testdata/sort_sev/no_dups/sc2.text"), + }, + "dups": { + t1: makeTable(t, "testdata/sort_sev/dups/sc1.text"), + t2: makeTable(t, "testdata/sort_sev/dups/sc2.text"), + }, + "full": { + t1: makeTable(t, "testdata/sort_sev/full/sc1.text"), + t2: makeTable(t, "testdata/sort_sev/full/sc2.text"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.t1.sortSev() + assert.Equal(t, u.t2, u.t1) + }) + } +} + +// Helpers... + +func makeTable(t *testing.T, path string) *table { + f, err := os.Open(path) + defer func() { + _ = f.Close() + }() + assert.NoError(t, err) + sc := bufio.NewScanner(f) + var tt table + for sc.Scan() { + ff := strings.Fields(sc.Text()) + tt.addRow(newRow(ff...)) + } + assert.NoError(t, sc.Err()) + + return &tt +} diff --git a/internal/vul/tally.go b/internal/vul/tally.go new file mode 100644 index 0000000000..a7ca25e8ed --- /dev/null +++ b/internal/vul/tally.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "fmt" + "io" +) + +const ( + sevCritical = iota + sevHigh + sevMedium + sevLow + sevNegligible + sevUnknown + sevFixed +) + +var vulWeights = []int{10_000, 100, 100, 10, 0, 0, 0, 0} + +type tally [7]int + +func newTally(t *table) tally { + var tt tally + for _, r := range t.Rows { + if r.Fix() != "" { + tt[sevFixed]++ + } + switch r.Severity() { + case Sev1: + tt[sevCritical]++ + case Sev2: + tt[sevHigh]++ + case Sev3: + tt[sevMedium]++ + case Sev4: + tt[sevLow]++ + case Sev5: + tt[sevNegligible]++ + case SevU: + tt[sevUnknown]++ + } + } + + return tt +} + +// Dump dumps tally as text. +func (t tally) Dump(w io.Writer) { + fmt.Fprintf(w, "%d critical, %d high, %d medium, %d low, %d negligible", + t[sevCritical], + t[sevHigh], + t[sevMedium], + t[sevLow], + t[sevNegligible], + ) + if t[sevUnknown] > 0 { + fmt.Fprintf(w, " (%d unknown)", t[sevUnknown]) + } + if t[sevFixed] > 0 { + fmt.Fprintf(w, " -- [Fixed: %d]", t[sevFixed]) + } +} + +func (t *tally) score() int { + var s int + for i, v := range t[:5] { + s += v * vulWeights[i] + } + + return s +} diff --git a/internal/vul/tally_test.go b/internal/vul/tally_test.go new file mode 100644 index 0000000000..3179ca0b51 --- /dev/null +++ b/internal/vul/tally_test.go @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_newTally(t *testing.T) { + uu := map[string]struct { + t *table + tt tally + }{ + "full": { + t: makeTable(t, "testdata/sort/full/sc2.text"), + tt: tally{7, 14, 8, 0, 0, 0, 29}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.tt, newTally(u.t)) + }) + } +} + +func Test_score(t *testing.T) { + uu := map[string]struct { + tt tally + sc int + }{ + "zero": {}, + "critical": { + tt: tally{29, 7, 14, 8, 0, 0, 0}, + sc: 292180, + }, + "high": { + tt: tally{0, 17, 14, 8, 0, 0, 0}, + sc: 3180, + }, + "medium": { + tt: tally{0, 0, 14, 0, 0, 0, 0}, + sc: 1400, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.sc, u.tt.score()) + }) + } +} diff --git a/internal/vul/testdata/sort/dups/sc1.text b/internal/vul/testdata/sort/dups/sc1.text new file mode 100644 index 0000000000..600a7a17e0 --- /dev/null +++ b/internal/vul/testdata/sort/dups/sc1.text @@ -0,0 +1,9 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort/dups/sc2.text b/internal/vul/testdata/sort/dups/sc2.text new file mode 100644 index 0000000000..38df5858cf --- /dev/null +++ b/internal/vul/testdata/sort/dups/sc2.text @@ -0,0 +1,6 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort/full/sc1.text b/internal/vul/testdata/sort/full/sc1.text new file mode 100644 index 0000000000..75ef53c497 --- /dev/null +++ b/internal/vul/testdata/sort/full/sc1.text @@ -0,0 +1,56 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort/full/sc2.text b/internal/vul/testdata/sort/full/sc2.text new file mode 100644 index 0000000000..74ffd85abd --- /dev/null +++ b/internal/vul/testdata/sort/full/sc2.text @@ -0,0 +1,29 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort/no_dups/sc1.text b/internal/vul/testdata/sort/no_dups/sc1.text new file mode 100644 index 0000000000..6190c71713 --- /dev/null +++ b/internal/vul/testdata/sort/no_dups/sc1.text @@ -0,0 +1,2 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High \ No newline at end of file diff --git a/internal/vul/testdata/sort/no_dups/sc2.text b/internal/vul/testdata/sort/no_dups/sc2.text new file mode 100644 index 0000000000..f9f06da82c --- /dev/null +++ b/internal/vul/testdata/sort/no_dups/sc2.text @@ -0,0 +1,2 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/dups/sc1.text b/internal/vul/testdata/sort_sev/dups/sc1.text new file mode 100644 index 0000000000..600a7a17e0 --- /dev/null +++ b/internal/vul/testdata/sort_sev/dups/sc1.text @@ -0,0 +1,9 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/dups/sc2.text b/internal/vul/testdata/sort_sev/dups/sc2.text new file mode 100644 index 0000000000..38df5858cf --- /dev/null +++ b/internal/vul/testdata/sort_sev/dups/sc2.text @@ -0,0 +1,6 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/full/sc1.text b/internal/vul/testdata/sort_sev/full/sc1.text new file mode 100644 index 0000000000..75ef53c497 --- /dev/null +++ b/internal/vul/testdata/sort_sev/full/sc1.text @@ -0,0 +1,56 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/full/sc2.text b/internal/vul/testdata/sort_sev/full/sc2.text new file mode 100644 index 0000000000..4e513f4cdc --- /dev/null +++ b/internal/vul/testdata/sort_sev/full/sc2.text @@ -0,0 +1,29 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24538 Critical +stdlib go1.19.4 n/a go-module CVE-2023-24540 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29402 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29404 Critical +stdlib go1.19.4 n/a go-module CVE-2023-29405 Critical +stdlib go1.19.4 n/a go-module CVE-2023-39323 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-4374-p667-p6c8 High +golang.org/x/net v0.4.0 0.7.0 go-module GHSA-vvpx-j8f3-3w6h High +stdlib go1.19.4 n/a go-module CVE-2022-41722 High +stdlib go1.19.4 n/a go-module CVE-2022-41723 High +stdlib go1.19.4 n/a go-module CVE-2022-41724 High +stdlib go1.19.4 n/a go-module CVE-2022-41725 High +stdlib go1.19.4 n/a go-module CVE-2023-24534 High +stdlib go1.19.4 n/a go-module CVE-2023-24536 High +stdlib go1.19.4 n/a go-module CVE-2023-24537 High +stdlib go1.19.4 n/a go-module CVE-2023-24539 High +stdlib go1.19.4 n/a go-module CVE-2023-29400 High +stdlib go1.19.4 n/a go-module CVE-2023-29403 High +stdlib go1.19.4 n/a go-module CVE-2023-44487 High +github.com/prometheus/alertmanager v0.25.0 0.25.1 go-module GHSA-v86x-5fm3-5p7j Medium +golang.org/x/net v0.4.0 0.13.0 go-module GHSA-2wrh-6pvc-2jm9 Medium +golang.org/x/net v0.4.0 0.17.0 go-module GHSA-qppj-fm5r-hxr3 Medium +stdlib go1.19.4 n/a go-module CVE-2023-24532 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29406 Medium +stdlib go1.19.4 n/a go-module CVE-2023-29409 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39318 Medium +stdlib go1.19.4 n/a go-module CVE-2023-39319 Medium \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/no_dups/sc1.text b/internal/vul/testdata/sort_sev/no_dups/sc1.text new file mode 100644 index 0000000000..6190c71713 --- /dev/null +++ b/internal/vul/testdata/sort_sev/no_dups/sc1.text @@ -0,0 +1,2 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High \ No newline at end of file diff --git a/internal/vul/testdata/sort_sev/no_dups/sc2.text b/internal/vul/testdata/sort_sev/no_dups/sc2.text new file mode 100644 index 0000000000..f9f06da82c --- /dev/null +++ b/internal/vul/testdata/sort_sev/no_dups/sc2.text @@ -0,0 +1,2 @@ +busybox 1.34.1 n/a binary CVE-2022-48174 Critical +busybox 1.34.1 n/a binary CVE-2022-28391 High \ No newline at end of file diff --git a/internal/vul/types.go b/internal/vul/types.go new file mode 100644 index 0000000000..339714d46c --- /dev/null +++ b/internal/vul/types.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package vul + +const ( + // Sev1 tracks Critical sev. + Sev1 = "SEV-1" + + // Sev2 tracks High sev. + Sev2 = "SEV-2" + + // Sev3 tracks Medium sev. + Sev3 = "SEV-3" + + // Sev4 tracks Low sev. + Sev4 = "SEV-4" + + // Sev5 tracks Negligible sev. + Sev5 = "SEV-5" + + // SevU tracks Unknown sev. + SevU = "SEV-U" +) diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 70fbe5fb7f..fcd67300fe 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -70,7 +70,7 @@ func (f *Factory) Terminate() { // List returns a resource collection. func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) { - inf, err := f.CanForResource(ns, gvr, client.MonitorAccess) + inf, err := f.CanForResource(ns, gvr, client.ListAccess) if err != nil { return nil, err } @@ -97,7 +97,7 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run // HasSynced checks if given informer is up to date. func (f *Factory) HasSynced(gvr, ns string) (bool, error) { - inf, err := f.CanForResource(ns, gvr, client.MonitorAccess) + inf, err := f.CanForResource(ns, gvr, client.ListAccess) if err != nil { return false, err } diff --git a/internal/xray/container.go b/internal/xray/container.go index 1f87336ccf..50a608607c 100644 --- a/internal/xray/container.go +++ b/internal/xray/container.go @@ -23,7 +23,7 @@ type Container struct{} func (c *Container) Render(ctx context.Context, ns string, o interface{}) error { co, ok := o.(render.ContainerRes) if !ok { - return fmt.Errorf("Expected ContainerRes, but got %T", o) + return fmt.Errorf("expected ContainerRes, but got %T", o) } f, ok := ctx.Value(internal.KeyFactory).(dao.Factory) diff --git a/internal/xray/dp.go b/internal/xray/dp.go index bfc47ef077..a32bbafd6f 100644 --- a/internal/xray/dp.go +++ b/internal/xray/dp.go @@ -25,7 +25,7 @@ type Deployment struct{} func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Unstructured, but got %T", o) + return fmt.Errorf("expected Unstructured, but got %T", o) } var dp appsv1.Deployment err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &dp) diff --git a/internal/xray/ds.go b/internal/xray/ds.go index fe55dc7158..d2ca86d65b 100644 --- a/internal/xray/ds.go +++ b/internal/xray/ds.go @@ -21,7 +21,7 @@ type DaemonSet struct{} func (d *DaemonSet) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Unstructured, but got %T", o) + return fmt.Errorf("expected Unstructured, but got %T", o) } var ds appsv1.DaemonSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &ds) diff --git a/internal/xray/ns.go b/internal/xray/ns.go index 647ada3d35..a39f141af0 100644 --- a/internal/xray/ns.go +++ b/internal/xray/ns.go @@ -20,7 +20,7 @@ type Namespace struct{} func (n *Namespace) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected NamespaceWithMetrics, but got %T", o) + return fmt.Errorf("expected NamespaceWithMetrics, but got %T", o) } var nss v1.Namespace diff --git a/internal/xray/pod.go b/internal/xray/pod.go index ddac6d9193..69bb293f12 100644 --- a/internal/xray/pod.go +++ b/internal/xray/pod.go @@ -24,7 +24,7 @@ type Pod struct{} func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error { pwm, ok := o.(*render.PodWithMetrics) if !ok { - return fmt.Errorf("Expected PodWithMetrics, but got %T", o) + return fmt.Errorf("expected PodWithMetrics, but got %T", o) } var po v1.Pod diff --git a/internal/xray/rs.go b/internal/xray/rs.go index 9dfda4c653..80c64f5b2d 100644 --- a/internal/xray/rs.go +++ b/internal/xray/rs.go @@ -21,7 +21,7 @@ type ReplicaSet struct{} func (r *ReplicaSet) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Unstructured, but got %T", o) + return fmt.Errorf("expected Unstructured, but got %T", o) } var rs appsv1.ReplicaSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rs) diff --git a/internal/xray/section.go b/internal/xray/section.go index c458b5fdb7..7e171004ae 100644 --- a/internal/xray/section.go +++ b/internal/xray/section.go @@ -21,7 +21,7 @@ type Section struct { func (s *Section) Render(ctx context.Context, ns string, o interface{}) error { section, ok := o.(render.Section) if !ok { - return fmt.Errorf("Expected Section, but got %T", o) + return fmt.Errorf("expected Section, but got %T", o) } root := NewTreeNode(section.GVR, section.Title) parent, ok := ctx.Value(KeyParent).(*TreeNode) diff --git a/internal/xray/sts.go b/internal/xray/sts.go index 0917c1965e..819a2b5a21 100644 --- a/internal/xray/sts.go +++ b/internal/xray/sts.go @@ -21,7 +21,7 @@ type StatefulSet struct{} func (s *StatefulSet) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Unstructured, but got %T", o) + return fmt.Errorf("expected Unstructured, but got %T", o) } var sts appsv1.StatefulSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sts) diff --git a/internal/xray/svc.go b/internal/xray/svc.go index af535c7c1d..9c0df61746 100644 --- a/internal/xray/svc.go +++ b/internal/xray/svc.go @@ -25,7 +25,7 @@ type Service struct{} func (s *Service) Render(ctx context.Context, ns string, o interface{}) error { raw, ok := o.(*unstructured.Unstructured) if !ok { - return fmt.Errorf("Expected Unstructured, but got %T", o) + return fmt.Errorf("expected Unstructured, but got %T", o) } var svc v1.Service diff --git a/main.go b/main.go index 13ce9520c7..05ffae543a 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - package main import ( From a13412a6d985abf2a925ecbdca1fc9c71c47415a Mon Sep 17 00:00:00 2001 From: derailed Date: Thu, 7 Dec 2023 09:58:11 -0700 Subject: [PATCH 020/169] [HotFix] Fix issues #2330 #2329 #2327 --- README.md | 2 ++ change_logs/release_v0.29.1.md | 34 ++++++++++++++++++++++++++++++++++ internal/ui/config.go | 33 ++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 change_logs/release_v0.29.1.md diff --git a/README.md b/README.md index ae3a89c0ee..aa8c825c40 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,8 @@ K9s uses aliases to navigate most K8s resources. noIcons: false # Toggles whether k9s should check for the latest revision from the Github repository releases. Default is false. skipLatestRevCheck: false + # When altering kubeconfig or using multiple kube configs, k9s will clean up clusters configurations that are no longer in use. Setting this flag to true will keep k9s from cleaning up inactive cluster configs. Defaults to false. + keepMissingClusters: false # Logs configuration logger: # Defines the number of lines to return. Default 100 diff --git a/change_logs/release_v0.29.1.md b/change_logs/release_v0.29.1.md new file mode 100644 index 0000000000..2c58c8f944 --- /dev/null +++ b/change_logs/release_v0.29.1.md @@ -0,0 +1,34 @@ + + +# Release v0.29.1 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +## Maintenance Release + +--- + +## Resolved Issues + +* [#2330](https://github.com/derailed/k9s/issues/2330) Skins don't work v0.29.0 +* [#2329](https://github.com/derailed/k9s/issues/2329) New skin system in v0.29.0 doesn't work if you use different k8s context files +* [#2327](https://github.com/derailed/k9s/issues/2327) [Bug] Item highlighting broke in v0.29.0 + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/ui/config.go b/internal/ui/config.go index 3d55da8f4c..b00196fff7 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -6,6 +6,7 @@ package ui import ( "context" "errors" + "fmt" "os" "path/filepath" @@ -130,6 +131,29 @@ func BenchConfig(context string) string { return filepath.Join(config.K9sHome(), config.K9sBench+"-"+context+".yml") } +func (c *Configurator) clusterFromContext(name string) (*config.Cluster, error) { + if c.Config == nil || c.Config.GetConnection() == nil { + return nil, fmt.Errorf("No config set in configurator") + } + + cc, err := c.Config.GetConnection().Config().Contexts() + if err != nil { + return nil, errors.New("unable to retrieve contexts map") + } + + context, ok := cc[name] + if !ok { + return nil, fmt.Errorf("no context named %s found", name) + } + + cl, ok := c.Config.K9s.Clusters[context.Cluster] + if !ok { + return nil, fmt.Errorf("no cluster named %s found", context.Cluster) + } + + return cl, nil +} + // RefreshStyles load for skin configuration changes. func (c *Configurator) RefreshStyles(context string) { c.BenchFile = BenchConfig(context) @@ -141,11 +165,10 @@ func (c *Configurator) RefreshStyles(context string) { } var skin string - if c.Config != nil { - cl, ok := c.Config.K9s.Clusters[context] - if !ok { - return - } + cl, err := c.clusterFromContext(context) + if err != nil { + log.Warn().Err(err).Msgf("No cluster found. Using default skin") + } else { skin = cl.Skin } From 362358aaa006d8de25a58dfd8308e13d5ebf14c0 Mon Sep 17 00:00:00 2001 From: derailed Date: Thu, 7 Dec 2023 10:54:02 -0700 Subject: [PATCH 021/169] update container revs --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6018cbedde..a5e2fd6955 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # The base image for building the k9s binary -FROM golang:1.20.4-alpine3.16 AS build +FROM golang:1.21.5-alpine3.17 AS build WORKDIR /k9s COPY go.mod go.sum main.go Makefile ./ @@ -13,7 +13,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # Build the final Docker image FROM alpine:3.18.4 -ARG KUBECTL_VERSION="v1.25.2" +ARG KUBECTL_VERSION="v1.27.3" COPY --from=build /k9s/execs/k9s /bin/k9s RUN apk add --update ca-certificates \ From e94f991ad4e6e690633076348790960555de13ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:08:51 -0700 Subject: [PATCH 022/169] Bump alpine from 3.18.4 to 3.18.5 (#2325) Bumps alpine from 3.18.4 to 3.18.5. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a5e2fd6955..d6379ad4e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # ----------------------------------------------------------------------------- # Build the final Docker image -FROM alpine:3.18.4 +FROM alpine:3.18.5 ARG KUBECTL_VERSION="v1.27.3" COPY --from=build /k9s/execs/k9s /bin/k9s From d0ec55737ae418cf45d11d11ca81d94bade06a6c Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Fri, 8 Dec 2023 23:10:40 +0800 Subject: [PATCH 023/169] fix fuzzy matching not working properly (#2321) * fix fuzzy matching not working properly * delete continuousRanges --- internal/dao/helpers.go | 14 +++++ internal/dao/helpers_test.go | 32 ++++++++++ internal/model/describe.go | 18 +----- internal/model/helpers.go | 26 +++++++- .../{yaml_test.go => helpers_int_test.go} | 16 +++-- internal/model/text.go | 18 +----- internal/model/tree.go | 4 +- internal/model/values.go | 18 +----- internal/model/yaml.go | 18 +----- internal/view/details.go | 13 +--- internal/view/helpers.go | 21 +++++++ internal/view/helpers_test.go | 59 +++++++++++++++++++ internal/view/live_view.go | 23 +------- internal/view/live_view_test.go | 57 ------------------ 14 files changed, 169 insertions(+), 168 deletions(-) rename internal/model/{yaml_test.go => helpers_int_test.go} (80%) diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index 049324a507..d837d6ead9 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -96,3 +96,17 @@ func serviceAccountMatches(podSA, saName string) bool { } return podSA == saName } + +// ContinuousRanges takes a sorted slice of integers and returns a slice of +// sub-slices representing continuous ranges of integers. +func ContinuousRanges(indexes []int) [][]int { + var ranges [][]int + for i, p := 1, 0; i <= len(indexes); i++ { + if i == len(indexes) || indexes[i]-indexes[p] != i-p { + ranges = append(ranges, []int{indexes[p], indexes[i-1] + 1}) + p = i + } + } + + return ranges +} diff --git a/internal/dao/helpers_test.go b/internal/dao/helpers_test.go index 4379627e9b..f201654b51 100644 --- a/internal/dao/helpers_test.go +++ b/internal/dao/helpers_test.go @@ -60,3 +60,35 @@ func TestServiceAccountMatches(t *testing.T) { assert.Equal(t, u.expect, serviceAccountMatches(u.podTemplate.ServiceAccountName, u.saName)) } } + +func TestContinuousRanges(t *testing.T) { + tests := []struct { + Indexes []int + Ranges [][]int + }{ + { + Indexes: []int{0}, + Ranges: [][]int{{0, 1}}, + }, + { + Indexes: []int{1}, + Ranges: [][]int{{1, 2}}, + }, + { + Indexes: []int{0, 1, 2}, + Ranges: [][]int{{0, 3}}, + }, + { + Indexes: []int{4, 5, 6}, + Ranges: [][]int{{4, 7}}, + }, + { + Indexes: []int{0, 2, 4, 5, 6}, + Ranges: [][]int{{0, 1}, {2, 3}, {4, 7}}, + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.Ranges, ContinuousRanges(tt.Indexes)) + } +} diff --git a/internal/model/describe.go b/internal/model/describe.go index c86c4001bb..82ca20b41c 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "reflect" - "regexp" "strings" "sync/atomic" "time" @@ -69,28 +68,13 @@ func (d *Describe) filter(q string, lines []string) fuzzy.Matches { if dao.IsFuzzySelector(q) { return d.fuzzyFilter(strings.TrimSpace(q[2:]), lines) } - return d.rxFilter(q, lines) + return rxFilter(q, lines) } func (*Describe) fuzzyFilter(q string, lines []string) fuzzy.Matches { return fuzzy.Find(q, lines) } -func (*Describe) rxFilter(q string, lines []string) fuzzy.Matches { - rx, err := regexp.Compile(`(?i)` + q) - if err != nil { - return nil - } - matches := make(fuzzy.Matches, 0, len(lines)) - for i, l := range lines { - if loc := rx.FindStringIndex(l); len(loc) == 2 { - matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc}) - } - } - - return matches -} - func (d *Describe) fireResourceChanged(lines []string, matches fuzzy.Matches) { for _, l := range d.listeners { l.ResourceChanged(lines, matches) diff --git a/internal/model/helpers.go b/internal/model/helpers.go index 507de8697b..0646b1ce0f 100644 --- a/internal/model/helpers.go +++ b/internal/model/helpers.go @@ -5,11 +5,13 @@ package model import ( "context" + "regexp" "time" "github.com/cenkalti/backoff/v4" "github.com/derailed/tview" - runewidth "github.com/mattn/go-runewidth" + "github.com/mattn/go-runewidth" + "github.com/sahilm/fuzzy" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -37,3 +39,25 @@ func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOf bf.InitialInterval, bf.MaxElapsedTime = start, max return backoff.WithContext(bf, ctx) } + +func rxFilter(q string, lines []string) fuzzy.Matches { + rx, err := regexp.Compile(`(?i)` + q) + if err != nil { + return nil + } + + matches := make(fuzzy.Matches, 0, len(lines)) + for i, l := range lines { + locs := rx.FindAllStringIndex(l, -1) + for _, loc := range locs { + indexes := make([]int, 0, loc[1]-loc[0]) + for v := loc[0]; v < loc[1]; v++ { + indexes = append(indexes, v) + } + + matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: indexes}) + } + } + + return matches +} diff --git a/internal/model/yaml_test.go b/internal/model/helpers_int_test.go similarity index 80% rename from internal/model/yaml_test.go rename to internal/model/helpers_int_test.go index 35612cefc7..3a21a4d2b4 100644 --- a/internal/model/yaml_test.go +++ b/internal/model/helpers_int_test.go @@ -4,13 +4,12 @@ package model import ( - "testing" - "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" + "testing" ) -func TestYAML_rxFilter(t *testing.T) { +func Test_rxFilter(t *testing.T) { uu := map[string]struct { q string lines []string @@ -32,7 +31,7 @@ func TestYAML_rxFilter(t *testing.T) { { Str: "foo", Index: 0, - MatchedIndexes: []int{0, 3}, + MatchedIndexes: []int{0, 1, 2}, }, }, }, @@ -43,26 +42,25 @@ func TestYAML_rxFilter(t *testing.T) { { Str: "foo", Index: 0, - MatchedIndexes: []int{0, 3}, + MatchedIndexes: []int{0, 1, 2}, }, { Str: "foo", Index: 2, - MatchedIndexes: []int{0, 3}, + MatchedIndexes: []int{0, 1, 2}, }, { Str: "foo", Index: 2, - MatchedIndexes: []int{8, 11}, + MatchedIndexes: []int{8, 9, 10}, }, }, }, } - var y YAML for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, y.rxFilter(u.q, u.lines)) + assert.Equal(t, u.e, rxFilter(u.q, u.lines)) }) } } diff --git a/internal/model/text.go b/internal/model/text.go index 0cc98cfdd0..d5c5fe893b 100644 --- a/internal/model/text.go +++ b/internal/model/text.go @@ -4,7 +4,6 @@ package model import ( - "regexp" "strings" "github.com/derailed/k9s/internal/dao" @@ -115,24 +114,9 @@ func (t *Text) filter(q string, lines []string) fuzzy.Matches { if dao.IsFuzzySelector(q) { return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines) } - return t.rxFilter(q, lines) + return rxFilter(q, lines) } func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches { return fuzzy.Find(q, lines) } - -func (*Text) rxFilter(q string, lines []string) fuzzy.Matches { - rx, err := regexp.Compile(`(?i)` + q) - if err != nil { - return nil - } - matches := make(fuzzy.Matches, 0, len(lines)) - for i, l := range lines { - if loc := rx.FindStringIndex(l); len(loc) == 2 { - matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc}) - } - } - - return matches -} diff --git a/internal/model/tree.go b/internal/model/tree.go index 4e21954c7c..f1c60f686c 100644 --- a/internal/model/tree.go +++ b/internal/model/tree.go @@ -226,7 +226,7 @@ func (t *Tree) reconcile(ctx context.Context) error { root.Sort() if t.query != "" { - t.root = root.Filter(t.query, rxFilter) + t.root = root.Filter(t.query, rxMatch) } if t.root == nil || t.root.Diff(root) { t.root = root @@ -277,7 +277,7 @@ func (t *Tree) getMeta(ctx context.Context, gvr string) (ResourceMeta, error) { // ---------------------------------------------------------------------------- // Helpers... -func rxFilter(q, path string) bool { +func rxMatch(q, path string) bool { rx := regexp.MustCompile(`(?i)` + q) tokens := strings.Split(path, "::") diff --git a/internal/model/values.go b/internal/model/values.go index 7dce96c02d..8d7135d7b9 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -5,7 +5,6 @@ package model import ( "context" - "regexp" "strings" "sync/atomic" "time" @@ -93,28 +92,13 @@ func (v *Values) filter(q string, lines []string) fuzzy.Matches { if dao.IsFuzzySelector(q) { return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines) } - return v.rxFilter(q, lines) + return rxFilter(q, lines) } func (*Values) fuzzyFilter(q string, lines []string) fuzzy.Matches { return fuzzy.Find(q, lines) } -func (*Values) rxFilter(q string, lines []string) fuzzy.Matches { - rx, err := regexp.Compile(`(?i)` + q) - if err != nil { - return nil - } - matches := make(fuzzy.Matches, 0, len(lines)) - for i, l := range lines { - if loc := rx.FindStringIndex(l); len(loc) == 2 { - matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc}) - } - } - - return matches -} - func (v *Values) fireResourceChanged(lines []string, matches fuzzy.Matches) { for _, l := range v.listeners { l.ResourceChanged(lines, matches) diff --git a/internal/model/yaml.go b/internal/model/yaml.go index 332af49af0..8ef8d64d1b 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "reflect" - "regexp" "strings" "sync/atomic" "time" @@ -78,28 +77,13 @@ func (y *YAML) filter(q string, lines []string) fuzzy.Matches { if dao.IsFuzzySelector(q) { return y.fuzzyFilter(strings.TrimSpace(q[2:]), lines) } - return y.rxFilter(q, lines) + return rxFilter(q, lines) } func (*YAML) fuzzyFilter(q string, lines []string) fuzzy.Matches { return fuzzy.Find(q, lines) } -func (*YAML) rxFilter(q string, lines []string) fuzzy.Matches { - rx, err := regexp.Compile(`(?i)` + q) - if err != nil { - return nil - } - matches := make(fuzzy.Matches, 0, len(lines)) - for i, l := range lines { - locs := rx.FindAllStringIndex(l, -1) - for _, loc := range locs { - matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc}) - } - } - return matches -} - func (y *YAML) fireResourceChanged(lines []string, matches fuzzy.Matches) { for _, l := range y.listeners { l.ResourceChanged(lines, matches) diff --git a/internal/view/details.go b/internal/view/details.go index cc366d33cd..86ca3a700c 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -102,19 +102,12 @@ func (d *Details) TextChanged(lines []string) { // TextFiltered notifies when the filter changed. func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) { - d.currentRegion, d.maxRegions = 0, 0 - - ll := make([]string, len(lines)) - copy(ll, lines) - for _, m := range matches { - loc, line := m.MatchedIndexes, ll[m.Index] - ll[m.Index] = line[:loc[0]] + fmt.Sprintf(`<<<"search_%d">>>`, d.maxRegions) + line[loc[0]:loc[1]] + `<<<"">>>` + line[loc[1]:] - d.maxRegions++ - } + d.currentRegion, d.maxRegions = 0, len(matches) + ll := linesWithRegions(lines, matches) d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n"))) d.text.Highlight() - if d.maxRegions > 0 { + if len(matches) > 0 { d.text.Highlight("search_0") d.text.ScrollToHighlight() } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index ef1cc73544..6805e40519 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -14,12 +14,14 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" + "github.com/sahilm/fuzzy" ) func clipboardWrite(text string) error { @@ -240,3 +242,22 @@ func decorateCpuMemHeaderRows(app *App, data *render.TableData) { } } } + +func matchTag(i int, s string) string { + return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>` +} + +func linesWithRegions(lines []string, matches fuzzy.Matches) []string { + ll := make([]string, len(lines)) + copy(ll, lines) + offsetForLine := make(map[int]int) + for i, m := range matches { + for _, loc := range dao.ContinuousRanges(m.MatchedIndexes) { + start, end := loc[0]+offsetForLine[m.Index], loc[1]+offsetForLine[m.Index] + regionStr := matchTag(i, ll[m.Index][start:end]) + ll[m.Index] = ll[m.Index][:start] + regionStr + ll[m.Index][end:] + offsetForLine[m.Index] += len(regionStr) - (end - start) + } + } + return ll +} diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 70699ae15c..d1d144f054 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -14,6 +14,7 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/rs/zerolog" + "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -266,3 +267,61 @@ func TestContainerID(t *testing.T) { }) } } + +func Test_linesWithRegions(t *testing.T) { + uu := map[string]struct { + lines []string + matches fuzzy.Matches + e []string + }{ + "empty-lines": { + e: []string{}, + }, + "no-match": { + lines: []string{"bar"}, + e: []string{"bar"}, + }, + "single-match": { + lines: []string{"foo", "bar", "baz"}, + matches: fuzzy.Matches{ + {Index: 1, MatchedIndexes: []int{0, 1, 2}}, + }, + e: []string{"foo", matchTag(0, "bar"), "baz"}, + }, + "single-character": { + lines: []string{"foo", "bar", "baz"}, + matches: fuzzy.Matches{ + {Index: 1, MatchedIndexes: []int{1}}, + }, + e: []string{"foo", "b" + matchTag(0, "a") + "r", "baz"}, + }, + "multiple-matches": { + lines: []string{"foo", "bar", "baz"}, + matches: fuzzy.Matches{ + {Index: 1, MatchedIndexes: []int{0, 1, 2}}, + {Index: 2, MatchedIndexes: []int{0, 1, 2}}, + }, + e: []string{"foo", matchTag(0, "bar"), matchTag(1, "baz")}, + }, + "multiple-matches-same-line": { + lines: []string{"foosfoo baz", "dfbarfoos bar"}, + matches: fuzzy.Matches{ + {Index: 0, MatchedIndexes: []int{0, 1, 2}}, + {Index: 0, MatchedIndexes: []int{4, 5, 6}}, + {Index: 1, MatchedIndexes: []int{5, 6, 7}}, + }, + e: []string{ + matchTag(0, "foo") + "s" + matchTag(1, "foo") + " baz", + "dfbar" + matchTag(2, "foo") + "s bar", + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + t.Parallel() + assert.Equal(t, u.e, linesWithRegions(u.lines, u.matches)) + }) + } +} diff --git a/internal/view/live_view.go b/internal/view/live_view.go index 46f5a82734..245a1ec419 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -100,36 +100,17 @@ func (v *LiveView) ResourceFailed(err error) { v.text.SetText(cowTalk(err.Error(), x+w)) } -func (*LiveView) linesWithRegions(lines []string, matches fuzzy.Matches) []string { - ll := make([]string, len(lines)) - copy(ll, lines) - offsetForLine := make(map[int]int) - for i, m := range matches { - loc, line := m.MatchedIndexes, ll[m.Index] - if len(loc) < 2 { - continue - } - offset := offsetForLine[m.Index] - - loc[0], loc[1] = loc[0]+offset, loc[1]+offset - regionStr := `<<<"search_` + strconv.Itoa(i) + `">>>` + line[loc[0]:loc[1]] + `<<<"">>>` - ll[m.Index] = line[:loc[0]] + regionStr + line[loc[1]:] - offsetForLine[m.Index] += len(regionStr) - (loc[1] - loc[0]) - } - return ll -} - // ResourceChanged notifies when the filter changes. func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) { v.app.QueueUpdateDraw(func() { v.text.SetTextAlign(tview.AlignLeft) - v.maxRegions = len(matches) + v.currentRegion, v.maxRegions = 0, len(matches) if v.text.GetText(true) == "" { v.text.ScrollToBeginning() } - lines = v.linesWithRegions(lines, matches) + lines = linesWithRegions(lines, matches) v.text.SetText(colorizeYAML(v.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) v.text.Highlight() if v.currentRegion < v.maxRegions { diff --git a/internal/view/live_view_test.go b/internal/view/live_view_test.go index 92509a8912..257e8114bb 100644 --- a/internal/view/live_view_test.go +++ b/internal/view/live_view_test.go @@ -5,18 +5,12 @@ package view import ( "context" - "strconv" "testing" "github.com/derailed/k9s/internal/config" - "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" ) -func matchTag(i int, s string) string { - return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>` -} - func TestLiveViewSetText(t *testing.T) { s := ` apiVersion: v1 @@ -30,54 +24,3 @@ apiVersion: v1 assert.Equal(t, s, sanitizeEsc(v.text.GetText(true))) } - -func TestLiveView_linesWithRegions(t *testing.T) { - uu := map[string]struct { - lines []string - matches fuzzy.Matches - e []string - }{ - "empty-lines": { - e: []string{}, - }, - "no-match": { - lines: []string{"bar"}, - e: []string{"bar"}, - }, - "single-match": { - lines: []string{"foo", "bar", "baz"}, - matches: fuzzy.Matches{ - {Index: 1, MatchedIndexes: []int{0, 3}}, - }, - e: []string{"foo", matchTag(0, "bar"), "baz"}, - }, - "multiple-matches": { - lines: []string{"foo", "bar", "baz"}, - matches: fuzzy.Matches{ - {Index: 1, MatchedIndexes: []int{0, 3}}, - {Index: 2, MatchedIndexes: []int{0, 3}}, - }, - e: []string{"foo", matchTag(0, "bar"), matchTag(1, "baz")}, - }, - "multiple-matches-same-line": { - lines: []string{"foosfoo baz", "dfbarfoos bar"}, - matches: fuzzy.Matches{ - {Index: 0, MatchedIndexes: []int{0, 3}}, - {Index: 0, MatchedIndexes: []int{4, 7}}, - {Index: 1, MatchedIndexes: []int{5, 8}}, - }, - e: []string{ - matchTag(0, "foo") + "s" + matchTag(1, "foo") + " baz", - "dfbar" + matchTag(2, "foo") + "s bar", - }, - }, - } - var v LiveView - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - t.Parallel() - assert.Equal(t, u.e, v.linesWithRegions(u.lines, u.matches)) - }) - } -} From f6dfc3721a246e27ffa9ce968f6d1364a47cf2b4 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sat, 9 Dec 2023 11:07:28 +0800 Subject: [PATCH 024/169] adding a default value for the AGE field (#2320) * adding a default value for the AGE field * use NAValue as a fallback value --- internal/render/generic.go | 4 ++++ internal/render/helpers.go | 4 ++++ internal/render/helpers_test.go | 2 ++ 3 files changed, 10 insertions(+) diff --git a/internal/render/generic.go b/internal/render/generic.go index b8c02a9f9c..0297f940f4 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/rs/zerolog/log" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" ) @@ -98,6 +99,9 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error { } if d, ok := duration.(string); ok { r.Fields = append(r.Fields, d) + } else if g.ageIndex > 0 { + log.Warn().Msgf("No Duration detected on age field") + r.Fields = append(r.Fields, NAValue) } return nil diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 2ccd503173..e6945c33a0 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -4,6 +4,7 @@ package render import ( + "math" "sort" "strconv" "strings" @@ -48,6 +49,9 @@ func durationToSeconds(duration string) int64 { if len(duration) == 0 { return 0 } + if duration == NAValue { + return math.MaxInt64 + } num := make([]rune, 0, 5) var n, m int64 diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 84c19a49cb..26eb18ca17 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -4,6 +4,7 @@ package render import ( + "math" "testing" "time" @@ -69,6 +70,7 @@ func TestDurationToSecond(t *testing.T) { "day_hour_minute_seconds": {s: "2d22h3m50s", e: 252230}, "year": {s: "3y", e: 94608000}, "year_day": {s: "1y2d", e: 31708800}, + "n/a": {s: NAValue, e: math.MaxInt64}, } for k := range uu { From 104bf9641574d7d910a13d362b25b8ff9fafe59b Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Thu, 14 Dec 2023 06:24:07 +0800 Subject: [PATCH 025/169] passing on the correct suggestion parameters (#2343) --- internal/ui/prompt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index 113f8cd531..bf468c555c 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -166,12 +166,12 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey { case tcell.KeyUp: if s, ok := m.NextSuggestion(); ok { - p.model.SetText(s, "") + p.model.SetText(p.model.GetText(), s) } case tcell.KeyDown: if s, ok := m.PrevSuggestion(); ok { - p.model.SetText(s, "") + p.model.SetText(p.model.GetText(), s) } case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF: From 414f7d84dd29e890528fc4b2d905126158b51392 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:34:01 -0700 Subject: [PATCH 026/169] Bump actions/setup-go from 4.1.0 to 5.0.0 (#2339) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.1.0 to 5.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4.1.0...v5.0.0) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ccfa529cf6..bebd4c743a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@4.1.1 - name: Install Go - uses: actions/setup-go@v4.1.0 + uses: actions/setup-go@v5.0.0 with: go-version-file: go.mod cache-dependency-path: go.sum diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 823afddffc..4ed30d22ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Install Go - uses: actions/setup-go@v4.1.0 + uses: actions/setup-go@v5.0.0 with: go-version-file: go.mod cache-dependency-path: go.sum From 5b73665f79cfd70d94a80216393dfe453dadf818 Mon Sep 17 00:00:00 2001 From: Tobias Germer Date: Sat, 16 Dec 2023 01:34:44 +0100 Subject: [PATCH 027/169] add pkgx to installation section (#2340) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index aa8c825c40..bf160babc5 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,12 @@ K9s is available on Linux, macOS and Windows platforms. curl -sS https://webinstall.dev/k9s | bash ``` +* Via [pkgx](https://pkgx.dev/pkgs/k9scli.io/) for Linux and macOS + + ```shell + pkgx k9s + ``` + * Via [Webi](https://webinstall.dev) for Windows ```shell From a52bbde9567c0f07ebefa869f2b4e19a2a03bff0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:40:43 -0700 Subject: [PATCH 028/169] Bump alpine from 3.18.5 to 3.19.0 (#2337) Bumps alpine from 3.18.5 to 3.19.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d6379ad4e1..81dfab66a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # ----------------------------------------------------------------------------- # Build the final Docker image -FROM alpine:3.18.5 +FROM alpine:3.19.0 ARG KUBECTL_VERSION="v1.27.3" COPY --from=build /k9s/execs/k9s /bin/k9s From 906138181e1388abce1180fb439276def49dd041 Mon Sep 17 00:00:00 2001 From: tyzbit <3319104+tyzbit@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:31:38 -0500 Subject: [PATCH 029/169] fix(misc plugins): split up multiline commands, use less -K everywhere (#2348) `less -K` makes ctrl+c work with `less`. When running commands in k9s that use less, sometimes the command doesn't ever return or the user doesn't wait for it to finish. Issuing ctrl+c to a command with a simple `| less` puts k9s into an unfixable state, requiring restarting because `less` never quits. With `less -K`, ctrl+c quits `less` and the user should always be returned to a working `k9s` session. I also used multiline strings in a few places which improve readability and remove the need for double quotes in many places as many commands had gotten long. --- plugins/carvel.yml | 9 +-- plugins/crossplane.yml | 10 ++- plugins/flux.yml | 134 ++++++++++++++++++++++++++------ plugins/get-all.yml | 12 +-- plugins/get_suspended.yml | 23 ------ plugins/helm-default-values.yml | 16 +++- plugins/helm_values.yml | 6 +- 7 files changed, 148 insertions(+), 62 deletions(-) delete mode 100644 plugins/get_suspended.yml diff --git a/plugins/carvel.yml b/plugins/carvel.yml index 02c6478ee9..eea45ca714 100644 --- a/plugins/carvel.yml +++ b/plugins/carvel.yml @@ -10,7 +10,7 @@ plugin: background: false args: - -c - - "export FORCE_COLOR=1;kapp inspect -a $NAME.app --namespace $NAMESPACE --kubeconfig-context $CONTEXT --color --tty |& less -R" + - "export FORCE_COLOR=1;kapp inspect -a $NAME.app --namespace $NAMESPACE --kubeconfig-context $CONTEXT --color --tty | less -RK" kctrl-app-status: shortCut: Shift-Q confirm: false @@ -21,7 +21,7 @@ plugin: background: false args: - -c - - "export FORCE_COLOR=1;kctrl app status -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --color --tty |& less -R" + - "export FORCE_COLOR=1;kctrl app status -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --color --tty | less -RK" kctrl-app-pause: shortCut: Shift-T confirm: false @@ -32,7 +32,7 @@ plugin: background: false args: - -c - - "export FORCE_COLOR=1;kctrl app pause -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --yes --color --tty |& less -R" + - "export FORCE_COLOR=1;kctrl app pause -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --yes --color --tty | less -RK" kctrl-app-kick: shortCut: Shift-Z confirm: false @@ -43,5 +43,4 @@ plugin: background: false args: - -c - - "export FORCE_COLOR=1;kctrl app kick -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --yes --color --tty |& less -R" - + - "export FORCE_COLOR=1;kctrl app kick -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --yes --color --tty | less -RK" diff --git a/plugins/crossplane.yml b/plugins/crossplane.yml index 8b16340dce..fb7015e123 100644 --- a/plugins/crossplane.yml +++ b/plugins/crossplane.yml @@ -10,4 +10,12 @@ plugin: background: false args: - -c - - "kubectl lineage -d 6 --exclude-types Event,ProviderConfigUsage.aws.upbound.io,ProviderConfigUsage.kubernetes.crossplane.io --show-group --context $CONTEXT $RESOURCE_NAME $NAME | less" \ No newline at end of file + - >- + kubectl lineage + -d 6 + --exclude-types Event,ProviderConfigUsage.aws.upbound.io,ProviderConfigUsage.kubernetes.crossplane.io + --show-group + --context $CONTEXT + $RESOURCE_NAME + $NAME + | less -K diff --git a/plugins/flux.yml b/plugins/flux.yml index dd3b98404d..ca5c68e665 100644 --- a/plugins/flux.yml +++ b/plugins/flux.yml @@ -13,7 +13,14 @@ plugin: background: false args: - -c - - "flux --context $CONTEXT $([ $(kubectl --context $CONTEXT get helmreleases -n $NAMESPACE $NAME -o=custom-columns=TYPE:.spec.suspend | tail -1) = \"true\" ] && echo \"resume\" || echo \"suspend\") helmrelease -n $NAMESPACE $NAME |& less" + - >- + suspended=$(kubectl --context $CONTEXT get helmreleases -n $NAMESPACE $NAME -o=custom-columns=TYPE:.spec.suspend | tail -1); + verb=$([ $suspended = "true" ] && echo "resume" || echo "suspend"); + flux + $verb helmrelease + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K toggle-kustomization: shortCut: Shift-T confirm: true @@ -24,29 +31,46 @@ plugin: background: false args: - -c - - "flux --context $CONTEXT $([ $(kubectl --context $CONTEXT get kustomizations -n $NAMESPACE $NAME -o=custom-columns=TYPE:.spec.suspend | tail -1) = \"true\" ] && echo \"resume\" || echo \"suspend\") kustomization -n $NAMESPACE $NAME |& less" + - >- + suspended=$(kubectl --context $CONTEXT get kustomizations -n $NAMESPACE $NAME -o=custom-columns=TYPE:.spec.suspend | tail -1); + verb=$([ $suspended = "true" ] && echo "resume" || echo "suspend"); + flux + $verb kustomization + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-git: shortCut: Shift-R confirm: false description: Flux reconcile scopes: - - gitrepositories + - gitrepositories command: bash background: false args: - - -c - - "flux --context $CONTEXT reconcile source git -n $NAMESPACE $NAME |& less" + - -c + - >- + flux + reconcile source git + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-hr: shortCut: Shift-R confirm: false description: Flux reconcile scopes: - - helmreleases + - helmreleases command: bash background: false args: - - -c - - "flux --context $CONTEXT reconcile helmrelease -n $NAMESPACE $NAME |& less" + - -c + - >- + flux + reconcile helmrelease + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-helm-repo: shortCut: Shift-Z description: Flux reconcile @@ -57,7 +81,12 @@ plugin: confirm: false args: - -c - - "flux reconcile source helm --context $CONTEXT -n $NAMESPACE $NAME |& less" + - >- + flux + reconcile source helm + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-oci-repo: shortCut: Shift-Z description: Flux reconcile @@ -68,48 +97,109 @@ plugin: confirm: false args: - -c - - "flux reconcile source oci --context $CONTEXT -n $NAMESPACE $NAME |& less" + - >- + flux + reconcile source oci + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-ks: shortCut: Shift-R confirm: false description: Flux reconcile scopes: - - kustomizations + - kustomizations command: bash background: false args: - - -c - - "flux --context $CONTEXT reconcile kustomization -n $NAMESPACE $NAME |& less" + - -c + - >- + flux + reconcile kustomization + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-ir: shortCut: Shift-R confirm: false description: Flux reconcile scopes: - - imagerepositories + - imagerepositories command: sh background: false args: - - -c - - "flux --context $CONTEXT reconcile image repository -n $NAMESPACE $NAME | less" + - -c + - >- + flux + reconcile image repository + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K reconcile-iua: shortCut: Shift-R confirm: false description: Flux reconcile scopes: - - imageupdateautomations + - imageupdateautomations command: sh background: false args: - - -c - - "flux --context $CONTEXT reconcile image update -n $NAMESPACE $NAME | less" + - -c + - >- + flux + reconcile image update + --context $CONTEXT + -n $NAMESPACE $NAME + | less -K trace: shortCut: Shift-A confirm: false description: Flux trace scopes: - - all + - all command: bash background: false args: - - -c - - "flux --context $CONTEXT trace --kind `echo $RESOURCE_NAME | sed -E 's/ies$/y/' | sed -E 's/ses$/se/' | sed -E 's/(s|es)$//g'` --api-version $RESOURCE_GROUP/$RESOURCE_VERSION --namespace $NAMESPACE $NAME |& less" + - -c + - >- + resource=$(echo $RESOURCE_NAME | sed -E 's/ies$/y/' | sed -E 's/ses$/se/' | sed -E 's/(s|es)$//g') + flux + trace + --context $CONTEXT + --kind $resource + --api-version $RESOURCE_GROUP/$RESOURCE_VERSION + --namespace $NAMESPACE $NAME + | less -K + # credits: https://github.com/fluxcd/flux2/discussions/2494 + get-suspended-helmreleases: + shortCut: Shift-S + confirm: false + description: Suspended Helm Releases + scopes: + - helmrelease + command: sh + background: false + args: + - -c + - >- + kubectl get + --all-namespaces + helmreleases.helm.toolkit.fluxcd.io -o json + | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.namespace,.metadata.name,.spec.suspend] | @tsv' + | less + get-suspended-kustomizations: + shortCut: Shift-S + confirm: false + description: Suspended Kustomizations + scopes: + - kustomizations + command: sh + background: false + args: + - -c + - >- + kubectl get + --all-namespaces + kustomizations.kustomize.toolkit.fluxcd.io -o json + | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.name,.spec.suspend] | @tsv' + | less diff --git a/plugins/get-all.yml b/plugins/get-all.yml index 1e1d871945..d872dd0de2 100644 --- a/plugins/get-all.yml +++ b/plugins/get-all.yml @@ -5,20 +5,20 @@ plugin: confirm: false description: get-all scopes: - - namespaces + - namespaces command: sh background: false args: - - -c - - "kubectl get-all --context $CONTEXT -n $NAME | less" + - -c + - "kubectl get-all --context $CONTEXT -n $NAME | less -K" get-all-other: shortCut: g confirm: false description: get-all scopes: - - all + - all command: sh background: false args: - - -c - - "kubectl get-all --context $CONTEXT -n $NAMESPACE | less" + - -c + - "kubectl get-all --context $CONTEXT -n $NAMESPACE | less -K" diff --git a/plugins/get_suspended.yml b/plugins/get_suspended.yml deleted file mode 100644 index a5270a27b9..0000000000 --- a/plugins/get_suspended.yml +++ /dev/null @@ -1,23 +0,0 @@ -# credits: https://github.com/fluxcd/flux2/discussions/2494 - get-suspended-helmreleases: - shortCut: Shift-S - confirm: false - description: Suspended Helm Releases - scopes: - - helmrelease - command: sh - background: false - args: - - -c - - "kubectl get --all-namespaces helmreleases.helm.toolkit.fluxcd.io -o json | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.namespace,.metadata.name,.spec.suspend] | @tsv' | less" - get-suspended-kustomizations: - shortCut: Shift-S - confirm: false - description: Suspended Kustomizations - scopes: - - kustomizations - command: sh - background: false - args: - - -c - - "kubectl get --all-namespaces kustomizations.kustomize.toolkit.fluxcd.io -o json | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.name,.spec.suspend] | @tsv' | less" diff --git a/plugins/helm-default-values.yml b/plugins/helm-default-values.yml index b79705d598..ce587347b7 100644 --- a/plugins/helm-default-values.yml +++ b/plugins/helm-default-values.yml @@ -9,5 +9,17 @@ plugin: background: false args: - -c - - "kubectl get secrets --context $CONTEXT -n $NAMESPACE sh.helm.release.v1.$COL-NAME.v$(helm history -n $NAMESPACE --kube-context $CONTEXT $COL-NAME | grep deployed | cut -d$'\\t' -f1 | tr -d ' \\t') -o yaml | yq e '.data.release' - | base64 -d | base64 -d | gunzip | jq -r '.chart.values' | yq -P | less" - + - >- + revision=$(helm history -n $NAMESPACE --kube-context $CONTEXT $COL-NAME | grep deployed | cut -d$'\t' -f1 | tr -d ' \t'); + kubectl + get secrets + --context $CONTEXT + -n $NAMESPACE + sh.helm.release.v1.$COL-NAME.v$revision -o yaml + | yq e '.data.release' - + | base64 -d + | base64 -d + | gunzip + | jq -r '.chart.values' + | yq -P + | less -K diff --git a/plugins/helm_values.yml b/plugins/helm_values.yml index 36d8fdb2eb..8efc0d0fba 100644 --- a/plugins/helm_values.yml +++ b/plugins/helm_values.yml @@ -6,9 +6,9 @@ plugin: confirm: false description: Values scopes: - - helm + - helm command: sh background: false args: - - -c - - "helm get values $COL-NAME -n $NAMESPACE --kube-context $CONTEXT | less" + - -c + - "helm get values $COL-NAME -n $NAMESPACE --kube-context $CONTEXT | less -K" From ca72490350a6576cdae7824b5e0bd5782401a11c Mon Sep 17 00:00:00 2001 From: Emanuele Ciurleo Date: Mon, 18 Dec 2023 12:31:50 -0300 Subject: [PATCH 030/169] #1873 add symlink into snap (#2350) K9s snap is missing a symlink when installing on Ubuntu 22.04.3 LTS This creates the link alongside the rest of the snap. --- snap/snapcraft.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 682f054b40..ea3ce095b4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -3,7 +3,7 @@ base: core20 version: 'v0.27.4' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | - K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of you clusters in a single powerful session. + K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. grade: stable confinement: classic @@ -38,5 +38,11 @@ parts: make test make build install $SNAPCRAFT_PART_BUILD/execs/k9s -D $SNAPCRAFT_PART_INSTALL/bin/k9s + override-prime: | + mkdir -p $SNAPCRAFT_PART_INSTALL/bin + ln -s $SNAPCRAFT_PART_INSTALL/bin/k9s $SNAPCRAFT_PART_INSTALL/bin/k9s build-packages: - build-essential + +prime: + - $build/bin/k9s From 7bd7ec766ab2436fb2ffe3a220ae8493f525556e Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 20 Dec 2023 06:21:12 +0800 Subject: [PATCH 031/169] adding value, yaml and describe views to helm-history (#2341) * correct describe of helm history * value, yaml and describe view for helm-history * add value_extender and clean up code * move showValues to the new extender --- internal/dao/helm_chart.go | 1 + internal/dao/helm_history.go | 66 ++++++++++++++++++++------- internal/dao/registry.go | 3 +- internal/dao/types.go | 6 +++ internal/model/values.go | 42 ++++++++++++++---- internal/view/helm_chart.go | 41 +---------------- internal/view/helm_history.go | 6 +-- internal/view/value_extender.go | 79 +++++++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 68 deletions(-) create mode 100644 internal/view/value_extender.go diff --git a/internal/dao/helm_chart.go b/internal/dao/helm_chart.go index 8aa26dcafe..0f1e8fdbcf 100644 --- a/internal/dao/helm_chart.go +++ b/internal/dao/helm_chart.go @@ -21,6 +21,7 @@ var ( _ Accessor = (*HelmChart)(nil) _ Nuker = (*HelmChart)(nil) _ Describer = (*HelmChart)(nil) + _ Valuer = (*HelmChart)(nil) ) // HelmChart represents a helm chart. diff --git a/internal/dao/helm_history.go b/internal/dao/helm_history.go index 38fda22ff9..d589298cd6 100644 --- a/internal/dao/helm_history.go +++ b/internal/dao/helm_history.go @@ -7,19 +7,23 @@ import ( "context" "fmt" "strconv" + "strings" - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/render/helm" + "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render/helm" ) var ( _ Accessor = (*HelmHistory)(nil) _ Nuker = (*HelmHistory)(nil) _ Describer = (*HelmHistory)(nil) + _ Valuer = (*HelmHistory)(nil) ) // HelmHistory represents a helm chart. @@ -55,12 +59,24 @@ func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, err // Get returns a resource. func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error) { - ns, n := client.Namespaced(path) + fqn, rev, found := strings.Cut(path, ":") + if !found || len(rev) == 0 { + return nil, fmt.Errorf("invalid path %q", path) + } + + ns, n := client.Namespaced(fqn) cfg, err := ensureHelmConfig(h.Client(), ns) if err != nil { return nil, err } - resp, err := action.NewGet(cfg).Run(n) + + getter := action.NewGet(cfg) + getter.Version, err = strconv.Atoi(rev) + if err != nil { + return nil, err + } + + resp, err := getter.Run(n) if err != nil { return nil, err } @@ -70,32 +86,50 @@ func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error // Describe returns the chart notes. func (h *HelmHistory) Describe(path string) (string, error) { - ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + rel, err := h.Get(context.Background(), path) if err != nil { return "", err } - resp, err := action.NewGet(cfg).Run(n) - if err != nil { - return "", err + + resp, ok := rel.(helm.ReleaseRes) + if !ok { + return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel) } - return resp.Info.Notes, nil + return resp.Release.Info.Notes, nil } // ToYAML returns the chart manifest. func (h *HelmHistory) ToYAML(path string, showManaged bool) (string, error) { - ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + rel, err := h.Get(context.Background(), path) if err != nil { return "", err } - resp, err := action.NewGet(cfg).Run(n) + + resp, ok := rel.(helm.ReleaseRes) + if !ok { + return "", fmt.Errorf("expected helm.ReleaseRes, but got %T", rel) + } + + return resp.Release.Manifest, nil +} + +// GetValues return the config for this chart. +func (h *HelmHistory) GetValues(path string, allValues bool) ([]byte, error) { + rel, err := h.Get(context.Background(), path) if err != nil { - return "", err + return nil, err } - return resp.Manifest, nil + resp, ok := rel.(helm.ReleaseRes) + if !ok { + return nil, fmt.Errorf("expected helm.ReleaseRes, but got %T", rel) + } + + if allValues { + return yaml.Marshal(resp.Release.Chart.Values) + } + return yaml.Marshal(resp.Release.Config) } func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error { diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 54fdbce53e..aee73f22ab 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -102,6 +102,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { client.NewGVR("v1/namespaces"): &Namespace{}, client.NewGVR("popeye"): &Popeye{}, client.NewGVR("helm"): &HelmChart{}, + client.NewGVR("helm-history"): &HelmHistory{}, client.NewGVR("dir"): &Dir{}, } @@ -314,7 +315,7 @@ func loadHelm(m ResourceMetas) { Kind: "History", Namespaced: true, Verbs: []string{"delete"}, - Categories: []string{k9sCat}, + Categories: []string{helmCat}, } } diff --git a/internal/dao/types.go b/internal/dao/types.go index e24cb22bf3..d51d25e81c 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -170,3 +170,9 @@ type Sanitizer interface { // Sanitize nukes all resources in unhappy state. Sanitize(context.Context, string) (int, error) } + +// Valuer represents a resource with values. +type Valuer interface { + // GetValues returns values for a resource. + GetValues(path string, allValues bool) ([]byte, error) +} diff --git a/internal/model/values.go b/internal/model/values.go index 8d7135d7b9..890facd0b9 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -5,6 +5,7 @@ package model import ( "context" + "fmt" "strings" "sync/atomic" "time" @@ -18,6 +19,7 @@ import ( // Values tracks Helm values representations. type Values struct { + factory dao.Factory gvr client.GVR inUpdate int32 path string @@ -34,20 +36,36 @@ func NewValues(gvr client.GVR, path string) *Values { gvr: gvr, path: path, allValues: false, - lines: getValues(path, false), } } -func getHelmDao() *dao.HelmChart { - return Registry["helm"].DAO.(*dao.HelmChart) +// Init initializes the model. +func (v *Values) Init(f dao.Factory) error { + v.factory = f + + var err error + v.lines, err = v.getValues() + + return err } -func getValues(path string, allValues bool) []string { - vals, err := getHelmDao().GetValues(path, allValues) +func (v *Values) getValues() ([]string, error) { + accessor, err := dao.AccessorFor(v.factory, v.gvr) + if err != nil { + return nil, err + } + + valuer, ok := accessor.(dao.Valuer) + if !ok { + return nil, fmt.Errorf("Resource %s is not Valuer", v.gvr) + } + + values, err := valuer.GetValues(v.path, v.allValues) if err != nil { - log.Error().Err(err).Msgf("Failed to get Helm values") + return nil, err } - return strings.Split(string(vals), "\n") + + return strings.Split(string(values), "\n"), nil } // GVR returns the resource gvr. @@ -56,10 +74,16 @@ func (v *Values) GVR() client.GVR { } // ToggleValues toggles between user supplied values and computed values. -func (v *Values) ToggleValues() { +func (v *Values) ToggleValues() error { v.allValues = !v.allValues - lines := getValues(v.path, v.allValues) + + lines, err := v.getValues() + if err != nil { + return err + } + v.lines = lines + return nil } // GetPath returns the active resource path. diff --git a/internal/view/helm_chart.go b/internal/view/helm_chart.go index e6aba216e1..d4624e7a3d 100644 --- a/internal/view/helm_chart.go +++ b/internal/view/helm_chart.go @@ -8,23 +8,19 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // HelmChart represents a helm chart view. type HelmChart struct { ResourceViewer - - Values *model.Values } -// NewHelm returns a new alias view. +// NewHelmChart returns a new helm-chart view. func NewHelmChart(gvr client.GVR) ResourceViewer { c := HelmChart{ - ResourceViewer: NewBrowser(gvr), + ResourceViewer: NewValueExtender(NewBrowser(gvr)), } c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) c.GetTable().SetSelectedStyle(tcell.StyleDefault. @@ -46,7 +42,6 @@ func (c *HelmChart) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ ui.KeyR: ui.NewKeyAction("Releases", c.historyCmd, true), ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(statusCol, true), false), - ui.KeyV: ui.NewKeyAction("Values", c.getValsCmd(), true), }) } @@ -77,35 +72,3 @@ func (c *HelmChart) helmContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyPath, path) } - -func (c *HelmChart) getValsCmd() func(evt *tcell.EventKey) *tcell.EventKey { - return func(evt *tcell.EventKey) *tcell.EventKey { - path := c.GetTable().GetSelectedItem() - if path == "" { - return evt - } - c.Values = model.NewValues(c.GVR(), path) - v := NewLiveView(c.App(), "Values", c.Values) - v.actions.Add(ui.KeyActions{ - ui.KeyV: ui.NewKeyAction("Toggle All Values", c.toggleValuesCmd, true), - }) - if err := v.app.inject(v, false); err != nil { - v.app.Flash().Err(err) - } - return nil - } -} - -func (c *HelmChart) toggleValuesCmd(evt *tcell.EventKey) *tcell.EventKey { - c.Values.ToggleValues() - if err := c.Values.Refresh(c.defaultCtx()); err != nil { - log.Error().Err(err).Msgf("helm refresh failed") - return nil - } - c.App().Flash().Infof("Values toggled") - return nil -} - -func (c *HelmChart) defaultCtx() context.Context { - return context.WithValue(context.Background(), internal.KeyFactory, c.App().factory) -} diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go index 9a1b7eb86e..185acaeb4d 100644 --- a/internal/view/helm_history.go +++ b/internal/view/helm_history.go @@ -21,10 +21,10 @@ type History struct { ResourceViewer } -// NewHelm returns a new alias view. +// NewHistory returns a new helm-history view. func NewHistory(gvr client.GVR) ResourceViewer { h := History{ - ResourceViewer: NewBrowser(gvr), + ResourceViewer: NewValueExtender(NewBrowser(gvr)), } h.GetTable().SetColorerFn(helm.History{}.ColorerFunc()) h.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) @@ -35,7 +35,7 @@ func NewHistory(gvr client.GVR) ResourceViewer { return &h } -// Init initializes the vie +// Init initializes the view func (h *History) Init(ctx context.Context) error { if err := h.ResourceViewer.Init(ctx); err != nil { return err diff --git a/internal/view/value_extender.go b/internal/view/value_extender.go new file mode 100644 index 0000000000..a7cf7f8c61 --- /dev/null +++ b/internal/view/value_extender.go @@ -0,0 +1,79 @@ +package view + +import ( + "context" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tcell/v2" + "github.com/rs/zerolog/log" +) + +// ValueExtender adds values actions to a given viewer. +type ValueExtender struct { + ResourceViewer +} + +// NewValueExtender returns a new extender. +func NewValueExtender(r ResourceViewer) ResourceViewer { + p := ValueExtender{ResourceViewer: r} + p.AddBindKeysFn(p.bindKeys) + p.GetTable().SetEnterFn(func(app *App, model ui.Tabular, gvr, path string) { + p.valuesCmd(nil) + }) + + return &p +} + +func (v *ValueExtender) bindKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ + ui.KeyV: ui.NewKeyAction("Values", v.valuesCmd, true), + }) +} + +func (v *ValueExtender) valuesCmd(evt *tcell.EventKey) *tcell.EventKey { + path := v.GetTable().GetSelectedItem() + if path == "" { + return evt + } + + showValues(v.defaultCtx(), v.App(), path, v.GVR()) + return nil +} + +func (v *ValueExtender) defaultCtx() context.Context { + return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory) +} + +func showValues(ctx context.Context, app *App, path string, gvr client.GVR) { + vm := model.NewValues(gvr, path) + if err := vm.Init(app.factory); err != nil { + app.Flash().Errf("Initializing the values model failed: %s", err) + } + + toggleValuesCmd := func(evt *tcell.EventKey) *tcell.EventKey { + if err := vm.ToggleValues(); err != nil { + app.Flash().Errf("Values toggle failed: %s", err) + return nil + } + + if err := vm.Refresh(ctx); err != nil { + log.Error().Err(err).Msgf("values refresh failed") + return nil + } + + app.Flash().Infof("Values toggled") + return nil + } + + v := NewLiveView(app, "Values", vm) + v.actions.Add(ui.KeyActions{ + ui.KeyV: ui.NewKeyAction("Toggle All Values", toggleValuesCmd, true), + }) + + if err := v.app.inject(v, false); err != nil { + v.app.Flash().Err(err) + } +} From 553875155211333678d2ea4b3d79af82f8c165ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:21:42 -0700 Subject: [PATCH 032/169] Bump github.com/containerd/containerd from 1.7.8 to 1.7.11 (#2358) Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.7.8 to 1.7.11. - [Release notes](https://github.com/containerd/containerd/releases) - [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md) - [Commits](https://github.com/containerd/containerd/compare/v1.7.8...v1.7.11) --- updated-dependencies: - dependency-name: github.com/containerd/containerd dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 11 +++++++---- go.sum | 20 ++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 89eb331940..4a428c8dd2 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 // indirect @@ -87,7 +87,7 @@ require ( github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/cgroups v1.1.0 // indirect - github.com/containerd/containerd v1.7.8 // indirect + github.com/containerd/containerd v1.7.11 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -116,6 +116,7 @@ require ( github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/fgprof v0.9.3 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/github/go-spdx/v2 v2.2.0 // indirect @@ -280,8 +281,10 @@ require ( github.com/zclconf/go-cty v1.14.0 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.14.0 // indirect - go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.16.0 // indirect diff --git a/go.sum b/go.sum index 1156338289..9a067490c1 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= -github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= @@ -351,8 +351,8 @@ github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWH github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.7.8 h1:RkwgOW3AVUT3H/dyT0W03Dc8AzlpMG65lX48KftOFSM= -github.com/containerd/containerd v1.7.8/go.mod h1:L/Hn9qylJtUFT7cPeM0Sr3fATj+WjHwRQ0lyrYk3OPY= +github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= +github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= @@ -1191,10 +1191,14 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= -go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= -go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= -go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= From a6b702f9df2774af12077fc03d77184e3fc81f63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:22:27 -0700 Subject: [PATCH 033/169] Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#2355) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a428c8dd2..a1730ab672 100644 --- a/go.mod +++ b/go.mod @@ -287,7 +287,7 @@ require ( go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect diff --git a/go.sum b/go.sum index 9a067490c1..4428bbc745 100644 --- a/go.sum +++ b/go.sum @@ -1226,8 +1226,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 17ad294ac2604cdac9a38e69651880c6456b4034 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:23:40 -0700 Subject: [PATCH 034/169] Bump helm.sh/helm/v3 from 3.13.2 to 3.13.3 (#2353) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.13.2 to 3.13.3. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.13.2...v3.13.3) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a1730ab672..9573a36397 100644 --- a/go.mod +++ b/go.mod @@ -27,9 +27,9 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.13.2 + helm.sh/helm/v3 v3.13.3 k8s.io/api v0.28.4 - k8s.io/apiextensions-apiserver v0.28.3 + k8s.io/apiextensions-apiserver v0.28.4 k8s.io/apimachinery v0.28.4 k8s.io/cli-runtime v0.28.4 k8s.io/client-go v0.28.4 @@ -310,7 +310,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.28.3 // indirect + k8s.io/apiserver v0.28.4 // indirect k8s.io/component-base v0.28.4 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect diff --git a/go.sum b/go.sum index 4428bbc745..841d9d4792 100644 --- a/go.sum +++ b/go.sum @@ -1834,8 +1834,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= -helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= +helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= +helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1845,12 +1845,12 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= +k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w= -k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM= +k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg= +k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w= k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= From 7897fb0eef8a57d40dece2a71adae3078861c1ef Mon Sep 17 00:00:00 2001 From: Emanuele Ciurleo Date: Tue, 19 Dec 2023 19:47:10 -0300 Subject: [PATCH 035/169] Added ln check for snap (#2357) * Added ln check for snap Realised that if you manually created (or already had) a ln it would cause the build to fail. So added in a check to see if it exists. Moved commands out of prime-override as it didnt really add anything and made it harder to read * Version no. update to 0.29.1 The snap package was being built with 0.29.1 but is presenting as 0.27 still (locally and in ubuntu snap store) --- .gitignore | 3 ++- snap/snapcraft.yaml | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 0fc4383608..953aa0a45f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ faas .settings/* demos /code -kind \ No newline at end of file +kind +*.snap diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ea3ce095b4..f6da346138 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s -base: core20 -version: 'v0.27.4' +base: core20 +version: 'v0.29.1' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. @@ -38,11 +38,9 @@ parts: make test make build install $SNAPCRAFT_PART_BUILD/execs/k9s -D $SNAPCRAFT_PART_INSTALL/bin/k9s - override-prime: | mkdir -p $SNAPCRAFT_PART_INSTALL/bin - ln -s $SNAPCRAFT_PART_INSTALL/bin/k9s $SNAPCRAFT_PART_INSTALL/bin/k9s + if [ ! -e $SNAPCRAFT_PART_INSTALL/bin/k9s ]; then + ln -s $SNAPCRAFT_PART_INSTALL/bin/k9s $SNAPCRAFT_PART_INSTALL/bin/k9s + fi build-packages: - build-essential - -prime: - - $build/bin/k9s From dcec53e061ec3372a62e945b8afa44640fbbebb5 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sat, 23 Dec 2023 14:29:55 -0700 Subject: [PATCH 036/169] K9s/rel v0.30.0 (#2361) * [Maint] Refactor VS col handling * [Bug] Add helm hist values cmd * [Bug] Add context specific skins within a given cluster config. * [Maint] Image scan controls * [Bug] Fix fwd+bench timestamp * [Refact] all-ns const * [Maint] update tabledefs from metav1beta1 to metav1 * [Feat] Introduce workload view * [Maint] Add convenience to map out ns names - Refactor allnamespaces * [Cleanup] axe pegomock * [Refact] Use gvr type vs string * [Feat] Add blacklist scans exclusions * [Feat] setLabels for stored commands * [Maint] Rename api-group column * [Refact] gvr type refactor * [Maint] Cleaning up * [Bug] Add ability to skin based on context - Handles cluster spanning *contexts * [Maint] Cleaning up * [Feat] Cmd interpreter * [Maint] Clean up + bug fixes * [Feat] Changed k9s config loader > NOTE: !!Breaking change!! - Make k9s config readonly - Move writable artifacts to XDG data dir - Move transient artifacts to XDG state dir - Add per context skin option - Add per context readonly option - Consistent pluralization file names to yaml section * [Docs] Update release and README docs * [Maint] Rebase + cleanup * [Maint] Normalize config extensions all yml -> yaml * [Maint] Cleaning up + fixes --- .gitignore | 3 +- .goreleaser.yml | 2 + Dockerfile | 2 +- Makefile | 2 +- README.md | 420 +++++---- change_logs/release_v0.30.0.md | 313 +++++++ cmd/info.go | 44 +- cmd/info_test.go | 17 +- cmd/root.go | 49 +- cmd/testdata/{k9s.yml => k9s.yaml} | 0 cmd/testdata/{k9s1.yml => k9s1.yaml} | 0 go.mod | 34 +- go.sum | 76 +- internal/client/client.go | 47 +- internal/client/config.go | 125 +-- internal/client/config_test.go | 95 +- internal/client/gvr.go | 2 + internal/client/gvr_test.go | 2 +- internal/client/helpers.go | 6 +- internal/client/metrics.go | 14 +- internal/client/testdata/config.2 | 23 + internal/client/types.go | 16 +- internal/config/alias.go | 46 +- internal/config/alias_test.go | 6 +- internal/config/{bench.go => benchmark.go} | 24 +- .../{bench_test.go => benchmark_test.go} | 14 +- internal/config/cluster.go | 51 - internal/config/cluster_test.go | 61 -- internal/config/config.go | 239 +++-- internal/config/config_test.go | 290 ++---- internal/config/data/config.go | 48 + internal/config/data/context.go | 67 ++ internal/config/data/context_test.go | 32 + internal/config/data/dir.go | 74 ++ internal/config/data/dir_test.go | 104 +++ internal/config/data/feature_gate.go | 14 + internal/config/data/helpers.go | 35 + internal/config/data/helpers_test.go | 58 ++ internal/config/{ => data}/ns.go | 32 +- internal/config/data/ns_test.go | 67 ++ .../config/data/testdata/configs/ct-1-1.yaml | 16 + .../config/data/testdata/configs/ct-1-2.yaml | 14 + .../config/data/testdata/configs/ct-2-1.yaml | 15 + .../config/data/testdata/configs/def_ct.yaml | 12 + .../testdata/data/k9s/cl-1/ct-1-1/config.yaml | 16 + .../testdata/data/k9s/cl-1/ct-1-2/config.yaml | 14 + .../testdata/data/k9s/cl-2/ct-2-1/config.yaml | 15 + internal/config/data/types.go | 42 + internal/config/{ => data}/view.go | 8 +- internal/config/{ => data}/view_test.go | 8 +- internal/config/feature.go | 16 +- internal/config/files.go | 297 ++++++ internal/config/files_test.go | 51 + internal/config/flags.go | 12 +- internal/config/helpers.go | 41 +- internal/config/helpers_test.go | 47 - internal/config/hotkey.go | 8 +- internal/config/hotkey_test.go | 2 +- internal/config/k9s.go | 281 +++--- internal/config/k9s_test.go | 154 +--- internal/config/logger.go | 3 +- internal/config/mock/test_helpers.go | 161 ++++ internal/config/mock_connection_test.go | 674 -------------- internal/config/mock_kubesettings_test.go | 252 ----- internal/config/ns_test.go | 91 -- internal/config/plugin.go | 82 +- internal/config/plugin_test.go | 29 +- internal/config/scans.go | 71 ++ internal/config/shell_pod.go | 3 +- internal/config/styles.go | 11 +- internal/config/styles_test.go | 8 +- internal/config/templates/aliases.yaml | 9 + internal/config/templates/benchmarks.yaml | 4 + internal/config/templates/hotkeys.yaml | 6 + internal/config/templates/stock-skin.yaml | 97 ++ .../config/testdata/{alias.yml => alias.yaml} | 2 +- .../{b_containers.yml => b_containers.yaml} | 0 ...b_containers_1.yml => b_containers_1.yaml} | 0 .../testdata/{b_good.yml => b_good.yaml} | 0 .../testdata/{b_toast.yml => b_toast.yaml} | 0 .../{bench-fred.yml => bench-fred.yaml} | 0 .../{black_and_wtf.yml => black_and_wtf.yaml} | 0 .../{empty_skin.yml => empty_skin.yaml} | 0 .../testdata/{hot_key.yml => hotkeys.yaml} | 2 +- .../config/testdata/{k8s.yml => k8s.yaml} | 0 .../config/testdata/{k9s.yml => k9s.yaml} | 5 +- .../config/testdata/{k9s1.yml => k9s1.yaml} | 0 .../testdata/{k9s_old.yml => k9s_old.yaml} | 0 .../{k9s_readonly.yml => k9s_readonly.yaml} | 3 +- .../{k9s_toast.yml => k9s_toast.yaml} | 3 +- ...beconfig-test.yml => kubeconfig-test.yaml} | 16 +- .../testdata/{plugin.yml => plugins.yaml} | 2 +- .../plugins/{test1.yml => test1.yaml} | 0 .../plugins/{test2.yml => test2.yaml} | 0 .../{skin_boarked.yml => skin_boarked.yaml} | 0 .../{view_settings.yml => view_settings.yaml} | 0 internal/config/threshold.go | 3 +- internal/config/types.go | 31 + internal/config/views.go | 8 +- internal/config/views_test.go | 2 +- internal/dao/alias.go | 42 +- internal/dao/alias_test.go | 54 +- internal/dao/benchmark.go | 11 +- internal/dao/benchmark_test.go | 1 + internal/dao/cluster.go | 4 +- internal/dao/container_test.go | 14 +- internal/dao/cronjob.go | 8 +- internal/dao/describe.go | 2 +- internal/dao/dp.go | 10 +- internal/dao/ds.go | 10 +- internal/dao/generic.go | 2 +- internal/dao/job.go | 8 +- internal/dao/node.go | 4 +- internal/dao/ns.go | 2 +- internal/dao/pod.go | 20 +- internal/dao/popeye.go | 4 +- internal/dao/port_forward.go | 8 +- internal/dao/port_forward_test.go | 2 +- internal/dao/port_forwarder.go | 9 +- internal/dao/rbac.go | 7 +- internal/dao/rbac_policy.go | 6 +- internal/dao/reference.go | 4 +- internal/dao/registry.go | 9 + internal/dao/rest_mapper.go | 2 +- internal/dao/sts.go | 10 +- internal/dao/table.go | 18 +- .../{benchspec.yml => benchspec.yaml} | 0 internal/dao/testdata/dir/{a.yml => a.yaml} | 0 internal/dao/testdata/dir/a/{b.yml => b.yaml} | 0 internal/dao/workload.go | 220 +++++ internal/model/cluster.go | 4 +- internal/model/fish_buff.go | 1 - internal/model/helpers_int_test.go | 3 +- internal/model/history.go | 11 +- internal/model/mock_clustermeta_test.go | 869 ------------------ internal/model/mock_connection_test.go | 655 ------------- internal/model/mock_metricsserver_test.go | 314 ------- internal/model/pulse_health.go | 4 +- internal/model/registry.go | 4 + internal/model/rev_values.go | 196 ++++ internal/model/stack_test.go | 18 +- internal/model/table.go | 25 +- internal/model/table_int_test.go | 4 +- internal/model/table_test.go | 2 +- internal/model/tree.go | 6 +- internal/model/types.go | 6 + internal/perf/benchmark.go | 34 +- internal/port/pf.go | 4 +- internal/render/alias.go | 2 +- internal/render/alias_test.go | 10 +- internal/render/benchmark.go | 3 +- internal/render/container.go | 19 +- internal/render/cronjob.go | 11 +- internal/render/cronjob_test.go | 2 +- internal/render/dp.go | 32 +- internal/render/dp_test.go | 2 +- internal/render/ds.go | 11 +- internal/render/ds_test.go | 2 +- internal/render/ev.go | 4 +- internal/render/generic.go | 9 +- internal/render/generic_test.go | 2 +- internal/render/header.go | 1 + internal/render/helpers.go | 4 +- internal/render/job.go | 11 +- internal/render/job_test.go | 2 +- internal/render/ns.go | 1 + internal/render/pod.go | 68 +- internal/render/pod_test.go | 8 +- internal/render/policy.go | 2 +- internal/render/port_forward_test.go | 12 +- internal/render/portforward.go | 6 +- internal/render/rbac.go | 2 +- internal/render/rs.go | 11 +- internal/render/rs_test.go | 2 +- internal/render/sts.go | 11 +- internal/render/sts_test.go | 2 +- internal/render/workload.go | 83 ++ internal/ui/app.go | 4 +- internal/ui/app_test.go | 14 +- internal/ui/config.go | 113 +-- internal/ui/config_test.go | 39 +- internal/ui/crumbs_test.go | 18 +- internal/ui/flash.go | 2 +- internal/ui/flash_test.go | 4 +- internal/ui/indicator_test.go | 9 +- internal/ui/pages.go | 2 +- internal/ui/prompt_test.go | 5 +- internal/ui/select_table.go | 15 +- internal/ui/table.go | 30 +- internal/ui/table_helper.go | 17 +- internal/ui/table_helper_test.go | 15 +- internal/ui/table_test.go | 6 +- internal/ui/types.go | 3 + internal/view/actions.go | 4 +- internal/view/alias.go | 2 +- internal/view/alias_test.go | 28 +- internal/view/app.go | 154 +--- internal/view/app_int_test.go | 38 - internal/view/app_test.go | 4 +- internal/view/benchmark.go | 14 +- internal/view/browser.go | 32 +- internal/view/cm.go | 6 +- internal/view/cmd/args.go | 74 ++ internal/view/cmd/args_test.go | 149 +++ internal/view/cmd/helpers.go | 122 +++ internal/view/cmd/helpers_test.go | 92 ++ internal/view/cmd/interpreter.go | 200 ++++ internal/view/cmd/interpreter_test.go | 465 ++++++++++ internal/view/cmd/types.go | 53 ++ internal/view/command.go | 277 +++--- internal/view/container.go | 7 +- internal/view/context.go | 5 +- internal/view/cronjob.go | 4 +- internal/view/details.go | 5 +- internal/view/dir.go | 4 +- internal/view/dp.go | 2 +- internal/view/ds.go | 2 +- internal/view/exec.go | 11 +- internal/view/helm_chart.go | 4 +- internal/view/helm_history.go | 19 + internal/view/help.go | 3 + internal/view/helpers.go | 41 +- internal/view/helpers_test.go | 3 +- internal/view/img_scan.go | 2 +- internal/view/job.go | 4 +- internal/view/live_view.go | 5 +- internal/view/live_view_test.go | 3 +- internal/view/log.go | 5 +- internal/view/log_indicator_test.go | 4 +- internal/view/log_test.go | 25 +- internal/view/logger.go | 2 +- internal/view/node.go | 12 +- internal/view/ns.go | 16 +- internal/view/ofaas.go | 49 - internal/view/pf.go | 27 +- internal/view/pf_dialog.go | 7 +- internal/view/pf_extender.go | 8 +- internal/view/picker.go | 3 + internal/view/pod.go | 9 +- internal/view/pod_test.go | 4 +- internal/view/policy.go | 4 +- internal/view/priorityclass.go | 3 +- internal/view/pulse.go | 3 + internal/view/pvc.go | 3 +- internal/view/rbac.go | 10 +- internal/view/reference.go | 2 +- internal/view/registrar.go | 5 +- internal/view/rs.go | 2 +- internal/view/sa.go | 4 +- internal/view/sanitizer.go | 16 +- internal/view/screen_dump.go | 10 +- internal/view/secret.go | 3 +- internal/view/sts.go | 2 +- internal/view/svc.go | 30 +- internal/view/table.go | 2 +- internal/view/table_int_test.go | 49 +- internal/view/types.go | 2 +- internal/view/value_extender.go | 5 +- internal/view/vul_extender.go | 2 +- internal/view/workload.go | 229 +++++ internal/view/xray.go | 16 +- internal/view/yaml.go | 2 +- internal/vul/scanner.go | 16 +- internal/watch/factory.go | 23 +- internal/watch/forwarders.go | 3 +- internal/watch/forwarders_test.go | 3 +- internal/xray/generic.go | 8 +- plugins/{carvel.yml => carvel.yaml} | 0 plugins/{crossplane.yml => crossplane.yaml} | 0 ...bug-container.yml => debug-container.yaml} | 0 plugins/{dive.yml => dive.yaml} | 0 plugins/{flux.yml => flux.yaml} | 0 plugins/{get-all.yml => get-all.yaml} | 0 ...lt-values.yml => helm-default-values.yaml} | 0 plugins/{helm-purge.yml => helm-purge.yaml} | 0 plugins/{helm_values.yml => helm_values.yaml} | 0 plugins/{job_suspend.yml => job_suspend.yaml} | 0 ...k3d_root_shell.yml => k3d_root_shell.yaml} | 0 plugins/{log_full.yml => log_full.yaml} | 0 plugins/{log_jq.yml => log_jq.yaml} | 0 plugins/{log_stern.yml => log_stern.yaml} | 0 plugins/{rm-ns.yml => rm-ns.yaml} | 0 .../{watch_events.yml => watch_events.yaml} | 0 skins/{axual.yml => axual.yaml} | 0 .../{black_and_wtf.yml => black_and_wtf.yaml} | 0 skins/{dracula.yml => dracula.yaml} | 0 skins/{gruvbox-dark.yml => gruvbox-dark.yaml} | 0 .../{gruvbox-light.yml => gruvbox-light.yaml} | 0 skins/{in_the_navy.yml => in_the_navy.yaml} | 0 skins/{kiss.yml => kiss.yaml} | 0 skins/{monokai.yml => monokai.yaml} | 0 skins/{narsingh.yml => narsingh.yaml} | 0 skins/{nightfox.yml => nightfox.yaml} | 0 skins/{nord.yml => nord.yaml} | 0 skins/{one_dark.yml => one_dark.yaml} | 0 skins/{red.yml => red.yaml} | 0 skins/{rose_pine.yml => rose_pine.yaml} | 0 skins/{snazzy.yml => snazzy.yaml} | 0 skins/{solarized-16.yml => solarized-16.yaml} | 0 ...solarized_dark.yml => solarized_dark.yaml} | 0 ...larized_light.yml => solarized_light.yaml} | 0 skins/{stock.yml => stock.yaml} | 0 skins/{transparent.yml => transparent.yaml} | 0 303 files changed, 5700 insertions(+), 5249 deletions(-) create mode 100644 change_logs/release_v0.30.0.md rename cmd/testdata/{k9s.yml => k9s.yaml} (100%) rename cmd/testdata/{k9s1.yml => k9s1.yaml} (100%) create mode 100644 internal/client/testdata/config.2 rename internal/config/{bench.go => benchmark.go} (100%) rename internal/config/{bench_test.go => benchmark_test.go} (92%) delete mode 100644 internal/config/cluster.go delete mode 100644 internal/config/cluster_test.go create mode 100644 internal/config/data/config.go create mode 100644 internal/config/data/context.go create mode 100644 internal/config/data/context_test.go create mode 100644 internal/config/data/dir.go create mode 100644 internal/config/data/dir_test.go create mode 100644 internal/config/data/feature_gate.go create mode 100644 internal/config/data/helpers.go create mode 100644 internal/config/data/helpers_test.go rename internal/config/{ => data}/ns.go (74%) create mode 100644 internal/config/data/ns_test.go create mode 100644 internal/config/data/testdata/configs/ct-1-1.yaml create mode 100644 internal/config/data/testdata/configs/ct-1-2.yaml create mode 100644 internal/config/data/testdata/configs/ct-2-1.yaml create mode 100644 internal/config/data/testdata/configs/def_ct.yaml create mode 100644 internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml create mode 100644 internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml create mode 100644 internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml create mode 100644 internal/config/data/types.go rename internal/config/{ => data}/view.go (76%) rename internal/config/{ => data}/view_test.go (78%) create mode 100644 internal/config/files.go create mode 100644 internal/config/files_test.go create mode 100644 internal/config/mock/test_helpers.go delete mode 100644 internal/config/mock_connection_test.go delete mode 100644 internal/config/mock_kubesettings_test.go delete mode 100644 internal/config/ns_test.go create mode 100644 internal/config/scans.go create mode 100644 internal/config/templates/aliases.yaml create mode 100644 internal/config/templates/benchmarks.yaml create mode 100644 internal/config/templates/hotkeys.yaml create mode 100644 internal/config/templates/stock-skin.yaml rename internal/config/testdata/{alias.yml => alias.yaml} (83%) rename internal/config/testdata/{b_containers.yml => b_containers.yaml} (100%) rename internal/config/testdata/{b_containers_1.yml => b_containers_1.yaml} (100%) rename internal/config/testdata/{b_good.yml => b_good.yaml} (100%) rename internal/config/testdata/{b_toast.yml => b_toast.yaml} (100%) rename internal/config/testdata/{bench-fred.yml => bench-fred.yaml} (100%) rename internal/config/testdata/{black_and_wtf.yml => black_and_wtf.yaml} (100%) rename internal/config/testdata/{empty_skin.yml => empty_skin.yaml} (100%) rename internal/config/testdata/{hot_key.yml => hotkeys.yaml} (90%) rename internal/config/testdata/{k8s.yml => k8s.yaml} (100%) rename internal/config/testdata/{k9s.yml => k9s.yaml} (91%) rename internal/config/testdata/{k9s1.yml => k9s1.yaml} (100%) rename internal/config/testdata/{k9s_old.yml => k9s_old.yaml} (100%) rename internal/config/testdata/{k9s_readonly.yml => k9s_readonly.yaml} (93%) rename internal/config/testdata/{k9s_toast.yml => k9s_toast.yaml} (92%) rename internal/config/testdata/{kubeconfig-test.yml => kubeconfig-test.yaml} (73%) rename internal/config/testdata/{plugin.yml => plugins.yaml} (95%) rename internal/config/testdata/plugins/{test1.yml => test1.yaml} (100%) rename internal/config/testdata/plugins/{test2.yml => test2.yaml} (100%) rename internal/config/testdata/{skin_boarked.yml => skin_boarked.yaml} (100%) rename internal/config/testdata/{view_settings.yml => view_settings.yaml} (100%) create mode 100644 internal/config/types.go rename internal/dao/testdata/{benchspec.yml => benchspec.yaml} (100%) rename internal/dao/testdata/dir/{a.yml => a.yaml} (100%) rename internal/dao/testdata/dir/a/{b.yml => b.yaml} (100%) create mode 100644 internal/dao/workload.go delete mode 100644 internal/model/mock_clustermeta_test.go delete mode 100644 internal/model/mock_connection_test.go delete mode 100644 internal/model/mock_metricsserver_test.go create mode 100644 internal/model/rev_values.go create mode 100644 internal/render/workload.go delete mode 100644 internal/view/app_int_test.go create mode 100644 internal/view/cmd/args.go create mode 100644 internal/view/cmd/args_test.go create mode 100644 internal/view/cmd/helpers.go create mode 100644 internal/view/cmd/helpers_test.go create mode 100644 internal/view/cmd/interpreter.go create mode 100644 internal/view/cmd/interpreter_test.go create mode 100644 internal/view/cmd/types.go delete mode 100644 internal/view/ofaas.go create mode 100644 internal/view/workload.go rename plugins/{carvel.yml => carvel.yaml} (100%) rename plugins/{crossplane.yml => crossplane.yaml} (100%) rename plugins/{debug-container.yml => debug-container.yaml} (100%) rename plugins/{dive.yml => dive.yaml} (100%) rename plugins/{flux.yml => flux.yaml} (100%) rename plugins/{get-all.yml => get-all.yaml} (100%) rename plugins/{helm-default-values.yml => helm-default-values.yaml} (100%) rename plugins/{helm-purge.yml => helm-purge.yaml} (100%) rename plugins/{helm_values.yml => helm_values.yaml} (100%) rename plugins/{job_suspend.yml => job_suspend.yaml} (100%) rename plugins/{k3d_root_shell.yml => k3d_root_shell.yaml} (100%) rename plugins/{log_full.yml => log_full.yaml} (100%) rename plugins/{log_jq.yml => log_jq.yaml} (100%) rename plugins/{log_stern.yml => log_stern.yaml} (100%) rename plugins/{rm-ns.yml => rm-ns.yaml} (100%) rename plugins/{watch_events.yml => watch_events.yaml} (100%) rename skins/{axual.yml => axual.yaml} (100%) rename skins/{black_and_wtf.yml => black_and_wtf.yaml} (100%) rename skins/{dracula.yml => dracula.yaml} (100%) rename skins/{gruvbox-dark.yml => gruvbox-dark.yaml} (100%) rename skins/{gruvbox-light.yml => gruvbox-light.yaml} (100%) rename skins/{in_the_navy.yml => in_the_navy.yaml} (100%) rename skins/{kiss.yml => kiss.yaml} (100%) rename skins/{monokai.yml => monokai.yaml} (100%) rename skins/{narsingh.yml => narsingh.yaml} (100%) rename skins/{nightfox.yml => nightfox.yaml} (100%) rename skins/{nord.yml => nord.yaml} (100%) rename skins/{one_dark.yml => one_dark.yaml} (100%) rename skins/{red.yml => red.yaml} (100%) rename skins/{rose_pine.yml => rose_pine.yaml} (100%) rename skins/{snazzy.yml => snazzy.yaml} (100%) rename skins/{solarized-16.yml => solarized-16.yaml} (100%) rename skins/{solarized_dark.yml => solarized_dark.yaml} (100%) rename skins/{solarized_light.yml => solarized_light.yaml} (100%) rename skins/{stock.yml => stock.yaml} (100%) rename skins/{transparent.yml => transparent.yaml} (100%) diff --git a/.gitignore b/.gitignore index 953aa0a45f..80b1e5a991 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .envrc cov.out execs -k9s +/k9s /k8s dist notes @@ -23,3 +23,4 @@ demos /code kind *.snap +/stresser diff --git a/.goreleaser.yml b/.goreleaser.yml index ae771143bb..3788ceed95 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,8 +1,10 @@ project_name: k9s + before: hooks: - go mod download - go generate ./... + release: prerelease: false diff --git a/Dockerfile b/Dockerfile index 81dfab66a6..5b5a39675d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # Build the final Docker image FROM alpine:3.19.0 -ARG KUBECTL_VERSION="v1.27.3" +ARG KUBECTL_VERSION="v1.29.0" COPY --from=build /k9s/execs/k9s /bin/k9s RUN apk add --update ca-certificates \ diff --git a/Makefile b/Makefile index 7409077e08..06dfb4c1cd 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.29.0 +VERSION ?= v0.30.0 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index bf160babc5..a94cb862ae 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,10 @@ for changes and offers subsequent commands to interact with your observed resour ## Note... -As you may know k9s is not pimped out by a big corporation with deep pockets. It is a complex OSS project that demands a lot of my time to maintain and support. K9s will always remain OSS and therefore free! That said if you feel, k9s makes your day to day Kubernetes journey a tad brighter, please consider sponsoring us or purchase a [K9sAlpha license](https://k9salpha.io). Your donations will go a long way in keeping our servers lights on and beers in our fridge! +K9s is not pimped out by a big corporation with deep pockets. +It is a complex OSS project that demands a lot of my time to maintain and support. +K9s will always remain OSS and therefore free! That said, if you feel k9s makes your day to day Kubernetes journey a tad brighter, saves you time and makes you more productive, please consider [sponsoring us!](https://github.com/sponsors/derailed) +Your donations will go a long way in keeping our servers lights on and beers in our fridge! **Thank you!** @@ -28,6 +31,35 @@ As you may know k9s is not pimped out by a big corporation with deep pockets. It --- +## Screenshots + +1. Pods + +2. Logs + +3. Deployments + + +--- + +## Demo Videos/Recordings + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) +* [K9s v0.29.0](https://youtu.be/oiU3wmoAkBo) +* [K9s v0.21.3](https://youtu.be/wG8KCwDAhnw) +* [K9s v0.19.X](https://youtu.be/kj-WverKZ24) +* [K9s v0.18.0](https://www.youtube.com/watch?v=zMnD5e53yRw) +* [K9s v0.17.0](https://www.youtube.com/watch?v=7S33CNLAofk&feature=youtu.be) +* [K9s Pulses](https://asciinema.org/a/UbXKPal6IWpTaVAjBBFmizcGN) +* [K9s v0.15.1](https://youtu.be/7Fx4XQ2ftpM) +* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s) +* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I) +* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8) +* [K9s v0 Demo](https://youtu.be/k7zseUhaXeU) + +--- + ## Documentation Please refer to our [K9s documentation](https://k9scli.io) site for installation, usage, customization and tips. @@ -42,8 +74,7 @@ Wanna discuss K9s features with your fellow `K9sers` or simply show your support ## Installation K9s is available on Linux, macOS and Windows platforms. - -* Binaries for Linux, Windows and Mac are available as tarballs in the [release](https://github.com/derailed/k9s/releases) page. +Binaries for Linux, Windows and Mac are available as tarballs in the [release page](https://github.com/derailed/k9s/releases). * Via [Homebrew](https://brew.sh/) for macOS or Linux @@ -56,6 +87,7 @@ K9s is available on Linux, macOS and Windows platforms. ```shell sudo port install k9s ``` + * Via [snap](https://snapcraft.io/k9s) for Linux ```shell @@ -81,6 +113,7 @@ K9s is available on Linux, macOS and Windows platforms. ``` * Via [Winget](https://github.com/microsoft/winget-cli) for Windows + ```shell winget install k9s ``` @@ -132,7 +165,8 @@ K9s is available on Linux, macOS and Windows platforms. ## Building From Source - K9s is currently using go v1.14 or above. In order to build K9s from source you must: + K9s is currently using GO v1.21.X or above. + In order to build K9s from source you must: 1. Clone the repo 2. Build and run the executable @@ -164,7 +198,7 @@ K9s is available on Linux, macOS and Windows platforms. You can build your own Docker image of k9s from the [Dockerfile](Dockerfile) with the following: ```shell - docker build -t k9s-docker:0.1 . + docker build -t k9s-docker:v0.0.1 . ``` You can get the latest stable `kubectl` version and pass it to the `docker build` command with the `--build-arg` option. @@ -200,7 +234,7 @@ K9s is available on Linux, macOS and Windows platforms. export K9S_EDITOR=my_fav_editor ``` -* K9s prefers recent kubernetes versions ie 1.16+ +* K9s prefers recent kubernetes versions ie 1.28+ --- @@ -208,112 +242,111 @@ K9s is available on Linux, macOS and Windows platforms. | k9s | k8s client | | ------------------ | ---------- | -| >= v0.27.0 | 0.26.1 | -| v0.26.7 - v0.26.6 | 0.25.3 | -| v0.26.5 - v0.26.4 | 0.25.1 | -| v0.26.3 - v0.26.1 | 0.24.3 | -| v0.26.0 - v0.25.19 | 0.24.2 | -| v0.25.18 - v0.25.3 | 0.22.3 | -| v0.25.2 - v0.25.0 | 0.22.0 | -| <= v0.24 | 0.21.3 | +| >= v0.27.0 | 1.26.1 | +| v0.26.7 - v0.26.6 | 1.25.3 | +| v0.26.5 - v0.26.4 | 1.25.1 | +| v0.26.3 - v0.26.1 | 1.24.3 | +| v0.26.0 - v0.25.19 | 1.24.2 | +| v0.25.18 - v0.25.3 | 1.22.3 | +| v0.25.2 - v0.25.0 | 1.22.0 | +| <= v0.24 | 1.21.3 | --- ## The Command Line ```shell -# List all available CLI options -k9s help +# List current version +k9s version + # To get info about K9s runtime (logs, configs, etc..) k9s info + +# List all available CLI options +k9s help + # To run K9s in a given namespace k9s -n mycoolns + # Start K9s in an existing KubeConfig context k9s --context coolCtx + # Start K9s in readonly mode - with all cluster modification commands disabled k9s --readonly ``` -## Logs +## Logs And Debug Logs -Given the nature of the ui k9s does produce logs to a specific location. To view the logs and turn on debug mode, use the following commands: +Given the nature of the ui k9s does produce logs to a specific location. +To view the logs and turn on debug mode, use the following commands: ```shell +# Find out where the logs are stored k9s info -# Will produces something like this -# ____ __.________ -# | |/ _/ __ \______ -# | < \____ / ___/ -# | | \ / /\___ \ -# |____|__ \ /____//____ > -# \/ \/ -# -# Configuration: ~/Library/Preferences/k9s/config.yml -# Logs: /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-fernand.log -# Screen Dumps: /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-screens-fernand - -# To view k9s logs -tail -f /var/folders/8c/hh6rqbgs5nx_c_8k9_17ghfh0000gn/T/k9s-fernand.log - -# Start K9s in debug mode -k9s -l debug ``` -## Key Bindings - -K9s uses aliases to navigate most K8s resources. +```text + ____ __.________ +| |/ _/ __ \______ +| < \____ / ___/ +| | \ / /\___ \ +|____|__ \ /____//____ > + \/ \/ + +Version: vX.Y.Z +Config: /Users/fernand/.config/k9s/config.yaml +Logs: /Users/fernand/.local/state/k9s/k9s.log +Dumps dir: /Users/fernand/.local/state/k9s/screen-dumps +Benchmarks dir: /Users/fernand/.local/state/k9s/benchmarks +Skins dir: /Users/fernand/.local/share/k9s/skins +Contexts dir: /Users/fernand/.local/share/k9s/clusters +Custom views file: /Users/fernand/.local/share/k9s/views.yaml +Plugins file: /Users/fernand/.local/share/k9s/plugins.yaml +Hotkeys file: /Users/fernand/.local/share/k9s/hotkeys.yaml +Alias file: /Users/fernand/.local/share/k9s/aliases.yaml +``` -| Action | Command | Comment | -|----------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------| -| Show active keyboard mnemonics and help | `?` | | -| Show all available resource alias | `ctrl-a` | | -| To bail out of K9s | `:q`, `ctrl-c` | | -| View a Kubernetes resource using singular/plural or short-name | `:`po⏎ | accepts singular, plural, short-name or alias ie pod or pods | -| View a Kubernetes resource in a given namespace | `:`alias namespace⏎ | | -| Filter out a resource view given a filter | `/`filter⏎ | Regex2 supported ie `fred|blee` to filter resources named fred or blee | -| Inverse regex filter | `/`! filter⏎ | Keep everything that *doesn't* match. | -| Filter resource view by labels | `/`-l label-selector⏎ | | -| Fuzzy find a resource given a filter | `/`-f filter⏎ | | -| Bails out of view/command/filter mode | `` | | -| Key mapping to describe, view, edit, view logs,... | `d`,`v`, `e`, `l`,... | | -| To view and switch to another Kubernetes context (Pod view) | `:`ctx⏎ | | -| To view and switch directly to another Kubernetes context (Last used view) | `:`ctx context-name⏎ | | -| To view and switch to another Kubernetes namespace | `:`ns⏎ | | -| To view all saved resources | `:`screendump or sd⏎ | | -| To delete a resource (TAB and ENTER to confirm) | `ctrl-d` | | -| To kill a resource (no confirmation dialog, equivalent to kubectl delete --now) | `ctrl-k` | | -| Launch pulses view | `:`pulses or pu⏎ | | -| Launch XRay view | `:`xray RESOURCE [NAMESPACE]⏎ | RESOURCE can be one of po, svc, dp, rs, sts, ds, NAMESPACE is optional | -| Launch Popeye view | `:`popeye or pop⏎ | See [popeye](#popeye) | +### View K9s logs ---- - -## Screenshots +```shell +tail -f /Users/fernand/.local/data/k9s/k9s.log +``` -1. Pods - -1. Logs - -1. Deployments - +### Start K9s in debug mode ---- +```shell +k9s -l debug +``` ---- +## Key Bindings -## Demo Videos/Recordings +K9s uses aliases to navigate most K8s resources. -* [k9s Kubernetes UI - A Terminal-Based Vim-Like Kubernetes Dashboard](https://youtu.be/boaW9odvRCc) -* [K9s v0.21.3](https://youtu.be/wG8KCwDAhnw) -* [K9s v0.19.X](https://youtu.be/kj-WverKZ24) -* [K9s v0.18.0](https://www.youtube.com/watch?v=zMnD5e53yRw) -* [K9s v0.17.0](https://www.youtube.com/watch?v=7S33CNLAofk&feature=youtu.be) -* [K9s Pulses](https://asciinema.org/a/UbXKPal6IWpTaVAjBBFmizcGN) -* [K9s v0.15.1](https://youtu.be/7Fx4XQ2ftpM) -* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s) -* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I) -* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8) -* [K9s v0 Demo](https://youtu.be/k7zseUhaXeU) +| Action | Command | Comment | +|---------------------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------| +| Show active keyboard mnemonics and help | `?` | | +| Show all available resource alias | `ctrl-a` | | +| To bail out of K9s | `:q`, `ctrl-c` | | +| View a Kubernetes resource using singular/plural or short-name | `:`pod⏎ | accepts singular, plural, short-name or alias ie pod or pods | +| View a Kubernetes resource in a given namespace | `:`pod ns-x⏎ | | +| View filtered pods (New v0.30.0!) | `:`pod /fred⏎ | View all pods filtered by fred | +| View labeled pods (New v0.30.0!) | `:`pod app=fred,env=dev⏎ | View all pods with labels matching app=fred and env=dev | +| View pods in a given context (New v0.30.0!) | `:`pod @ctx1⏎ | View all pods in context ctx1. Switches out your current k9s context! | +| Filter out a resource view given a filter | `/`filter⏎ | Regex2 supported ie `fred|blee` to filter resources named fred or blee | +| Inverse regex filter | `/`! filter⏎ | Keep everything that *doesn't* match. | +| Filter resource view by labels | `/`-l label-selector⏎ | | +| Fuzzy find a resource given a filter | `/`-f filter⏎ | | +| Bails out of view/command/filter mode | `` | | +| Key mapping to describe, view, edit, view logs,... | `d`,`v`, `e`, `l`,... | | +| To view and switch to another Kubernetes context (Pod view) | `:`ctx⏎ | | +| To view and switch directly to another Kubernetes context (Last used view) | `:`ctx context-name⏎ | | +| To view and switch to another Kubernetes namespace | `:`ns⏎ | | +| To view all saved resources | `:`screendump or sd⏎ | | +| To delete a resource (TAB and ENTER to confirm) | `ctrl-d` | | +| To kill a resource (no confirmation dialog, equivalent to kubectl delete --now) | `ctrl-k` | | +| Launch pulses view | `:`pulses or pu⏎ | | +| Launch XRay view | `:`xray RESOURCE [NAMESPACE]⏎ | RESOURCE can be one of po, svc, dp, rs, sts, ds, NAMESPACE is optional | +| Launch Popeye view | `:`popeye or pop⏎ | See [popeye](#popeye) | --- @@ -327,13 +360,13 @@ K9s uses aliases to navigate most K8s resources. > NOTE: This is still in flux and will change while in pre-release stage! - > NOTE! Thanks to [Mr Alexandru Placenta](https://github.com/placintaalexandru) the config files can now use either `.yml` or `.yaml` mimes. - ```yaml - # $XDG_CONFIG_HOME/k9s/config.yml + # $XDG_CONFIG_HOME/k9s/config.yaml k9s: # Enable periodic refresh of resource browser windows. Default false liveViewAutoRefresh: false + # The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info) + screenDumpDir: /tmp/dumps # Represents ui poll intervals. Default 2secs refreshRate: 2 # Number of retries once the connection to the api-server is lost. Default 15. @@ -368,12 +401,6 @@ K9s uses aliases to navigate most K8s resources. textWrap: false # Toggles log line timestamp info. Default false showTime: false - # Indicates the current kube context. Defaults to current context - currentContext: minikube - # Indicates the current kube cluster. Defaults to current context cluster - currentCluster: minikube - # KeepMissingClusters will keep clusters in the config if they are missing from the current kubeconfig file. Default false - KeepMissingClusters: false # Provide shell pod customization when nodeShell feature gate is enabled! shellPod: # The shell pod image to use. @@ -386,41 +413,13 @@ K9s uses aliases to navigate most K8s resources. memory: 100Mi # Enable TTY tty: true - # Persists per cluster preferences for favorite namespaces and view. - clusters: - coolio: - namespace: - active: coolio - # With this set, the favorites list won't be updated as you switch namespaces - lockFavorites: false - favorites: - - cassandra - - default - view: - active: po - featureGates: - # Toggles NodeShell support. Allow K9s to shell into nodes if needed. Default false. - nodeShell: true - # The IP Address to use when launching a port-forward. - portForwardAddress: 1.2.3.4 - kind: - namespace: - active: all - favorites: - - all - - kube-system - - default - view: - active: dp - # The path to screen dump. Default: '%temp_dir%/k9s-screens-%username%' (k9s info) - screenDumpDir: /tmp ``` --- ## Popeye Configuration -K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes cluster sanitizer. Popeye itself uses a configuration called `spinach.yml`, but when integrating with K9s the cluster-specific file should be name `$XDG_CONFIG_HOME/k9s/_spinach.yml`. This allows you to have a different spinach config per cluster. +K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes cluster sanitizer. Popeye itself uses a configuration called `spinach.yml`, but when integrating with K9s the cluster-specific file should be name `$XDG_CONFIG_HOME/share/k9s/clusters/clusterX/contextY/spinach.yml`. This allows you to have a different spinach config per cluster. --- @@ -429,7 +428,7 @@ K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes By enabling the nodeShell feature gate on a given cluster, K9s allows you to shell into your cluster nodes. Once enabled, you will have a new `s` for `shell` menu option while in node view. K9s will launch a pod on the selected node using a special k9s_shell pod. Furthermore, you can refine your shell pod by using a custom docker image preloaded with the shell tools you love. By default k9s uses a BusyBox image, but you can configure it as follows: ```yaml -# $XDG_CONFIG_HOME/k9s/config.yml +# $XDG_CONFIG_HOME/k9s/config.yaml k9s: # You can also further tune the shell pod specification shellPod: @@ -438,41 +437,63 @@ k9s: limits: cpu: 100m memory: 100Mi - clusters: - # Configures node shell on cluster blee - blee: - featureGates: - # You must enable the nodeShell feature gate to enable shelling into nodes - nodeShell: true +``` + +Then in your cluster configuration file... + +```yaml +# $XDG_DATA_HOME/k9s/clusters/cluster-1/context-1 +k9s: + cluster: cluster-1 + readOnly: false + namespace: + active: default + lockFavorites: false + favorites: + - kube-system + - default + view: + active: po + featureGates: + nodeShell: true # => Enable this feature gate to make nodeShell available on this cluster + portForwardAddress: localhost ``` --- ## Command Aliases -In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.config/k9s` define a file called `alias.yml`. A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file: +In K9s, you can define your very own command aliases (shortnames) to access your resources. In your `$HOME/.config/k9s` define a file called `aliases.yaml`. +A K9s alias defines pairs of alias:gvr. A gvr (Group/Version/Resource) represents a fully qualified Kubernetes resource identifier. Here is an example of an alias file: ```yaml -# $XDG_CONFIG_HOME/k9s/alias.yml -alias: +# $XDG_DATA_HOME/k9s/aliases.yaml +aliases: pp: v1/pods crb: rbac.authorization.k8s.io/v1/clusterrolebindings + # As of v0.30.0 you can also refer to another command alias... + fred: pod fred app=blee # => view pods in namespace fred with labels matching app=blee ``` -Using this alias file, you can now type pp/crb to list pods or ClusterRoleBindings respectively. +Using this aliases file, you can now type `:pp` or `:crb` or `:fred` to activate their respective commands. --- ## HotKey Support -Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. We're introducing hotkeys that allows a user to define their own hotkeys to activate their favorite resource views. In order to enable hotkeys please follow these steps: +Entering the command mode and typing a resource name or alias, could be cumbersome for navigating thru often used resources. +We're introducing hotkeys that allow users to define their own key combination to activate their favorite resource views. + +Additionally, you can define context specific hotkeys by add a context level configuration file in `$XDG_DATA_HOME/k9s/clusters/clusterX/contextY/hotkeys.yaml` -1. Create a file named `$XDG_CONFIG_HOME/k9s/hotkey.yml` -2. Add the following to your `hotkey.yml`. You can use resource name/short name to specify a command ie same as typing it while in command mode. +In order to surface hotkeys globally please follow these steps: + +1. Create a file named `$XDG_CONFIG_HOME/k9s/hotkeys.yaml` +2. Add the following to your `hotkeys.yaml`. You can use resource name/short name to specify a command ie same as typing it while in command mode. ```yaml - # $XDG_CONFIG_HOME/k9s/hotkey.yml - hotKey: + # $XDG_CONFIG_HOME/k9s/hotkeys.yaml + hotKeys: # Hitting Shift-0 navigates to your pod view shift-0: shortCut: Shift-0 @@ -490,7 +511,8 @@ Entering the command mode and typing a resource name or alias, could be cumberso command: xray deploy ``` - Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. Also your hotkey file will be automatically reloaded so you can readily use your hotkeys as you define them. + Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. + Also your hotkeys file will be automatically reloaded so you can readily use your hotkeys as you define them. You can choose any keyboard shortcuts that make sense to you, provided they are not part of the standard K9s shortcuts list. @@ -502,9 +524,9 @@ Entering the command mode and typing a resource name or alias, could be cumberso As of v0.25.0, you can leverage the `FastForwards` feature to tell K9s how to default port-forwards. In situations where you are dealing with multiple containers or containers exposing multiple ports, it can be cumbersome to specify the desired port-forward from the dialog as in most cases, you already know which container/port tuple you desire. For these use cases, you can now annotate your manifests with the following annotations: -- `k9scli.io/auto-port-forwards` +@ `k9scli.io/auto-port-forwards` activates one or more port-forwards directly bypassing the port-forward dialog all together. -- `k9scli.io/port-forwards` +@ `k9scli.io/port-forwards` pre-selects one or more port-forwards when launching the port-forward dialog. The annotation value takes on the shape `container-name::[local-port:]container-port` @@ -553,14 +575,14 @@ The annotation value must specify a container to forward to as well as a local p [SneakCast v0.17.0 on The Beach! - Yup! sound is sucking but what a setting!](https://youtu.be/7S33CNLAofk) -You can change which columns shows up for a given resource via custom views. To surface this feature, you will need to create a new configuration file, namely `$XDG_CONFIG_HOME/k9s/views.yml`. This file leverages GVR (Group/Version/Resource) to configure the associated table view columns. If no GVR is found for a view the default rendering will take over (ie what we have now). Going wide will add all the remaining columns that are available on the given resource after your custom columns. To boot, you can edit your views config file and tune your resources views live! +You can change which columns shows up for a given resource via custom views. To surface this feature, you will need to create a new configuration file, namely `$XDG_CONFIG_HOME/k9s/views.yaml`. This file leverages GVR (Group/Version/Resource) to configure the associated table view columns. If no GVR is found for a view the default rendering will take over (ie what we have now). Going wide will add all the remaining columns that are available on the given resource after your custom columns. To boot, you can edit your views config file and tune your resources views live! > NOTE: This is experimental and will most likely change as we iron this out! Here is a sample views configuration that customize a pods and services views. ```yaml -# $XDG_CONFIG_HOME/k9s/views.yml +# $XDG_CONFIG_HOME/k9s/views.yaml k9s: views: v1/pods: @@ -585,7 +607,9 @@ k9s: ## Plugins -K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$XDG_CONFIG_HOME/k9s/plugin.yml` to locate all available plugins. A plugin is defined as follows: +K9s allows you to extend your command line and tooling by defining your very own cluster commands via plugins. K9s will look at `$XDG_CONFIG_HOME/k9s/plugins.yaml` to locate all available plugins. + +A plugin is defined as follows: * Shortcut option represents the key combination a user would type to activate the plugin * Confirm option (when enabled) lets you see the command that is going to be executed and gives you an option to confirm or prevent execution @@ -614,13 +638,13 @@ K9s does provide additional environment variables for you to customize your plug Curly braces can be used to embed an environment variable inside another string, or if the column name contains special characters. (e.g. `${NAME}-example` or `${COL-%CPU/L}`) -### Example +### Plugin Example -This defines a plugin for viewing logs on a selected pod using `ctrl-l` for shortcut. +This defines a plugin for viewing logs on a selected pod using `ctrl-l` as shortcut. ```yaml -# $XDG_CONFIG_HOME/k9s/plugin.yml -plugin: +# $XDG_DATA_HOME/k9s/plugins.yaml +plugins: # Defines a plugin to provide a `ctrl-l` shortcut to tail the logs while in pod view. fred: shortCut: Ctrl-L @@ -657,12 +681,14 @@ Initially, the benchmarks will run with the following defaults: * HTTP Verb: GET * Path: / -The PortForward view is backed by a new K9s config file namely: `$XDG_CONFIG_HOME/k9s/bench-.yml` (note: extension is `yml` and not `yaml`). Each cluster you connect to will have its own bench config file, containing the name of the K8s context for the cluster. Changes to this file should automatically update the PortForward view to indicate how you want to run your benchmarks. +The PortForward view is backed by a new K9s config file namely: `$XDG_DATA_HOME/k9s/clusters/clusterX/contextY/benchmarks.yaml`. Each cluster you connect to will have its own bench config file, containing the name of the K8s context for the cluster. Changes to this file should automatically update the PortForward view to indicate how you want to run your benchmarks. -Here is a sample benchmarks.yml configuration. Please keep in mind this file will likely change in subsequent releases! +Benchmarks result reports are stored in `$XDG_STATE_HOME/k9s/clusters/clusterX/contextY` + +Here is a sample benchmarks.yaml configuration. Please keep in mind this file will likely change in subsequent releases! ```yaml -# This file resides in $XDG_CONFIG_HOME/k9s/bench-mycontext.yml +# This file resides in $XDG_DATA_HOME/k9s/clusters/clusterX/contextY/benchmarks.yaml benchmarks: # Indicates the default concurrency and number of requests setting if a container or service rule does not match. defaults: @@ -810,36 +836,90 @@ Example: Dracula Skin ;) Dracula Skin -You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s default skin is loaded from `$XDG_CONFIG_HOME/k9s/skin.yml`. If a skin file is detected then the skin will be loaded if not the current stock skin remains in effect. +You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. See this repo `skins` directory for examples. +You can skin k9s by default by specifying a UI.skin attribute. You can also change K9s skins based on the context you are connecting too. +In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin file without the extension!) and copy this repo +`skins/dracula.yaml` to `$XDG_CONFIG_HOME/k9s/skins/` directory. -You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify a skin field on your cluster config aka `skin: dracula` (just the name of the skin!) and copy this repo skins/dracula.yml to `$XDG_CONFIG_HOME/k9s/skins` directory. -Below is a sample skin file, more skins are available in the skins directory in this repo, just simply copy any of these in your k9s home dir as `skin.yml`. +In the case where your cluster spans several contexts, you can add a skin context configuration to your context configuration. +This is a collection of {context_name, skin} tuples (please see example below!) Colors can be defined by name or using a hex representation. Of recent, we've added a color named `default` to indicate a transparent background color to preserve your terminal background color settings if so desired. > NOTE: This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly! > NOTE: Please see [K9s Skins](https://k9scli.io/topics/skins/) for a list of available colors. +To skin a specific context and provided the file `in_the_navy.yaml` is present in your skins directory. + ```yaml -# Make cluster fred display in_the_navy skin when loaded... +# $XDG_DATA_HOME/k9s/clusters/clusterX/contextY/config.yaml k9s: - ... - clusters: - fred: - # Override the default skin and use this skin for this cluster. - # NOTE: Just the skin file name to extension! - skin: in_the_navy # -> Look for a skin file in ~/.config/k9s/skins/in_the_navy.yml - namespace: - ... - view: - active: pod - featureGates: - nodeShell: false - portForwardAddress: localhost + cluster: clusterX + skin: in_the_navy + readOnly: false + namespace: + active: default + lockFavorites: false + favorites: + - kube-system + - default + view: + active: po + featureGates: + nodeShell: false + portForwardAddress: localhost +``` + +You can also specify a default skin for all contexts in the root k9s config file as so: + +```yaml +k9s: + liveViewAutoRefresh: false + screenDumpDir: /tmp/dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + # By default all contexts wil use the dracula skin unless explicitly overridden in the context config file. + skin: dracula # => assumes the file skins/dracular.yaml is present in the $XDG_DATA_HOME/k9s/skins directory + skipLatestRevCheck: false + disablePodCounting: false + shellPod: + image: busybox + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + blackList: + namespaces: [] + labels: {} + logger: + tail: 100 + buffer: 5000 + sinceSeconds: -1 + fullScreenLogs: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 ``` ```yaml -# in_the_navy.yml: Skin InTheNavy... +# $XDG_DATA_HOME/k9s/skins/in_the_navy.yaml +# Skin InTheNavy! k9s: # General K9s styles body: @@ -935,7 +1015,7 @@ that you want, please file an issue and if so inclined submit a PR! K9s will most likely blow up if... -1. You're running older versions of Kubernetes. K9s works best on Kubernetes latest. +1. You're running older versions of Kubernetes. K9s works best on later Kubernetes versions. 2. You don't have enough RBAC fu to manage your cluster. --- @@ -966,4 +1046,4 @@ We always enjoy hearing from folks who benefit from our work! --- -Imhotep  © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) +Imhotep  © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/change_logs/release_v0.30.0.md b/change_logs/release_v0.30.0.md new file mode 100644 index 0000000000..9f3be44dc5 --- /dev/null +++ b/change_logs/release_v0.30.0.md @@ -0,0 +1,313 @@ + + +# Release v0.30.0 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +## ♫ Sounds Behind The Release ♭ + +Going back to the classics... + +* [Home For Christmas - Fats Domino](https://www.youtube.com/watch?v=ykAVdPz8o1Q) +* [Our Love - Al Jarreau](https://www.youtube.com/watch?v=9ztMe6GIwi8) +* [Body And Soul - Louis Armstrong](https://www.youtube.com/watch?v=2Gnz69TbqHQ) +* [On The Dunes - Donald Fagen](https://www.youtube.com/watch?v=QoVT3XcMVvk) +* [Ciao - Lucio Dalla](https://www.youtube.com/watch?v=qcqXcmKu_I4) +* [Basin Street Blues - Louis Prima](https://www.youtube.com/watch?v=IijXXXpUefM&list=RDIijXXXpUefM&start_radio=1) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Bojan](https://github.com/rbojan) + +> Sponsorship cancellations since the last release: **5!** 🥹 + +--- + +## 🎄 Feature Release! 🎄 + +🎅 Merry Christmas to all and Best wishes for the new year!!🧑‍🎄 + +--- + +### Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +### Breaking Bad! + +> ☢️ !!Prior to installing v0.30.0!! Please be sure to backup your k9s configs directories or move them somewhere safe!! + +> ☢️ Please watch the v0.30.0 Sneak peek series (links below) for detailed information. +> +> ☢️ Most K9s configuration files have either split or changed location or names on this drop!! + +> We recommend moving your current k9s config dirs to another location and start k9s from scratch and let it create and initialize the various configs +> to their new spec and location. You can then use your existing setup and patch with the new layout/spec. +> As of v0.30.0 all config files now use the `*.yaml` extension. We did our best to update all the docs to match the new version. +> If you find doc issues either file an issue or better yet submit a PR! + +Some of you might say: `You're on the roll their bud! Two breaking changes drops in a row!!` +Per the wise words of my beloved Grand mama! `One can't cook a decent meal without creating a mess!` +Not to mention we're still at v0.x.y so `Open season on breaking changes` is very much in full effect. + +Tho I have tested this drop quite a bit, there is a strong chance that I've broken some stuff. +The key here is to walk the fine line of improving k9s code base and features set with minimal impact to you. +As you know by now, I am committed to ease the pain and resolve issues quickly to get you all back up and running. + +From the scope changes in this release, I would caution that this drop will likely break you! +If so, worry not! We will fix the duds so we are `Happy as a Hippo` once again. + +There was a few issues with the way K9s persists it's configuration and various artifacts. So we rewrote it! +First and foremost all k9s related YAML resources, will now use the standard ".yaml" extension. +I think we've bloated the code checking for both extensions with no real actionable value! + +As it stands the main K9s configuration `config.yml` will now be static. These settings are now readonly! All the dynamic configurations that K9s manages now live in a new directory aka `clusters`. The clusters directory manages your k8s cluster/context configurations. So things like active view, namespace, favorites, etc... now live in this directory. K9s configurations are still managed using either xdg `XDG_CONFIG_HOME` or you can set `K9S_CONFIG_DIR` to specify a your preferred k9s configs location. Also all config files will now use the ".yaml" extension vs ".yml"!! + +So the main k9s configuration (static) now looks like this: + +```yaml +# $XDG_CONFIG_HOME/k9s/config.yaml +# File will be autogenerated will all the default fixins if not found in the config specification. +k9s: + liveViewAutoRefresh: false + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: # NOTE! New level!! + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false + # ShellPod configuration applies to all your clusters + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + # ImageScan config changed from v0.29.0! + imageScans: + enable: false + # Now figures exclusions ie blacklist namespaces or specific workload labels + blackList: + # Exclude the following namespaces for image vulscans! + namespaces: + - kube-system + - fred + # Exclude the following labels from image vulscans! + labels: + k8s-app: + - kindnet + - bozo + env: + - dev + logger: + tail: 100 + buffer: 5000 + sinceSeconds: -1 + fullScreenLogs: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 +``` + +Next context specific configurations that are managed by you and k9s live in the XDG data directory +i.e `$XDG_DATA_HOME/k9s/clusters` or `$K9S_CONFIG_DIR/clusters` if the env var is set. + +```text +$XDG_DATA_HOME/k9s +// Clusters tracks visited kubeconfig cluster/contexts +├── clusters +│ ├── fred +│ │ └── bozo +│ │ └── config.yaml +│ ├── bozorg +│ │ ├── kind-bozo-1 +│ │ │ └── config.yaml +│ │ ├── kind-bozo-2 +│ │ │ └── config.yaml +│ │ └── kind-bozo-3 +│ │ └── config.yaml +│ └── bumblebeetuna +│ └── blee +│ └── config.yaml +└── skins + ├── black_and_wtf.yaml + ├── dracula.yaml + ├── in_the_navy.yml + ├── ... +``` + +Now looking at a given context configuration i.e cluster-1/context-1/config.yaml + +```yaml +# $XDG_DATA_HOME/k9s/clusters/bumblebeetuna/blee/config.yaml +k9s: + cluster: bumblebeetuna + readOnly: false # [New!] you can now single out a given context and make it readonly. Woof! + skin: in_the_navy # [NEW!] you can also skin individual contexts. Woof Woof! + namespace: + active: all + lockFavorites: false + favorites: + - all + - kube-system + - default + view: + active: dp + featureGates: + nodeShell: false + portForwardAddress: localhost +``` + +Transient artifacts ie k9s logs, screen-dumps, benchmarks etc now live in the state config dir. + +```text +$XDG_STATE_HOME/k9s +├── k9s.log # K9s log files +└── screen-dumps + └── bumblebeetuna # Screen dumps location for context blee + └── blee + └── deployments-kube-system-1703018199222861000.csv +``` + +If you get stuck or if my instructions are just `clear as mud`... `k9s info` is always your friend!! + +I feel this is an improvement (tho I might be unanimous on this!) especially for folks dealing with multi-clusters or swapping out there kubeconfigs... + +> NOTE! Paint is still fresh on this deal. Proceed with caution and please help us flush this feature out! + +--- + +# Got Prompt? + +In this drop, we've also gave the k9s command prompt aka `:xxx` some love. +You have the ability to specify filter directly in the prompt. + +So for example, you can now run something like `:po /fred` to run pod view with a filter to just show pods containing `fred`. Likewise `:po k8s-app=fred,env=blee` to filter by labels. +And now for the`Krampus` special... you can see pods in a different context all together via `:pod @ctx-2`. +Finally you can combo and send the `whole enchilada` via `:po k8s-app=fred /blee ns-1 @ctx-x` +Did I mention with completion where applicable? Yes Please!! +Compliments of [Jayson Wang](https://github.com/wjiec). Be sure to thank him!! + +Put these frequent flyers command in an alias and now you can nav your clusters with `even more style`! + +--- + +# All Is Love? + +🎵 `On The twentieth day of Christmas my true love gave to me... Ten worklords a-leaping??...` 🎵 + +This is a feature reported by many of you and its (finally!) here. As of this drop, we intro the `workload` view aka `wk` which is similar to `kubetcl get all`. I was reluctant to intro it given the potential hazards on larger clusters but figured why not? YOLO. I think using it in combo with the prompt updates it could pack a serious punch to observe workload related artifacts. + +--- + +# The Black List... + +As it seems customary with all k9s new features, folks want to turn them off ;( +The `Vulscan` feature did not get out unscaped ;( +As it was rightfully so pointed out, you may want to opted out scans for images that you do not control. +Tho I think it might be a good idea to run wide open once in a while to see if your cluster has any holes?? +For this reason, we've opted to intro a blacklist section under the image scan configuration to exclude certain images from the scans. + +Here is a sample configuration: + +```yaml +k9s: + liveViewAutoRefresh: false + refreshRate: 2 + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + imageScans: + enable: true + blackList: + # Skip scans on these namespaces + namespaces: + - ns-1 + - ns-2 + # Skip scans for pods matching these labels + labels: + - app: + - fred + - blee + - duh + - env: + - dev +``` + +This is a bit of a blur now, but I think that it! We hope you guys will dig this drop or at least the concepts as likely this is going to be `Open Season` on bugs ;( + +🎵 `On The second day of Christmas my true love gave to me... Eleven buggers bugging??...` 🎵 + +Lastly looks like the sponsorship stream is down to an alarming trickle so if you dig this project and find it useful be sure `to give til it hurts!` + +--- + +🎅 Best wishes to you and yours for good health and happiness this holiday season!! 🎉 + +AndJoy! +Fernand + +--- + +## Resolved Issues + +* [#2346](https://github.com/derailed/k9s/issues/2346) k9s should not write state to config.yaml +* [#2335](https://github.com/derailed/k9s/issues/2335) Restore 0.28 column order on pod view bug +* [#2331](https://github.com/derailed/k9s/issues/2331) Set a shortcut key to run Vuln Scanning on a resource. Don't scan every resource at every startup. +* [#2283](https://github.com/derailed/k9s/issues/2283) Adding auto complete in search bar + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2357](https://github.com/derailed/k9s/pull/2357) Added ln check for snap +* [#2350](https://github.com/derailed/k9s/pull/2350) Add symlink into snap +* [#2348](https://github.com/derailed/k9s/pull/2348) Fix(misc plugins): split up multiline commands, use less -K everywhere +* [#2343](https://github.com/derailed/k9s/pull/2343) Passing on the correct suggestion parameters +* [#2341](https://github.com/derailed/k9s/pull/2340) Adding value, yaml and describe views to helm-history +* [#2340](https://github.com/derailed/k9s/pull/2340) Add pkgx to installation section + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/info.go b/cmd/info.go index 1ac5a5ff5f..719c946176 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -5,7 +5,6 @@ package cmd import ( "fmt" - "os" "github.com/derailed/k9s/internal/color" @@ -19,21 +18,31 @@ import ( func infoCmd() *cobra.Command { return &cobra.Command{ Use: "info", - Short: "Print configuration info", - Long: "Print configuration information", - Run: func(cmd *cobra.Command, args []string) { - printInfo() - }, + Short: "List K9s configurations info", + RunE: printInfo, } } -func printInfo() { - const fmat = "%-25s %s\n" +func printInfo(cmd *cobra.Command, args []string) error { + if err := config.InitLocs(); err != nil { + return err + } + const fmat = "%-27s %s\n" printLogo(color.Cyan) - printTuple(fmat, "Configuration", config.K9sConfigFile, color.Cyan) - printTuple(fmat, "Logs", config.DefaultLogFile, color.Cyan) - printTuple(fmat, "Screen Dumps", getScreenDumpDirForInfo(), color.Cyan) + printTuple(fmat, "Version", version, color.Cyan) + printTuple(fmat, "Config", config.AppConfigFile, color.Cyan) + printTuple(fmat, "Custom Views", config.AppViewsFile, color.Cyan) + printTuple(fmat, "Plugins", config.AppPluginsFile, color.Cyan) + printTuple(fmat, "Hotkeys", config.AppHotKeysFile, color.Cyan) + printTuple(fmat, "Aliases", config.AppAliasesFile, color.Cyan) + printTuple(fmat, "Skins", config.AppSkinsDir, color.Cyan) + printTuple(fmat, "Context Configs", config.AppContextsDir, color.Cyan) + printTuple(fmat, "Logs", config.AppLogFile, color.Cyan) + printTuple(fmat, "Benchmarks", config.AppBenchmarksDir, color.Cyan) + printTuple(fmat, "ScreenDumps", getScreenDumpDirForInfo(), color.Cyan) + + return nil } func printLogo(c color.Paint) { @@ -45,23 +54,20 @@ func printLogo(c color.Paint) { // getScreenDumpDirForInfo get default screen dump config dir or from config.K9sConfigFile configuration. func getScreenDumpDirForInfo() string { - if config.K9sConfigFile == "" { - return config.K9sDefaultScreenDumpDir + if config.AppConfigFile == "" { + return config.AppDumpsDir } - f, err := os.ReadFile(config.K9sConfigFile) + f, err := os.ReadFile(config.AppConfigFile) if err != nil { log.Error().Err(err).Msgf("Reads k9s config file %v", err) - return config.K9sDefaultScreenDumpDir + return config.AppDumpsDir } var cfg config.Config if err := yaml.Unmarshal(f, &cfg); err != nil { log.Error().Err(err).Msgf("Unmarshal k9s config %v", err) - return config.K9sDefaultScreenDumpDir - } - if cfg.K9s == nil { - cfg.K9s = config.NewK9s() + return config.AppDumpsDir } return cfg.K9s.GetScreenDumpDir() diff --git a/cmd/info_test.go b/cmd/info_test.go index 6f2a240780..e181848502 100644 --- a/cmd/info_test.go +++ b/cmd/info_test.go @@ -16,32 +16,31 @@ func Test_getScreenDumpDirForInfo(t *testing.T) { expectedScreenDumpDir string }{ "withK9sConfigFile": { - k9sConfigFile: "testdata/k9s.yml", + k9sConfigFile: "testdata/k9s.yaml", expectedScreenDumpDir: "/tmp", }, "withEmptyK9sConfigFile": { k9sConfigFile: "", - expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, + expectedScreenDumpDir: config.AppDumpsDir, }, "withInvalidK9sConfigFilePath": { k9sConfigFile: "invalid", - expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, + expectedScreenDumpDir: config.AppDumpsDir, }, "withScreenDumpDirEmptyInK9sConfigFile": { - k9sConfigFile: "testdata/k9s1.yml", - expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, + k9sConfigFile: "testdata/k9s1.yaml", + expectedScreenDumpDir: config.AppDumpsDir, }, } for k := range tests { u := tests[k] t.Run(k, func(t *testing.T) { - initK9sConfigFile := config.K9sConfigFile - - config.K9sConfigFile = u.k9sConfigFile + initK9sConfigFile := config.AppConfigFile + config.AppConfigFile = u.k9sConfigFile assert.Equal(t, u.expectedScreenDumpDir, getScreenDumpDirForInfo()) - config.K9sConfigFile = initK9sConfigFile + config.AppConfigFile = initK9sConfigFile }) } } diff --git a/cmd/root.go b/cmd/root.go index 882b96e270..7a3bfc17da 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,8 @@ import ( "os" "runtime/debug" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" @@ -20,12 +22,12 @@ import ( ) const ( - appName = "k9s" + appName = config.AppName shortAppDesc = "A graphical CLI for your Kubernetes cluster management." longAppDesc = "K9s is a CLI to view and manage your Kubernetes clusters." ) -var _ config.KubeSettings = (*client.Config)(nil) +var _ data.KubeSettings = (*client.Config)(nil) var ( version, commit, date = "dev", "dev", client.NA @@ -43,6 +45,10 @@ var ( ) func init() { + if err := config.InitLogLoc(); err != nil { + fmt.Printf("Fail to init k9s logs location %s\n", err) + } + rootCmd.AddCommand(versionCmd(), infoCmd()) initK9sFlags() initK8sFlags() @@ -51,18 +57,21 @@ func init() { // Execute root command. func Execute() { if err := rootCmd.Execute(); err != nil { - log.Panic().Err(err) + panic(err) } } func run(cmd *cobra.Command, args []string) error { - if err := config.EnsureDirPath(*k9sFlags.LogFile, config.DefaultDirMod); err != nil { + if err := config.InitLocs(); err != nil { return err } - mod := os.O_CREATE | os.O_APPEND | os.O_WRONLY - file, err := os.OpenFile(*k9sFlags.LogFile, mod, config.DefaultFileMod) + file, err := os.OpenFile( + *k9sFlags.LogFile, + os.O_CREATE|os.O_APPEND|os.O_WRONLY, + data.DefaultFileMod, + ) if err != nil { - return err + return fmt.Errorf("Log file %q init failed: %w", *k9sFlags.LogFile, err) } defer func() { if file != nil { @@ -80,8 +89,8 @@ func run(cmd *cobra.Command, args []string) error { }() log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) - zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel)) + app := view.NewApp(loadConfiguration()) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { return err @@ -99,38 +108,24 @@ func run(cmd *cobra.Command, args []string) error { func loadConfiguration() *config.Config { log.Info().Msg("🐶 K9s starting up...") - // Load K9s config file... k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) - - if err := k9sCfg.Load(config.K9sConfigFile); err != nil { + if err := k9sCfg.Load(config.AppConfigFile); err != nil { log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") + k9sCfg.K9s.Generate(k9sFlags) } - - if *k9sFlags.RefreshRate != config.DefaultRefreshRate { - k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate) - } - - k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless) - k9sCfg.K9s.OverrideLogoless(*k9sFlags.Logoless) - k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless) - k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly) - k9sCfg.K9s.OverrideWrite(*k9sFlags.Write) - k9sCfg.K9s.OverrideCommand(*k9sFlags.Command) - k9sCfg.K9s.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir) - if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("refine failed") } conn, err := client.InitConnection(k8sCfg) k9sCfg.SetConnection(conn) if err != nil { - log.Error().Err(err).Msgf("failed to connect to cluster %q", k9sCfg.K9s.CurrentContext) + log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName()) return k9sCfg } // Try to access server version if that fail. Connectivity issue? if !k9sCfg.GetConnection().CheckConnectivity() { - log.Panic().Msgf("Cannot connect to cluster %s", k9sCfg.K9s.CurrentCluster) + log.Panic().Msgf("Cannot connect to context %s", k9sCfg.K9s.ActiveContextName()) } if !k9sCfg.GetConnection().ConnectionOK() { panic("No connectivity") @@ -177,7 +172,7 @@ func initK9sFlags() { rootCmd.Flags().StringVarP( k9sFlags.LogFile, "logFile", "", - config.DefaultLogFile, + config.AppLogFile, "Specify the log file", ) rootCmd.Flags().BoolVar( diff --git a/cmd/testdata/k9s.yml b/cmd/testdata/k9s.yaml similarity index 100% rename from cmd/testdata/k9s.yml rename to cmd/testdata/k9s.yaml diff --git a/cmd/testdata/k9s1.yml b/cmd/testdata/k9s1.yaml similarity index 100% rename from cmd/testdata/k9s1.yml rename to cmd/testdata/k9s1.yaml diff --git a/go.mod b/go.mod index 9573a36397..1c246e6949 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/anchore/grype v0.73.4 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/derailed/popeye v0.11.1 + github.com/derailed/popeye v0.11.2 github.com/derailed/tcell/v2 v2.3.1-rc.3 github.com/derailed/tview v0.8.2 github.com/fatih/color v1.16.0 @@ -27,15 +27,15 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.13.3 - k8s.io/api v0.28.4 - k8s.io/apiextensions-apiserver v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/cli-runtime v0.28.4 - k8s.io/client-go v0.28.4 + helm.sh/helm/v3 v3.13.2 + k8s.io/api v0.29.0 + k8s.io/apiextensions-apiserver v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/cli-runtime v0.29.0 + k8s.io/client-go v0.29.0 k8s.io/klog/v2 v2.110.1 - k8s.io/kubectl v0.28.4 - k8s.io/metrics v0.28.4 + k8s.io/kubectl v0.29.0 + k8s.io/metrics v0.29.0 sigs.k8s.io/yaml v1.4.0 ) @@ -109,7 +109,7 @@ require ( github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect @@ -153,6 +153,7 @@ require ( github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gookit/color v1.5.4 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect @@ -216,9 +217,10 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/gomega v1.27.10 // indirect + github.com/onsi/gomega v1.29.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.5 // indirect @@ -310,10 +312,10 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.28.4 // indirect - k8s.io/component-base v0.28.4 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + k8s.io/apiserver v0.29.0 // indirect + k8s.io/component-base v0.29.0 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect modernc.org/libc v1.29.0 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.7.2 // indirect @@ -322,5 +324,5 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 841d9d4792..ba245aaa7c 100644 --- a/go.sum +++ b/go.sum @@ -384,8 +384,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= -github.com/derailed/popeye v0.11.1 h1:bjt5mXkcXY696ipuJqwY1sa5s3i431L9BlkQc6EuaqE= -github.com/derailed/popeye v0.11.1/go.mod h1:NkvjHH1F94tE7Ui17PlYiagQcFt7yXUV2hIhPzSK+0w= +github.com/derailed/popeye v0.11.2 h1:8MKMjYBJdYNktTKeh98TeT127jZY6CFAsurrENoTZCY= +github.com/derailed/popeye v0.11.2/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= github.com/derailed/tview v0.8.2 h1:8b+QwVECV1lZ6VV7Vf1tergpJxJ+ReA/JhIBYyUVSFI= @@ -423,8 +423,8 @@ github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -670,6 +670,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -918,6 +920,8 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -926,10 +930,10 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -1204,8 +1208,8 @@ go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93V go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -1834,8 +1838,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= -helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= +helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= +helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1843,30 +1847,30 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= -k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg= -k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w= -k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= -k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= -k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= +k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= +k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= +k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ= -k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c= -k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk= -k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= +k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/metrics v0.29.0 h1:a6dWcNM+EEowMzMZ8trka6wZtSRIfEA/9oLjuhBksGc= +k8s.io/metrics v0.29.0/go.mod h1:UCuTT4dC/x/x6ODSk87IWIZQnuAfcwxOjb1gjWJdjMA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= @@ -1886,8 +1890,8 @@ sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKU sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/client/client.go b/internal/client/client.go index fc5c9f2877..577c3a3fb5 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -14,7 +14,6 @@ import ( "github.com/rs/zerolog/log" authorizationv1 "k8s.io/api/authorization/v1" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/cache" "k8s.io/apimachinery/pkg/version" @@ -35,8 +34,8 @@ const ( var supportedMetricsAPIVersions = []string{"v1beta1"} -// Namespaces tracks a collection of namespace names. -type Namespaces map[string]struct{} +// NamespaceNames tracks a collection of namespace names. +type NamespaceNames map[string]struct{} // APIClient represents a Kubernetes api client. type APIClient struct { @@ -86,7 +85,7 @@ func (a *APIClient) ConnectionOK() bool { func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { if ns == ClusterScope { - ns = AllNamespaces + ns = BlankNamespace } spec := NewGVR(gvr) res := spec.GVR() @@ -107,9 +106,9 @@ func makeCacheKey(ns, gvr string, vv []string) string { return ns + ":" + gvr + "::" + strings.Join(vv, ",") } -// ActiveCluster returns the current cluster name. -func (a *APIClient) ActiveCluster() string { - c, err := a.config.CurrentClusterName() +// ActiveContext returns the current context name. +func (a *APIClient) ActiveContext() string { + c, err := a.config.CurrentContextName() if err != nil { log.Error().Msgf("Unable to located active cluster") return "" @@ -119,9 +118,10 @@ func (a *APIClient) ActiveCluster() string { // IsActiveNamespace returns true if namespaces matches. func (a *APIClient) IsActiveNamespace(ns string) bool { - if a.ActiveNamespace() == AllNamespaces { + if a.ActiveNamespace() == BlankNamespace { return true } + return a.ActiveNamespace() == ns } @@ -131,7 +131,7 @@ func (a *APIClient) ActiveNamespace() string { return ns } - return AllNamespaces + return BlankNamespace } func (a *APIClient) clearCache() { @@ -149,7 +149,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) return false, errors.New("ACCESS -- No API server connection") } if IsClusterWide(ns) { - ns = AllNamespaces + ns = BlankNamespace } key := makeCacheKey(ns, gvr, verbs) if v, ok := a.cache.Get(key); ok { @@ -212,14 +212,27 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { return info, nil } -// ValidNamespaces returns all available namespaces. -func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { +func (a *APIClient) IsValidNamespace(ns string) bool { + if IsAllNamespace(ns) { + return true + } + nn, err := a.ValidNamespaceNames() + if err != nil { + return false + } + _, ok := nn[ns] + + return ok +} + +// ValidNamespaceNames returns all available namespaces. +func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { if a == nil { return nil, fmt.Errorf("validNamespaces: no available client found") } if nn, ok := a.cache.Get("validNamespaces"); ok { - if nss, ok := nn.([]v1.Namespace); ok { + if nss, ok := nn.(NamespaceNames); ok { return nss, nil } } @@ -233,9 +246,13 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { if err != nil { return nil, err } - a.cache.Add("validNamespaces", nn.Items, cacheExpiry) + nns := make(NamespaceNames, len(nn.Items)) + for _, n := range nn.Items { + nns[n.Name] = struct{}{} + } + a.cache.Add("validNamespaces", nns, cacheExpiry) - return nn.Items, nil + return nns, nil } // CheckConnectivity return true if api server is cool or false otherwise. diff --git a/internal/client/config.go b/internal/client/config.go index 6603850b4a..e7d47885e8 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -10,15 +10,14 @@ import ( "sync" "time" - v1 "k8s.io/api/core/v1" "k8s.io/cli-runtime/pkg/genericclioptions" restclient "k8s.io/client-go/rest" clientcmd "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/tools/clientcmd/api" ) const ( - defaultCallTimeoutDuration time.Duration = 10 * time.Second + defaultCallTimeoutDuration time.Duration = 15 * time.Second // UsePersistentConfig caches client config to avoid reloads. UsePersistentConfig = true @@ -60,7 +59,7 @@ func (c *Config) Flags() *genericclioptions.ConfigFlags { return c.flags } -func (c *Config) RawConfig() (clientcmdapi.Config, error) { +func (c *Config) RawConfig() (api.Config, error) { return c.clientConfig().RawConfig() } @@ -72,11 +71,14 @@ func (c *Config) reset() {} // SwitchContext changes the kubeconfig context to a new cluster. func (c *Config) SwitchContext(name string) error { - if _, err := c.GetContext(name); err != nil { + ct, err := c.GetContext(name) + if err != nil { return fmt.Errorf("context %q does not exist", name) } + // !!BOZO!! Do you need to reset the flags? flags := genericclioptions.NewConfigFlags(UsePersistentConfig) - flags.Context = &name + flags.Context, flags.ClusterName = &name, &ct.Cluster + flags.Namespace = c.flags.Namespace flags.Timeout = c.flags.Timeout flags.KubeConfig = c.flags.KubeConfig c.flags = flags @@ -84,6 +86,22 @@ func (c *Config) SwitchContext(name string) error { return nil } +// CurrentClusterName returns the currently active cluster name. +func (c *Config) CurrentClusterName() (string, error) { + if isSet(c.flags.ClusterName) { + return *c.flags.ClusterName, nil + } + cfg, err := c.RawConfig() + if err != nil { + return "", err + } + + ct := cfg.Contexts[cfg.CurrentContext] + + return ct.Cluster, nil + +} + // CurrentContextName returns the currently active config context. func (c *Config) CurrentContextName() (string, error) { if isSet(c.flags.Context) { @@ -110,8 +128,17 @@ func (c *Config) CurrentContextNamespace() (string, error) { return context.Namespace, nil } +// CurrentContext returns the current context configuration. +func (c *Config) CurrentContext() (*api.Context, error) { + n, err := c.CurrentContextName() + if err != nil { + return nil, err + } + return c.GetContext(n) +} + // GetContext fetch a given context or error if it does not exists. -func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) { +func (c *Config) GetContext(n string) (*api.Context, error) { cfg, err := c.RawConfig() if err != nil { return nil, err @@ -124,7 +151,7 @@ func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) { } // Contexts fetch all available contexts. -func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) { +func (c *Config) Contexts() (map[string]*api.Context, error) { cfg, err := c.RawConfig() if err != nil { return nil, err @@ -180,63 +207,14 @@ func (c *Config) RenameContext(old string, new string) error { } // ContextNames fetch all available contexts. -func (c *Config) ContextNames() ([]string, error) { +func (c *Config) ContextNames() (map[string]struct{}, error) { cfg, err := c.RawConfig() if err != nil { return nil, err } - - cc := make([]string, 0, len(cfg.Contexts)) + cc := make(map[string]struct{}, len(cfg.Contexts)) for n := range cfg.Contexts { - cc = append(cc, n) - } - return cc, nil -} - -// ClusterNameFromContext returns the cluster associated with the given context. -func (c *Config) ClusterNameFromContext(context string) (string, error) { - cfg, err := c.RawConfig() - if err != nil { - return "", err - } - - if ctx, ok := cfg.Contexts[context]; ok { - return ctx.Cluster, nil - } - return "", fmt.Errorf("unable to locate cluster from context %s", context) -} - -// CurrentClusterName returns the active cluster name. -func (c *Config) CurrentClusterName() (string, error) { - if isSet(c.flags.ClusterName) { - return *c.flags.ClusterName, nil - } - cfg, err := c.RawConfig() - if err != nil { - return "", err - } - context, err := c.CurrentContextName() - if err != nil { - context = cfg.CurrentContext - } - - if ctx, ok := cfg.Contexts[context]; ok { - return ctx.Cluster, nil - } - - return "", errors.New("unable to locate current cluster") -} - -// ClusterNames fetch all kubeconfig defined clusters. -func (c *Config) ClusterNames() (map[string]struct{}, error) { - cfg, err := c.RawConfig() - if err != nil { - return nil, err - } - - cc := make(map[string]struct{}, len(cfg.Clusters)) - for name := range cfg.Clusters { - cc[name] = struct{}{} + cc[n] = struct{}{} } return cc, nil @@ -297,16 +275,17 @@ func (c *Config) CurrentUserName() (string, error) { // CurrentNamespaceName retrieves the active namespace. func (c *Config) CurrentNamespaceName() (string, error) { - ns, _, err := c.clientConfig().Namespace() - - if ns == "default" { - ns, err = c.CurrentContextNamespace() - if ns == "" && err == nil { - return "", errors.New("No namespace specified in context") - } + ns, overridden, err := c.clientConfig().Namespace() + if err != nil { + return BlankNamespace, err + } + // Checks if ns is passed is in args. + if overridden { + return ns, nil } - return ns, err + // Return ns set in context if any?? + return c.CurrentContextNamespace() } // ConfigAccess return the current kubeconfig api server access configuration. @@ -320,16 +299,6 @@ func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) { // ---------------------------------------------------------------------------- // Helpers... -// NamespaceNames fetch all available namespaces on current cluster. -func NamespaceNames(nns []v1.Namespace) []string { - nn := make([]string, 0, len(nns)) - for _, ns := range nns { - nn = append(nn, ns.Name) - } - - return nn -} - func isSet(s *string) bool { return s != nil && len(*s) != 0 } diff --git a/internal/client/config_test.go b/internal/client/config_test.go index 37a1029c03..dda1e39ff3 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -5,13 +5,12 @@ package client_test import ( "errors" + "os" "testing" "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -55,15 +54,15 @@ func TestConfigCurrentCluster(t *testing.T) { name, kubeConfig := "blee", "./testdata/config" uu := map[string]struct { flags *genericclioptions.ConfigFlags - cluster string + context string }{ "default": { flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, - cluster: "fred", + context: "fred", }, "custom": { - flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, ClusterName: &name}, - cluster: "blee", + flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Context: &name}, + context: "blee", }, } @@ -71,9 +70,9 @@ func TestConfigCurrentCluster(t *testing.T) { u := uu[k] t.Run(k, func(t *testing.T) { cfg := client.NewConfig(u.flags) - ctx, err := cfg.CurrentClusterName() + ct, err := cfg.CurrentContextName() assert.Nil(t, err) - assert.Equal(t, u.cluster, ctx) + assert.Equal(t, u.context, ct) }) } } @@ -173,8 +172,8 @@ func TestConfigGetContext(t *testing.T) { func TestConfigSwitchContext(t *testing.T) { cluster, kubeConfig := "duh", "./testdata/config" flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, + KubeConfig: &kubeConfig, + Context: &cluster, } cfg := client.NewConfig(&flags) @@ -185,24 +184,11 @@ func TestConfigSwitchContext(t *testing.T) { assert.Equal(t, "blee", ctx) } -func TestConfigClusterNameFromContext(t *testing.T) { - cluster, kubeConfig := "duh", "./testdata/config" - flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, - } - - cfg := client.NewConfig(&flags) - cl, err := cfg.ClusterNameFromContext("blee") - assert.Nil(t, err) - assert.Equal(t, "blee", cl) -} - func TestConfigAccess(t *testing.T) { - cluster, kubeConfig := "duh", "./testdata/config" + context, kubeConfig := "duh", "./testdata/config" flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, + KubeConfig: &kubeConfig, + Context: &context, } cfg := client.NewConfig(&flags) @@ -211,24 +197,11 @@ func TestConfigAccess(t *testing.T) { assert.True(t, len(acc.GetDefaultFilename()) > 0) } -func TestConfigContexts(t *testing.T) { - cluster, kubeConfig := "duh", "./testdata/config" - flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, - } - - cfg := client.NewConfig(&flags) - cc, err := cfg.Contexts() - assert.Nil(t, err) - assert.Equal(t, 3, len(cc)) -} - func TestConfigContextNames(t *testing.T) { cluster, kubeConfig := "duh", "./testdata/config" flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, + KubeConfig: &kubeConfig, + Context: &cluster, } cfg := client.NewConfig(&flags) @@ -237,33 +210,37 @@ func TestConfigContextNames(t *testing.T) { assert.Equal(t, 3, len(cc)) } -func TestConfigClusterNames(t *testing.T) { - cluster, kubeConfig := "duh", "./testdata/config" +func TestConfigContexts(t *testing.T) { + context, kubeConfig := "duh", "./testdata/config" flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, + KubeConfig: &kubeConfig, + Context: &context, } cfg := client.NewConfig(&flags) - cc, err := cfg.ClusterNames() + cc, err := cfg.Contexts() assert.Nil(t, err) assert.Equal(t, 3, len(cc)) } func TestConfigDelContext(t *testing.T) { - cluster, kubeConfig := "duh", "./testdata/config.1" + assert.NoError(t, cp("./testdata/config.2", "./testdata/config.1")) + + context, kubeConfig := "duh", "./testdata/config.1" flags := genericclioptions.ConfigFlags{ - KubeConfig: &kubeConfig, - ClusterName: &cluster, + KubeConfig: &kubeConfig, + Context: &context, } cfg := client.NewConfig(&flags) err := cfg.DelContext("fred") - assert.Nil(t, err) + assert.NoError(t, err) + cc, err := cfg.ContextNames() - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, 1, len(cc)) - assert.Equal(t, "blee", cc[0]) + _, ok := cc["blee"] + assert.True(t, ok) } func TestConfigRestConfig(t *testing.T) { @@ -289,13 +266,13 @@ func TestConfigBadConfig(t *testing.T) { assert.NotNil(t, err) } -func TestNamespaceNames(t *testing.T) { - nn := []v1.Namespace{ - {ObjectMeta: metav1.ObjectMeta{Name: "ns1"}}, - {ObjectMeta: metav1.ObjectMeta{Name: "ns2"}}, +// Helpers... + +func cp(src string, dst string) error { + data, err := os.ReadFile(src) + if err != nil { + return err } - nns := client.NamespaceNames(nn) - assert.Equal(t, 2, len(nns)) - assert.Equal(t, []string{"ns1", "ns2"}, nns) + return os.WriteFile(dst, data, 0600) } diff --git a/internal/client/gvr.go b/internal/client/gvr.go index 715e274dd7..fabb81f721 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -14,6 +14,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +var NoGVR = GVR{} + // GVR represents a kubernetes resource schema as a string. // Format is group/version/resources:subresource. type GVR struct { diff --git a/internal/client/gvr_test.go b/internal/client/gvr_test.go index 714f1f454f..744ef26773 100644 --- a/internal/client/gvr_test.go +++ b/internal/client/gvr_test.go @@ -49,7 +49,7 @@ func TestGVRCan(t *testing.T) { } } -func TestAsGVR(t *testing.T) { +func TestGVR(t *testing.T) { uu := map[string]struct { gvr string e schema.GroupVersionResource diff --git a/internal/client/helpers.go b/internal/client/helpers.go index 68006ed132..204da6a547 100644 --- a/internal/client/helpers.go +++ b/internal/client/helpers.go @@ -17,13 +17,13 @@ var toFileName = regexp.MustCompile(`[^(\w/\.)]`) // IsClusterWide returns true if ns designates cluster scope, false otherwise. func IsClusterWide(ns string) bool { - return ns == NamespaceAll || ns == AllNamespaces || ns == ClusterScope + return ns == NamespaceAll || ns == BlankNamespace || ns == ClusterScope } // CleanseNamespace ensures all ns maps to blank. func CleanseNamespace(ns string) string { if IsAllNamespace(ns) { - return AllNamespaces + return BlankNamespace } return ns @@ -36,7 +36,7 @@ func IsAllNamespace(ns string) bool { // IsAllNamespaces returns true if all namespaces, false otherwise. func IsAllNamespaces(ns string) bool { - return ns == NamespaceAll || ns == AllNamespaces + return ns == NamespaceAll || ns == BlankNamespace } // IsNamespaced returns true if a specific ns is given. diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 8ba7fd3568..fe2e90baf1 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -20,6 +20,8 @@ import ( const ( mxCacheSize = 100 mxCacheExpiry = 1 * time.Minute + podMXGVR = "metrics.k8s.io/v1beta1/pods" + nodeMXGVR = "metrics.k8s.io/v1beta1/nodes" ) // MetricsDial tracks global metric server handle. @@ -149,7 +151,7 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe const msg = "user is not authorized to list node metrics" mx := new(mv1beta1.NodeMetricsList) - if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil { + if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil { return mx, err } @@ -180,7 +182,7 @@ func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1bet const msg = "user is not authorized to list node metrics" mx := new(mv1beta1.NodeMetrics) - if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil { + if err := m.checkAccess(ClusterScope, nodeMXGVR, msg); err != nil { return mx, err } @@ -218,9 +220,9 @@ func (m *MetricsServer) FetchPodsMetrics(ctx context.Context, ns string) (*mv1be const msg = "user is not authorized to list pods metrics" if ns == NamespaceAll { - ns = AllNamespaces + ns = BlankNamespace } - if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil { + if err := m.checkAccess(ns, podMXGVR, msg); err != nil { return mx, err } @@ -269,9 +271,9 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be ns, _ := Namespaced(fqn) if ns == NamespaceAll { - ns = AllNamespaces + ns = BlankNamespace } - if err := m.checkAccess(ns, "metrics.k8s.io/v1beta1/pods", msg); err != nil { + if err := m.checkAccess(ns, podMXGVR, msg); err != nil { return mx, err } diff --git a/internal/client/testdata/config.2 b/internal/client/testdata/config.2 new file mode 100644 index 0000000000..efcf664750 --- /dev/null +++ b/internal/client/testdata/config.2 @@ -0,0 +1,23 @@ +apiVersion: v1 +clusters: +- cluster: + insecure-skip-tls-verify: true + server: https://localhost:3001 + name: blee +- cluster: + insecure-skip-tls-verify: true + server: https://localhost:3002 + name: fred +contexts: +- context: + cluster: blee + user: blee + name: blee +- context: + cluster: fred + user: fred + name: fred +current-context: blee +kind: Config +preferences: {} +users: null diff --git a/internal/client/types.go b/internal/client/types.go index fd814572da..24d66aad69 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -4,7 +4,6 @@ package client import ( - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" @@ -21,8 +20,8 @@ const ( // NamespaceAll designates the fictional all namespace. NamespaceAll = "all" - // AllNamespaces designates all namespaces. - AllNamespaces = "" + // BlankNamespace designates no namespace. + BlankNamespace = "" // DefaultNamespace designates the default namespace. DefaultNamespace = "default" @@ -118,8 +117,11 @@ type Connection interface { // HasMetrics checks if metrics server is available. HasMetrics() bool - // ValidNamespaces returns all available namespaces. - ValidNamespaces() ([]v1.Namespace, error) + // ValidNamespaces returns all available namespace names. + ValidNamespaceNames() (NamespaceNames, error) + + // IsValidNamespace checks if given namespace is known. + IsValidNamespace(string) bool // ServerVersion returns current server version. ServerVersion() (*version.Info, error) @@ -127,8 +129,8 @@ type Connection interface { // CheckConnectivity checks if api server connection is happy or not. CheckConnectivity() bool - // ActiveCluster returns the current cluster name. - ActiveCluster() string + // ActiveContext returns the current context name. + ActiveContext() string // ActiveNamespace returns the current namespace. ActiveNamespace() string diff --git a/internal/config/alias.go b/internal/config/alias.go index f8900a018a..6ce8365a3c 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -5,16 +5,13 @@ package config import ( "os" - "path/filepath" "sync" + "github.com/derailed/k9s/internal/config/data" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" ) -// K9sAlias manages K9s aliases. -var K9sAlias = YamlExtension(filepath.Join(K9sHome(), "alias.yml")) - // Alias tracks shortname to GVR mappings. type Alias map[string]string @@ -23,7 +20,7 @@ type ShortNames map[string][]string // Aliases represents a collection of aliases. type Aliases struct { - Alias Alias `yaml:"alias"` + Alias Alias `yaml:"aliases"` mx sync.RWMutex } @@ -101,13 +98,28 @@ func (a *Aliases) Define(gvr string, aliases ...string) { } // Load K9s aliases. -func (a *Aliases) Load() error { +func (a *Aliases) Load(path string) error { a.loadDefaultAliases() - return a.LoadFileAliases(K9sAlias) + + f, err := EnsureAliasesCfgFile() + if err != nil { + log.Error().Err(err).Msgf("Unable to gen config aliases") + } + + // load global alias file + if err := a.LoadFile(f); err != nil { + return err + } + + // load context specific aliases if any + return a.LoadFile(path) } -// LoadFileAliases loads alias from a given file. -func (a *Aliases) LoadFileAliases(path string) error { +// LoadFile loads alias from a given file. +func (a *Aliases) LoadFile(path string) error { + if path == "" { + return nil + } f, err := os.ReadFile(path) if err == nil { var aa Aliases @@ -136,15 +148,6 @@ func (a *Aliases) loadDefaultAliases() { a.mx.Lock() defer a.mx.Unlock() - a.Alias["dp"] = "apps/v1/deployments" - a.Alias["sec"] = "v1/secrets" - a.Alias["jo"] = "batch/v1/jobs" - a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles" - a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings" - a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles" - a.Alias["rb"] = "rbac.authorization.k8s.io/v1/rolebindings" - a.Alias["np"] = "networking.k8s.io/v1/networkpolicies" - a.declare("help", "h", "?") a.declare("quit", "q", "q!", "qa", "Q") a.declare("aliases", "alias", "a") @@ -155,21 +158,22 @@ func (a *Aliases) loadDefaultAliases() { a.declare("users", "user", "usr") a.declare("groups", "group", "grp") a.declare("portforwards", "portforward", "pf") - a.declare("benchmarks", "bench", "benchmark", "be") + a.declare("benchmarks", "benchmark", "bench") a.declare("screendumps", "screendump", "sd") a.declare("pulses", "pulse", "pu", "hz") a.declare("xrays", "xray", "x") + a.declare("workloads", "workload", "wk") } // Save alias to disk. func (a *Aliases) Save() error { log.Debug().Msg("[Config] Saving Aliases...") - return a.SaveAliases(K9sAlias) + return a.SaveAliases(AppAliasesFile) } // SaveAliases saves aliases to a given file. func (a *Aliases) SaveAliases(path string) error { - if err := EnsureDirPath(path, DefaultDirMod); err != nil { + if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil { return err } cfg, err := yaml.Marshal(a) diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index 3d40bc41a2..f65ddb9993 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -75,7 +75,7 @@ func TestAliasDefine(t *testing.T) { func TestAliasesLoad(t *testing.T) { a := config.NewAliases() - assert.Nil(t, a.LoadFileAliases("testdata/alias.yml")) + assert.Nil(t, a.LoadFile("testdata/alias.yaml")) assert.Equal(t, 2, len(a.Alias)) } @@ -84,7 +84,7 @@ func TestAliasesSave(t *testing.T) { a.Alias["test"] = "fred" a.Alias["blee"] = "duh" - assert.Nil(t, a.SaveAliases("/tmp/a.yml")) - assert.Nil(t, a.LoadFileAliases("/tmp/a.yml")) + assert.Nil(t, a.SaveAliases("/tmp/a.yaml")) + assert.Nil(t, a.LoadFile("/tmp/a.yaml")) assert.Equal(t, 2, len(a.Alias)) } diff --git a/internal/config/bench.go b/internal/config/benchmark.go similarity index 100% rename from internal/config/bench.go rename to internal/config/benchmark.go index b837b361fc..329c094011 100644 --- a/internal/config/bench.go +++ b/internal/config/benchmark.go @@ -67,6 +67,18 @@ const ( DefaultMethod = "GET" ) +// DefaultBenchSpec returns a default bench spec. +func DefaultBenchSpec() BenchConfig { + return BenchConfig{ + C: DefaultC, + N: DefaultN, + HTTP: HTTP{ + Method: DefaultMethod, + Path: "/", + }, + } +} + func newBenchmark() Benchmark { return Benchmark{ C: DefaultC, @@ -106,15 +118,3 @@ func (s *Bench) load(path string) error { return yaml.Unmarshal(f, &s) } - -// DefaultBenchSpec returns a default bench spec. -func DefaultBenchSpec() BenchConfig { - return BenchConfig{ - C: DefaultC, - N: DefaultN, - HTTP: HTTP{ - Method: DefaultMethod, - Path: "/", - }, - } -} diff --git a/internal/config/bench_test.go b/internal/config/benchmark_test.go similarity index 92% rename from internal/config/bench_test.go rename to internal/config/benchmark_test.go index d6c225db5e..7a4b54caa8 100644 --- a/internal/config/bench_test.go +++ b/internal/config/benchmark_test.go @@ -35,14 +35,14 @@ func TestBenchLoad(t *testing.T) { coCount int }{ "goodConfig": { - "testdata/b_good.yml", + "testdata/b_good.yaml", 2, 1000, 2, 0, }, "malformed": { - "testdata/b_toast.yml", + "testdata/b_toast.yaml", 1, 200, 0, @@ -103,7 +103,7 @@ func TestBenchServiceLoad(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - b, err := NewBench("testdata/b_good.yml") + b, err := NewBench("testdata/b_good.yaml") assert.Nil(t, err) assert.Equal(t, 2, len(b.Benchmarks.Services)) @@ -122,16 +122,16 @@ func TestBenchServiceLoad(t *testing.T) { } func TestBenchReLoad(t *testing.T) { - b, err := NewBench("testdata/b_containers.yml") + b, err := NewBench("testdata/b_containers.yaml") assert.Nil(t, err) assert.Equal(t, 2, b.Benchmarks.Defaults.C) - assert.Nil(t, b.Reload("testdata/b_containers_1.yml")) + assert.Nil(t, b.Reload("testdata/b_containers_1.yaml")) assert.Equal(t, 20, b.Benchmarks.Defaults.C) } func TestBenchLoadToast(t *testing.T) { - _, err := NewBench("testdata/toast.yml") + _, err := NewBench("testdata/toast.yaml") assert.NotNil(t, err) } @@ -174,7 +174,7 @@ func TestBenchContainerLoad(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - b, err := NewBench("testdata/b_containers.yml") + b, err := NewBench("testdata/b_containers.yaml") assert.Nil(t, err) assert.Equal(t, 2, len(b.Benchmarks.Services)) diff --git a/internal/config/cluster.go b/internal/config/cluster.go deleted file mode 100644 index 1fe5dccf9b..0000000000 --- a/internal/config/cluster.go +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config - -import "github.com/derailed/k9s/internal/client" - -// DefaultPFAddress specifies the default PortForward host address. -const DefaultPFAddress = "localhost" - -// Cluster tracks K9s cluster configuration. -type Cluster struct { - Namespace *Namespace `yaml:"namespace"` - View *View `yaml:"view"` - Skin string `yaml:"skin,omitempty"` - FeatureGates *FeatureGates `yaml:"featureGates"` - PortForwardAddress string `yaml:"portForwardAddress"` -} - -// NewCluster creates a new cluster configuration. -func NewCluster() *Cluster { - return &Cluster{ - Namespace: NewNamespace(), - View: NewView(), - PortForwardAddress: DefaultPFAddress, - FeatureGates: NewFeatureGates(), - } -} - -// Validate a cluster config. -func (c *Cluster) Validate(conn client.Connection, ks KubeSettings) { - if c.PortForwardAddress == "" { - c.PortForwardAddress = DefaultPFAddress - } - - if c.Namespace == nil { - c.Namespace = NewNamespace() - } - if c.Namespace.Active == client.AllNamespaces { - c.Namespace.Active = client.NamespaceAll - } - - if c.FeatureGates == nil { - c.FeatureGates = NewFeatureGates() - } - - if c.View == nil { - c.View = NewView() - } - c.View.Validate() -} diff --git a/internal/config/cluster_test.go b/internal/config/cluster_test.go deleted file mode 100644 index 86f4957886..0000000000 --- a/internal/config/cluster_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/config" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestClusterValidate(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) - - c := config.NewCluster() - c.Validate(mc, mk) - - assert.Equal(t, "po", c.View.Active) - assert.Equal(t, "default", c.Namespace.Active) - assert.Equal(t, 1, len(c.Namespace.Favorites)) - assert.Equal(t, []string{"default"}, c.Namespace.Favorites) -} - -func TestClusterValidateEmpty(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) - - var c config.Cluster - c.Validate(mc, mk) - - assert.Equal(t, "po", c.View.Active) - assert.Equal(t, "default", c.Namespace.Active) - assert.Equal(t, 1, len(c.Namespace.Favorites)) - assert.Equal(t, []string{"default"}, c.Namespace.Favorites) -} - -func namespaces() []v1.Namespace { - return []v1.Namespace{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "fred", - }, - }, - } -} diff --git a/internal/config/config.go b/internal/config/config.go index f7674c7777..d4e8e81b7a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,7 +4,6 @@ package config import ( - "errors" "fmt" "os" "path/filepath" @@ -12,56 +11,26 @@ import ( "github.com/adrg/xdg" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config/data" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/cli-runtime/pkg/genericclioptions" ) -// K9sConfig represents K9s configuration dir env var. -const K9sConfig = "K9SCONFIG" - -var ( - // K9sConfigFile represents K9s config file location. - K9sConfigFile = filepath.Join(K9sHome(), "config.yml") - - // K9sSkinDir represent K9s skin dir - K9sSkinDir = filepath.Join(K9sHome(), "skins") - - // K9sDefaultScreenDumpDir represents a default directory where K9s screen dumps will be persisted. - K9sDefaultScreenDumpDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-screens-%s", MustK9sUser())) -) - -type ( - // KubeSettings exposes kubeconfig context information. - KubeSettings interface { - // CurrentContextName returns the name of the current context. - CurrentContextName() (string, error) - - // CurrentClusterName returns the name of the current cluster. - CurrentClusterName() (string, error) - - // CurrentNamespace returns the name of the current namespace. - CurrentNamespaceName() (string, error) - - // ClusterNames() returns all available cluster names. - ClusterNames() (map[string]struct{}, error) - } - - // Config tracks K9s configuration options. - Config struct { - K9s *K9s `yaml:"k9s"` - client client.Connection - settings KubeSettings - } -) +// Config tracks K9s configuration options. +type Config struct { + K9s *K9s `yaml:"k9s"` + conn client.Connection + settings data.KubeSettings +} // K9sHome returns k9s configs home directory. func K9sHome() string { - if env := os.Getenv(K9sConfig); env != "" { + if env := os.Getenv(K9sConfigDir); env != "" { return env } - xdgK9sHome, err := xdg.ConfigFile("k9s") + xdgK9sHome, err := xdg.ConfigFile(AppName) if err != nil { log.Fatal().Err(err).Msg("Unable to create configuration directory for k9s") } @@ -70,35 +39,50 @@ func K9sHome() string { } // NewConfig creates a new default config. -func NewConfig(ks KubeSettings) *Config { - return &Config{K9s: NewK9s(), settings: ks} +func NewConfig(ks data.KubeSettings) *Config { + return &Config{ + settings: ks, + K9s: NewK9s(nil, ks), + } +} + +// ContextAliasesPath returns a context specific aliases file spec. +func (c *Config) ContextAliasesPath() string { + ct, err := c.K9s.ActiveContext() + if err != nil { + return "" + } + + return AppContextAliasesFile(ct.ClusterName, c.K9s.activeContextName) +} + +// ContextPluginsPath returns a context specific plugins file spec. +func (c *Config) ContextPluginsPath() string { + ct, err := c.K9s.ActiveContext() + if err != nil { + return "" + } + + return AppContextPluginsFile(ct.ClusterName, c.K9s.activeContextName) } // Refine the configuration based on cli args. func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error { if isSet(flags.Context) { - c.K9s.CurrentContext = *flags.Context + if _, err := c.K9s.ActivateContext(*flags.Context); err != nil { + return err + } } else { - context, err := cfg.CurrentContextName() + n, err := cfg.CurrentContextName() + if err != nil { + return err + } + _, err = c.K9s.ActivateContext(n) if err != nil { return err } - c.K9s.CurrentContext = context - } - log.Debug().Msgf("Active Context %q", c.K9s.CurrentContext) - if c.K9s.CurrentContext == "" { - return errors.New("Invalid kubeconfig context detected") - } - cc, err := cfg.Contexts() - if err != nil { - return err - } - context, ok := cc[c.K9s.CurrentContext] - if !ok { - return fmt.Errorf("the specified context %q does not exists in kubeconfig", c.K9s.CurrentContext) } - c.K9s.CurrentCluster = context.Cluster - c.K9s.ActivateCluster(context.Namespace) + log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) var ns = client.DefaultNamespace switch { @@ -107,96 +91,87 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c case isSet(flags.Namespace): ns = *flags.Namespace default: - if nss := context.Namespace; nss != "" { - ns = nss - } else if nss == "" { - ns = c.K9s.ActiveCluster().Namespace.Active + nss, err := c.K9s.ActiveContextNamespace() + if err != nil { + return err } + ns = nss } - if err := c.SetActiveNamespace(ns); err != nil { return err } flags.Namespace = &ns - if isSet(flags.ClusterName) { - c.K9s.CurrentCluster = *flags.ClusterName - } - - return EnsureDirPath(c.K9s.GetScreenDumpDir(), DefaultDirMod) + return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod) } -// Reset the context to the new current context/cluster. -// if it does not exist. +// Reset resets the context to the new current context/cluster. func (c *Config) Reset() { - c.K9s.CurrentContext, c.K9s.CurrentCluster = "", "" + c.K9s.Reset() } -// CurrentCluster fetch the configuration activeCluster. -func (c *Config) CurrentCluster() *Cluster { - if c, ok := c.K9s.Clusters[c.K9s.CurrentCluster]; ok { - return c +func (c *Config) SetCurrentContext(n string) (*data.Context, error) { + ct, err := c.K9s.ActivateContext(n) + if err != nil { + return nil, fmt.Errorf("set current context %q failed: %w", n, err) } - return nil + + return ct, nil +} + +// CurrentContext fetch the configuration active context. +func (c *Config) CurrentContext() (*data.Context, error) { + return c.K9s.ActiveContext() } -// ActiveNamespace returns the active namespace in the current cluster. +// ActiveNamespace returns the active namespace in the current context. +// If none found return the empty ns. func (c *Config) ActiveNamespace() string { - if c.K9s.Clusters == nil { - log.Warn().Msgf("No context detected returning default namespace") - return "default" - } - cl := c.CurrentCluster() - if cl != nil && cl.Namespace != nil { - return cl.Namespace.Active - } - if cl == nil { - cl = NewCluster() - c.K9s.Clusters[c.K9s.CurrentCluster] = cl - } - if ns, err := c.settings.CurrentNamespaceName(); err == nil && ns != "" { - if cl.Namespace == nil { - cl.Namespace = NewNamespace() - } - cl.Namespace.Active = ns - return ns + ns, err := c.K9s.ActiveContextNamespace() + if err != nil { + log.Error().Err(err).Msgf("Unable to assert active namespace. Using default") + ns = client.DefaultNamespace } - return "default" + return ns } // ValidateFavorites ensure favorite ns are legit. func (c *Config) ValidateFavorites() { - cl := c.K9s.ActiveCluster() - cl.Validate(c.client, c.settings) - cl.Namespace.Validate(c.client, c.settings) + ct, err := c.K9s.ActiveContext() + if err == nil { + ct.Validate(c.conn, c.settings) + ct.Namespace.Validate(c.conn, c.settings) + } } -// FavNamespaces returns fav namespaces in the current cluster. +// FavNamespaces returns fav namespaces in the current context. func (c *Config) FavNamespaces() []string { - cl := c.K9s.ActiveCluster() + ct, err := c.K9s.ActiveContext() + if err != nil { + return nil + } - return cl.Namespace.Favorites + return ct.Namespace.Favorites } -// SetActiveNamespace set the active namespace in the current cluster. +// SetActiveNamespace set the active namespace in the current context. func (c *Config) SetActiveNamespace(ns string) error { - if cl := c.K9s.ActiveCluster(); cl != nil { - return cl.Namespace.SetActive(ns, c.settings) + ct, err := c.K9s.ActiveContext() + if err != nil { + return err } - err := errors.New("no active cluster. unable to set active namespace") - log.Error().Err(err).Msg("SetActiveNamespace") - return err + return ct.Namespace.SetActive(ns, c.settings) } -// ActiveView returns the active view in the current cluster. +// ActiveView returns the active view in the current context. func (c *Config) ActiveView() string { - cl := c.K9s.ActiveCluster() - if cl == nil { - return defaultView + ct, err := c.K9s.ActiveContext() + if err != nil { + return data.DefaultView } - cmd := cl.View.Active + cmd := ct.View.Active if c.K9s.manualCommand != nil && *c.K9s.manualCommand != "" { cmd = *c.K9s.manualCommand // We reset the manualCommand property because @@ -208,37 +183,41 @@ func (c *Config) ActiveView() string { return cmd } -// SetActiveView set the currently cluster active view. +// SetActiveView sets current context active view. func (c *Config) SetActiveView(view string) { - if cl := c.K9s.ActiveCluster(); cl != nil { - cl.View.Active = view + if ct, err := c.K9s.ActiveContext(); err == nil { + ct.View.Active = view } } // GetConnection return an api server connection. func (c *Config) GetConnection() client.Connection { - return c.client + return c.conn } // SetConnection set an api server connection. func (c *Config) SetConnection(conn client.Connection) { - c.client = conn + c.conn, c.K9s.conn = conn, conn + c.Validate() +} + +func (c *Config) ActiveContextName() string { + return c.K9s.activeContextName } -// Load K9s configuration from file. +// Load loads K9s configuration from file. func (c *Config) Load(path string) error { f, err := os.ReadFile(path) if err != nil { return err } - c.K9s = NewK9s() var cfg Config if err := yaml.Unmarshal(f, &cfg); err != nil { return err } if cfg.K9s != nil { - c.K9s = cfg.K9s + c.K9s.Refine(cfg.K9s) } if c.K9s.Logger == nil { c.K9s.Logger = NewLogger() @@ -249,13 +228,15 @@ func (c *Config) Load(path string) error { // Save configuration to disk. func (c *Config) Save() error { c.Validate() - - return c.SaveFile(K9sConfigFile) + if err := c.K9s.Save(); err != nil { + return err + } + return c.SaveFile(AppConfigFile) } // SaveFile K9s configuration to disk. func (c *Config) SaveFile(path string) error { - if err := EnsureDirPath(path, DefaultDirMod); err != nil { + if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil { return err } cfg, err := yaml.Marshal(c) @@ -268,14 +249,14 @@ func (c *Config) SaveFile(path string) error { // Validate the configuration. func (c *Config) Validate() { - c.K9s.Validate(c.client, c.settings) + c.K9s.Validate(c.conn, c.settings) } // Dump debug... func (c *Config) Dump(msg string) { - log.Debug().Msgf("Current Cluster: %s\n", c.K9s.CurrentCluster) - for k, cl := range c.K9s.Clusters { - log.Debug().Msgf("K9s cluster: %s -- %+v\n", k, cl.Namespace) + ct, err := c.K9s.ActiveContext() + if err != nil { + log.Debug().Msgf("Current Contexts: %s\n", ct.ClusterName) } } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index bef14aeab5..e29d9f7add 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" m "github.com/petergtz/pegomock" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -22,19 +22,16 @@ func init() { } func TestConfigRefine(t *testing.T) { - cfgFile, ctx, cluster, ns := "testdata/kubeconfig-test.yml", "test2", "cluster2", "ns2" + var ( + cfgFile = "testdata/kubeconfig-test.yaml" + ctx, cluster, ns = "ct-1-1", "cl-1", "ns-1" + ) + uu := map[string]struct { flags *genericclioptions.ConfigFlags issue bool context, cluster, namespace string }{ - "plain": { - flags: &genericclioptions.ConfigFlags{KubeConfig: &cfgFile}, - issue: false, - context: "test1", - cluster: "cluster1", - namespace: "ns1", - }, "overrideNS": { flags: &genericclioptions.ConfigFlags{ KubeConfig: &cfgFile, @@ -61,18 +58,14 @@ func TestConfigRefine(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - mk := newMockSettings(u.flags) - cfg := config.NewConfig(mk) + cfg := mock.NewMockConfig() err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags)) if u.issue { assert.NotNil(t, err) } else { assert.Nil(t, err) - assert.Equal(t, u.context, cfg.K9s.CurrentContext) - assert.Equal(t, u.cluster, cfg.K9s.CurrentCluster) + assert.Equal(t, u.context, cfg.K9s.ActiveContextName()) assert.Equal(t, u.namespace, cfg.ActiveNamespace()) } }) @@ -80,167 +73,60 @@ func TestConfigRefine(t *testing.T) { } func TestConfigValidate(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) + cfg := mock.NewMockConfig() + cfg.SetConnection(mock.NewMockConnection()) - cfg := config.NewConfig(mk) - cfg.SetConnection(mc) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) cfg.Validate() } func TestConfigLoad(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) + cfg := mock.NewMockConfig() + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) assert.Equal(t, 2, cfg.K9s.RefreshRate) assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize) assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount) - assert.Equal(t, "minikube", cfg.K9s.CurrentContext) - assert.Equal(t, "minikube", cfg.K9s.CurrentCluster) - assert.NotNil(t, cfg.K9s.Clusters) - assert.Equal(t, 2, len(cfg.K9s.Clusters)) - - nn := []string{ - "default", - "kube-public", - "istio-system", - "all", - "kube-system", - } - - assert.Equal(t, "kube-system", cfg.K9s.Clusters["minikube"].Namespace.Active) - assert.Equal(t, nn, cfg.K9s.Clusters["minikube"].Namespace.Favorites) - assert.Equal(t, "ctx", cfg.K9s.Clusters["minikube"].View.Active) -} - -func TestConfigCurrentCluster(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - assert.NotNil(t, cfg.CurrentCluster()) - assert.Equal(t, "kube-system", cfg.CurrentCluster().Namespace.Active) - assert.Equal(t, "ctx", cfg.CurrentCluster().View.Active) -} - -func TestConfigActiveNamespace(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - assert.Equal(t, "kube-system", cfg.ActiveNamespace()) -} - -func TestConfigActiveNamespaceBlank(t *testing.T) { - cfg := config.Config{K9s: new(config.K9s)} - assert.Equal(t, "default", cfg.ActiveNamespace()) -} - -func TestConfigSetActiveNamespace(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - assert.Nil(t, cfg.SetActiveNamespace("default")) - assert.Equal(t, "default", cfg.ActiveNamespace()) -} - -func TestConfigActiveView(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - assert.Equal(t, "ctx", cfg.ActiveView()) -} - -func TestConfigActiveViewBlank(t *testing.T) { - cfg := config.Config{K9s: new(config.K9s)} - assert.Equal(t, "po", cfg.ActiveView()) -} - -func TestConfigSetActiveView(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - cfg.SetActiveView("po") - assert.Equal(t, "po", cfg.ActiveView()) -} - -func TestConfigFavNamespaces(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"} - assert.Equal(t, expectedNS, cfg.FavNamespaces()) } func TestConfigLoadOldCfg(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s_old.yml")) + cfg := mock.NewMockConfig() + + assert.Nil(t, cfg.Load("testdata/k9s_old.yaml")) } func TestConfigLoadCrap(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yml")) + cfg := mock.NewMockConfig() + + assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yaml")) } func TestConfigSaveFile(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) + cfg := mock.NewMockConfig() - mk := NewMockKubeSettings() - m.When(mk.CurrentContextName()).ThenReturn("minikube", nil) - m.When(mk.CurrentClusterName()).ThenReturn("minikube", nil) - m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) - m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"minikube": {}, "fred": {}, "blee": {}}, nil) - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) - cfg := config.NewConfig(mk) - cfg.SetConnection(mc) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) cfg.K9s.RefreshRate = 100 cfg.K9s.ReadOnly = true cfg.K9s.Logger.TailCount = 500 cfg.K9s.Logger.BufferSize = 800 - cfg.K9s.CurrentContext = "blee" - cfg.K9s.CurrentCluster = "blee" cfg.Validate() - path := filepath.Join("/tmp", "k9s.yml") + + path := filepath.Join("/tmp", "k9s.yaml") err := cfg.SaveFile(path) assert.Nil(t, err) - raw, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, expectedConfig, string(raw)) } func TestConfigReset(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.CurrentContextName()).ThenReturn("blee", nil) - m.When(mk.CurrentClusterName()).ThenReturn("blee", nil) - m.When(mk.CurrentNamespaceName()).ThenReturn("default", nil) - m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"blee": {}}, nil) - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) - - cfg := config.NewConfig(mk) - cfg.SetConnection(mc) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) + cfg := mock.NewMockConfig() + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) cfg.Reset() cfg.Validate() - path := filepath.Join("/tmp", "k9s.yml") + path := filepath.Join("/tmp", "k9s.yaml") err := cfg.SaveFile(path) assert.Nil(t, err) @@ -258,46 +144,35 @@ func TestSetup(t *testing.T) { }) } -type mockSettings struct { - flags *genericclioptions.ConfigFlags -} - -var _ config.KubeSettings = (*mockSettings)(nil) - -func newMockSettings(flags *genericclioptions.ConfigFlags) *mockSettings { - return &mockSettings{flags: flags} -} -func (m *mockSettings) CurrentContextName() (string, error) { - return *m.flags.Context, nil -} -func (m *mockSettings) CurrentClusterName() (string, error) { return "", nil } -func (m *mockSettings) CurrentNamespaceName() (string, error) { - return *m.flags.Namespace, nil -} -func (m *mockSettings) ClusterNames() (map[string]struct{}, error) { return nil, nil } - // ---------------------------------------------------------------------------- // Test Data... var expectedConfig = `k9s: liveViewAutoRefresh: true + screenDumpDir: /tmp refreshRate: 100 maxConnRetry: 5 - enableMouse: false - enableImageScan: false - headless: false - logoless: false - crumbsless: false readOnly: true noExitOnCtrlC: false - noIcons: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false shellPod: image: busybox:1.35.0 namespace: default limits: cpu: 100m memory: 100Mi - skipLatestRevCheck: false + imageScans: + enable: false + blackList: + namespaces: [] + labels: {} logger: tail: 500 buffer: 800 @@ -305,51 +180,6 @@ var expectedConfig = `k9s: fullScreenLogs: false textWrap: false showTime: false - currentContext: blee - currentCluster: blee - keepMissingClusters: false - clusters: - blee: - namespace: - active: default - lockFavorites: false - favorites: - - default - view: - active: po - featureGates: - nodeShell: false - portForwardAddress: localhost - fred: - namespace: - active: default - lockFavorites: false - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: po - featureGates: - nodeShell: false - portForwardAddress: localhost - minikube: - namespace: - active: kube-system - lockFavorites: false - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: ctx - featureGates: - nodeShell: false - portForwardAddress: localhost thresholds: cpu: critical: 90 @@ -357,29 +187,34 @@ var expectedConfig = `k9s: memory: critical: 90 warn: 70 - screenDumpDir: /tmp - disablePodCounting: false ` var resetConfig = `k9s: liveViewAutoRefresh: true + screenDumpDir: /tmp refreshRate: 2 maxConnRetry: 5 - enableMouse: false - enableImageScan: false - headless: false - logoless: false - crumbsless: false readOnly: false noExitOnCtrlC: false - noIcons: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false shellPod: image: busybox:1.35.0 namespace: default limits: cpu: 100m memory: 100Mi - skipLatestRevCheck: false + imageScans: + enable: false + blackList: + namespaces: [] + labels: {} logger: tail: 200 buffer: 2000 @@ -387,21 +222,6 @@ var resetConfig = `k9s: fullScreenLogs: false textWrap: false showTime: false - currentContext: blee - currentCluster: blee - keepMissingClusters: false - clusters: - blee: - namespace: - active: default - lockFavorites: false - favorites: - - default - view: - active: po - featureGates: - nodeShell: false - portForwardAddress: localhost thresholds: cpu: critical: 90 @@ -409,6 +229,4 @@ var resetConfig = `k9s: memory: critical: 90 warn: 70 - screenDumpDir: /tmp - disablePodCounting: false ` diff --git a/internal/config/data/config.go b/internal/config/data/config.go new file mode 100644 index 0000000000..3faf5fcc27 --- /dev/null +++ b/internal/config/data/config.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "fmt" + "io" + "os" + + "github.com/derailed/k9s/internal/client" + "gopkg.in/yaml.v2" + "k8s.io/client-go/tools/clientcmd/api" +) + +// Config tracks a context configuration. +type Config struct { + Context *Context `yaml:"k9s"` +} + +func NewConfig(ct *api.Context) *Config { + return &Config{ + Context: NewContextFromConfig(ct), + } +} + +func (c *Config) Validate(conn client.Connection, ks KubeSettings) { + c.Context.Validate(conn, ks) +} + +func (c *Config) Dump(w io.Writer) { + bb, _ := yaml.Marshal(&c) + + fmt.Fprintf(w, "%s\n", string(bb)) +} + +func (c *Config) Save(path string) error { + if err := EnsureDirPath(path, DefaultDirMod); err != nil { + return err + } + + cfg, err := yaml.Marshal(c) + if err != nil { + return err + } + + return os.WriteFile(path, cfg, DefaultFileMod) +} diff --git a/internal/config/data/context.go b/internal/config/data/context.go new file mode 100644 index 0000000000..e08de8ffa2 --- /dev/null +++ b/internal/config/data/context.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "github.com/derailed/k9s/internal/client" + "k8s.io/client-go/tools/clientcmd/api" +) + +// DefaultPFAddress specifies the default PortForward host address. +const DefaultPFAddress = "localhost" + +// Context tracks K9s context configuration. +type Context struct { + ClusterName string `yaml:"cluster,omitempty"` + ReadOnly bool `yaml:"readOnly"` + Skin string `yaml:"skin,omitempty"` + Namespace *Namespace `yaml:"namespace"` + View *View `yaml:"view"` + FeatureGates FeatureGates `yaml:"featureGates"` + PortForwardAddress string `yaml:"portForwardAddress"` +} + +// NewContext creates a new cluster configuration. +func NewContext() *Context { + return &Context{ + Namespace: NewNamespace(), + View: NewView(), + PortForwardAddress: DefaultPFAddress, + FeatureGates: NewFeatureGates(), + } +} + +func NewContextFromConfig(cfg *api.Context) *Context { + return &Context{ + Namespace: NewActiveNamespace(cfg.Namespace), + ClusterName: cfg.Cluster, + View: NewView(), + PortForwardAddress: DefaultPFAddress, + FeatureGates: NewFeatureGates(), + } +} + +// Validate a context config. +func (c *Context) Validate(conn client.Connection, ks KubeSettings) { + if c.PortForwardAddress == "" { + c.PortForwardAddress = DefaultPFAddress + } + + if cl, err := ks.CurrentClusterName(); err != nil { + c.ClusterName = cl + } + + if c.Namespace == nil { + c.Namespace = NewNamespace() + } + if c.Namespace.Active == client.BlankNamespace { + c.Namespace.Active = client.DefaultNamespace + } + c.Namespace.Validate(conn, ks) + + if c.View == nil { + c.View = NewView() + } + c.View.Validate() +} diff --git a/internal/config/data/context_test.go b/internal/config/data/context_test.go new file mode 100644 index 0000000000..d66c8d5689 --- /dev/null +++ b/internal/config/data/context_test.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/mock" + "github.com/stretchr/testify/assert" +) + +func TestClusterValidate(t *testing.T) { + c := data.NewContext() + c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, "po", c.View.Active) + assert.Equal(t, "default", c.Namespace.Active) + assert.Equal(t, 1, len(c.Namespace.Favorites)) + assert.Equal(t, []string{"default"}, c.Namespace.Favorites) +} + +func TestClusterValidateEmpty(t *testing.T) { + c := data.NewContext() + c.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, "po", c.View.Active) + assert.Equal(t, "default", c.Namespace.Active) + assert.Equal(t, 1, len(c.Namespace.Favorites)) + assert.Equal(t, []string{"default"}, c.Namespace.Favorites) +} diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go new file mode 100644 index 0000000000..3a577eceda --- /dev/null +++ b/internal/config/data/dir.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "errors" + "os" + "path/filepath" + + "github.com/derailed/k9s/internal/client" + "github.com/rs/zerolog/log" + "gopkg.in/yaml.v2" + "k8s.io/client-go/tools/clientcmd/api" +) + +type Dir struct { + root string + conn client.Connection + ks KubeSettings +} + +func NewDir(root string, conn client.Connection, ks KubeSettings) *Dir { + return &Dir{ + root: root, + ks: ks, + conn: conn, + } +} + +func (d Dir) Load(n string, ct *api.Context) (*Config, error) { + if ct == nil { + return nil, errors.New("api.Context must not be nil") + } + + var ( + path = filepath.Join(d.root, ct.Cluster, n, MainConfigFile) + cfg *Config + err error + ) + if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 { + log.Debug().Msgf("Context config not found! Generating... %q", path) + cfg, err = d.genConfig(path, ct) + } else { + log.Debug().Msgf("Found existing context config: %q", path) + cfg, err = d.loadConfig(path) + } + + return cfg, err +} + +func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { + cfg := NewConfig(ct) + cfg.Validate(d.conn, d.ks) + if err := cfg.Save(path); err != nil { + return nil, err + } + + return cfg, nil +} + +func (d *Dir) loadConfig(path string) (*Config, error) { + bb, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var cfg Config + if err := yaml.Unmarshal(bb, &cfg); err != nil { + return nil, err + } + cfg.Validate(d.conn, d.ks) + + return &cfg, nil +} diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go new file mode 100644 index 0000000000..fc4c3d3fde --- /dev/null +++ b/internal/config/data/dir_test.go @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data_test + +import ( + "os" + "strings" + "testing" + + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/mock" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +func init() { + zerolog.SetGlobalLevel(zerolog.FatalLevel) +} + +func TestDirLoad(t *testing.T) { + uu := map[string]struct { + dir string + flags *genericclioptions.ConfigFlags + err error + cfg *data.Config + }{ + "happy-cl-1-ct-1": { + dir: "testdata/data/k9s", + flags: makeFlags("cl-1", "ct-1-1"), + cfg: mustLoadConfig("testdata/configs/ct-1-1.yaml"), + }, + + "happy-cl-1-ct2": { + dir: "testdata/data/k9s", + flags: makeFlags("cl-1", "ct-1-2"), + cfg: mustLoadConfig("testdata/configs/ct-1-2.yaml"), + }, + + "happy-cl-2": { + dir: "testdata/data/k9s", + flags: makeFlags("cl-2", "ct-2-1"), + cfg: mustLoadConfig("testdata/configs/ct-2-1.yaml"), + }, + + "toast": { + dir: "/tmp/data/k9s", + flags: makeFlags("cl-test", "ct-test-1"), + cfg: mustLoadConfig("testdata/configs/def_ct.yaml"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.NotNil(t, u.cfg, "test config must not be nil") + if u.cfg == nil { + return + } + + ks := mock.NewMockKubeSettings(u.flags) + if strings.Index(u.dir, "/tmp") == 0 { + assert.NoError(t, mock.EnsureDir(u.dir)) + } + + d := data.NewDir(u.dir, mock.NewMockConnection(), ks) + ct, err := ks.CurrentContext() + assert.NoError(t, err) + if err != nil { + return + } + + cfg, err := d.Load(*u.flags.Context, ct) + assert.Equal(t, u.err, err) + if u.err == nil { + assert.Equal(t, u.cfg, cfg) + } + }) + } +} + +// Helpers... + +func makeFlags(cl, ct string) *genericclioptions.ConfigFlags { + return &genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + } +} + +func mustLoadConfig(cfg string) *data.Config { + bb, err := os.ReadFile(cfg) + if err != nil { + return nil + } + var ct data.Config + if err = yaml.Unmarshal(bb, &ct); err != nil { + return nil + } + + return &ct +} diff --git a/internal/config/data/feature_gate.go b/internal/config/data/feature_gate.go new file mode 100644 index 0000000000..8631c309a8 --- /dev/null +++ b/internal/config/data/feature_gate.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +// FeatureGates represents K9s opt-in features. +type FeatureGates struct { + NodeShell bool `yaml:"nodeShell"` +} + +// NewFeatureGates returns a new feature gate. +func NewFeatureGates() FeatureGates { + return FeatureGates{} +} diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go new file mode 100644 index 0000000000..98887bf6dd --- /dev/null +++ b/internal/config/data/helpers.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "os" + "path/filepath" +) + +// InList check if string is in a collection of strings. +func InList(ll []string, n string) bool { + for _, l := range ll { + if l == n { + return true + } + } + return false +} + +// EnsureDirPath ensures a directory exist from the given path. +func EnsureDirPath(path string, mod os.FileMode) error { + return EnsureFullPath(filepath.Dir(path), mod) +} + +// EnsureFullPath ensures a directory exist from the given path. +func EnsureFullPath(path string, mod os.FileMode) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err = os.MkdirAll(path, mod); err != nil { + return err + } + } + + return nil +} diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go new file mode 100644 index 0000000000..be4a6a8537 --- /dev/null +++ b/internal/config/data/helpers_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/derailed/k9s/internal/config/data" + "github.com/stretchr/testify/assert" +) + +func TestHelperInList(t *testing.T) { + uu := []struct { + item string + list []string + expected bool + }{ + {"a", []string{}, false}, + {"", []string{}, false}, + {"", []string{""}, true}, + {"a", []string{"a", "b", "c", "d"}, true}, + {"z", []string{"a", "b", "c", "d"}, false}, + } + + for _, u := range uu { + assert.Equal(t, u.expected, data.InList(u.list, u.item)) + } +} + +func TestEnsureDirPathNone(t *testing.T) { + var mod os.FileMode = 0744 + dir := filepath.Join("/tmp", "fred") + os.Remove(dir) + + path := filepath.Join(dir, "duh.yaml") + assert.NoError(t, data.EnsureDirPath(path, mod)) + + p, err := os.Stat(dir) + assert.NoError(t, err) + assert.Equal(t, "drwxr--r--", p.Mode().String()) +} + +func TestEnsureDirPathNoOpt(t *testing.T) { + var mod os.FileMode = 0744 + dir := filepath.Join("/tmp", "k9s-test") + os.Remove(dir) + assert.NoError(t, os.Mkdir(dir, mod)) + + path := filepath.Join(dir, "duh.yaml") + assert.NoError(t, data.EnsureDirPath(path, mod)) + + p, err := os.Stat(dir) + assert.NoError(t, err) + assert.Equal(t, "drwxr--r--", p.Mode().String()) +} diff --git a/internal/config/ns.go b/internal/config/data/ns.go similarity index 74% rename from internal/config/ns.go rename to internal/config/data/ns.go index c49f4324b1..252b4f5750 100644 --- a/internal/config/ns.go +++ b/internal/config/data/ns.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package config +package data import ( "github.com/derailed/k9s/internal/client" @@ -11,8 +11,6 @@ import ( const ( // MaxFavoritesNS number # favorite namespaces to keep in the configuration. MaxFavoritesNS = 9 - defaultNS = "default" - allNS = "all" ) // Namespace tracks active and favorites namespaces. @@ -25,27 +23,33 @@ type Namespace struct { // NewNamespace create a new namespace configuration. func NewNamespace() *Namespace { return &Namespace{ - Active: defaultNS, - Favorites: []string{defaultNS}, + Active: client.DefaultNamespace, + Favorites: []string{client.DefaultNamespace}, + } +} + +func NewActiveNamespace(n string) *Namespace { + return &Namespace{ + Active: n, + Favorites: []string{client.DefaultNamespace}, } } // Validate a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { if c == nil { - return + n = NewActiveNamespace(client.DefaultNamespace) } - nns, err := c.ValidNamespaces() - if err != nil { + if c == nil { + log.Debug().Msgf("No connection found. Skipping ns validation") return } - nn := client.NamespaceNames(nns) - if !n.isAllNamespaces() && !InList(nn, n.Active) { + if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) { log.Error().Msgf("[Config] Validation error active namespace %q does not exists", n.Active) } for _, ns := range n.Favorites { - if ns != allNS && !InList(nn, ns) { + if ns != client.NamespaceAll && !c.IsValidNamespace(ns) { log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) n.rmFavNS(ns) } @@ -54,8 +58,8 @@ func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { // SetActive set the active namespace. func (n *Namespace) SetActive(ns string, ks KubeSettings) error { - if ns == client.NotNamespaced { - ns = client.AllNamespaces + if ns == client.BlankNamespace { + ns = client.NamespaceAll } n.Active = ns if ns != "" && !n.LockFavorites { @@ -66,7 +70,7 @@ func (n *Namespace) SetActive(ns string, ks KubeSettings) error { } func (n *Namespace) isAllNamespaces() bool { - return n.Active == allNS || n.Active == "" + return n.Active == client.NamespaceAll || n.Active == "" } func (n *Namespace) addFavNS(ns string) { diff --git a/internal/config/data/ns_test.go b/internal/config/data/ns_test.go new file mode 100644 index 0000000000..7a45ead59c --- /dev/null +++ b/internal/config/data/ns_test.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/mock" + "github.com/stretchr/testify/assert" +) + +func TestNSValidate(t *testing.T) { + ns := data.NewNamespace() + ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, "default", ns.Active) + assert.Equal(t, []string{"default"}, ns.Favorites) +} + +func TestNSValidateMissing(t *testing.T) { + ns := data.NewNamespace() + ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, "default", ns.Active) + assert.Equal(t, []string{"default"}, ns.Favorites) +} + +func TestNSValidateNoNS(t *testing.T) { + ns := data.NewNamespace() + ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, "default", ns.Active) + assert.Equal(t, []string{"default"}, ns.Favorites) +} + +func TestNSSetActive(t *testing.T) { + allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"} + uu := []struct { + ns string + fav []string + }{ + {"all", []string{"all", "default"}}, + {"ns1", []string{"ns1", "all", "default"}}, + {"ns2", []string{"ns2", "ns1", "all", "default"}}, + {"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}}, + {"ns4", allNS}, + } + + mk := mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1")) + ns := data.NewNamespace() + for _, u := range uu { + err := ns.SetActive(u.ns, mk) + assert.Nil(t, err) + assert.Equal(t, u.ns, ns.Active) + assert.Equal(t, u.fav, ns.Favorites) + } +} + +func TestNSValidateRmFavs(t *testing.T) { + ns := data.NewNamespace() + ns.Favorites = []string{"default", "fred"} + ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + + assert.Equal(t, []string{"default", "fred"}, ns.Favorites) +} diff --git a/internal/config/data/testdata/configs/ct-1-1.yaml b/internal/config/data/testdata/configs/ct-1-1.yaml new file mode 100644 index 0000000000..091b90719b --- /dev/null +++ b/internal/config/data/testdata/configs/ct-1-1.yaml @@ -0,0 +1,16 @@ +k9s: + cluster: cl-1 + skin: skin-1 + readOnly: false + namespace: + active: ns-1 + lockFavorites: true + favorites: + - default + - ns-1 + - ns-2 + view: + active: dp + featureGates: + nodeShell: true + portForwardAddress: localhost diff --git a/internal/config/data/testdata/configs/ct-1-2.yaml b/internal/config/data/testdata/configs/ct-1-2.yaml new file mode 100644 index 0000000000..e7bd28f509 --- /dev/null +++ b/internal/config/data/testdata/configs/ct-1-2.yaml @@ -0,0 +1,14 @@ +k9s: + cluster: cl-1 + skin: in_the_navy + readOnly: true + namespace: + active: default + lockFavorites: false + favorites: + - default + view: + active: po + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/data/testdata/configs/ct-2-1.yaml b/internal/config/data/testdata/configs/ct-2-1.yaml new file mode 100644 index 0000000000..5a2e25befe --- /dev/null +++ b/internal/config/data/testdata/configs/ct-2-1.yaml @@ -0,0 +1,15 @@ +k9s: + cluster: cl-2 + skin: skin-2 + readOnly: true + namespace: + active: ns-2 + lockFavorites: true + favorites: + - ns-1 + - ns-2 + view: + active: svc + featureGates: + nodeShell: true + portForwardAddress: fred diff --git a/internal/config/data/testdata/configs/def_ct.yaml b/internal/config/data/testdata/configs/def_ct.yaml new file mode 100644 index 0000000000..a69eb34685 --- /dev/null +++ b/internal/config/data/testdata/configs/def_ct.yaml @@ -0,0 +1,12 @@ +k9s: + cluster: cl-test + namespace: + active: default + lockFavorites: false + favorites: + - default + view: + active: po + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml b/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml new file mode 100644 index 0000000000..091b90719b --- /dev/null +++ b/internal/config/data/testdata/data/k9s/cl-1/ct-1-1/config.yaml @@ -0,0 +1,16 @@ +k9s: + cluster: cl-1 + skin: skin-1 + readOnly: false + namespace: + active: ns-1 + lockFavorites: true + favorites: + - default + - ns-1 + - ns-2 + view: + active: dp + featureGates: + nodeShell: true + portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml b/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml new file mode 100644 index 0000000000..e7bd28f509 --- /dev/null +++ b/internal/config/data/testdata/data/k9s/cl-1/ct-1-2/config.yaml @@ -0,0 +1,14 @@ +k9s: + cluster: cl-1 + skin: in_the_navy + readOnly: true + namespace: + active: default + lockFavorites: false + favorites: + - default + view: + active: po + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml b/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml new file mode 100644 index 0000000000..5a2e25befe --- /dev/null +++ b/internal/config/data/testdata/data/k9s/cl-2/ct-2-1/config.yaml @@ -0,0 +1,15 @@ +k9s: + cluster: cl-2 + skin: skin-2 + readOnly: true + namespace: + active: ns-2 + lockFavorites: true + favorites: + - ns-1 + - ns-2 + view: + active: svc + featureGates: + nodeShell: true + portForwardAddress: fred diff --git a/internal/config/data/types.go b/internal/config/data/types.go new file mode 100644 index 0000000000..d798f77b8d --- /dev/null +++ b/internal/config/data/types.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "os" + + "k8s.io/client-go/tools/clientcmd/api" +) + +const ( + // DefaultDirMod default unix perms for k9s directory. + DefaultDirMod os.FileMode = 0744 + + // DefaultFileMod default unix perms for k9s files. + DefaultFileMod os.FileMode = 0600 + + // MainConfigFile track main configuration file.. + MainConfigFile = "config.yaml" +) + +// KubeSettings exposes kubeconfig context information. +type KubeSettings interface { + // CurrentContextName returns the name of the current context. + CurrentContextName() (string, error) + + // CurrentClusterName returns the name of the current cluster. + CurrentClusterName() (string, error) + + // CurrentNamespace returns the name of the current namespace. + CurrentNamespaceName() (string, error) + + // ContextNames() returns all available context names. + ContextNames() (map[string]struct{}, error) + + // CurrentContext returns the current context configuration. + CurrentContext() (*api.Context, error) + + // GetContext returns a given context configuration or err if not found. + GetContext(string) (*api.Context, error) +} diff --git a/internal/config/view.go b/internal/config/data/view.go similarity index 76% rename from internal/config/view.go rename to internal/config/data/view.go index e4078ccdbe..044972ebe5 100644 --- a/internal/config/view.go +++ b/internal/config/data/view.go @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package config +package data -const defaultView = "po" +const DefaultView = "po" // View tracks view configuration options. type View struct { @@ -12,12 +12,12 @@ type View struct { // NewView creates a new view configuration. func NewView() *View { - return &View{Active: defaultView} + return &View{Active: DefaultView} } // Validate a view configuration. func (v *View) Validate() { if len(v.Active) == 0 { - v.Active = defaultView + v.Active = DefaultView } } diff --git a/internal/config/view_test.go b/internal/config/data/view_test.go similarity index 78% rename from internal/config/view_test.go rename to internal/config/data/view_test.go index 10dfb152e8..100491fef6 100644 --- a/internal/config/view_test.go +++ b/internal/config/data/view_test.go @@ -1,17 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package config_test +package data_test import ( "testing" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/stretchr/testify/assert" ) func TestViewValidate(t *testing.T) { - v := config.NewView() + v := data.NewView() v.Validate() assert.Equal(t, "po", v.Active) @@ -22,7 +22,7 @@ func TestViewValidate(t *testing.T) { } func TestViewValidateBlank(t *testing.T) { - var v config.View + var v data.View v.Validate() assert.Equal(t, "po", v.Active) } diff --git a/internal/config/feature.go b/internal/config/feature.go index f94f5855a4..4b1fd75bcd 100644 --- a/internal/config/feature.go +++ b/internal/config/feature.go @@ -3,12 +3,12 @@ package config -// FeatureGates represents K9s opt-in features. -type FeatureGates struct { - NodeShell bool `yaml:"nodeShell"` -} +// // FeatureGates represents K9s opt-in features. +// type FeatureGates struct { +// NodeShell bool `yaml:"nodeShell"` +// } -// NewFeatureGates returns a new feature gate. -func NewFeatureGates() *FeatureGates { - return &FeatureGates{} -} +// // NewFeatureGates returns a new feature gate. +// func NewFeatureGates() *FeatureGates { +// return &FeatureGates{} +// } diff --git a/internal/config/files.go b/internal/config/files.go new file mode 100644 index 0000000000..05c40e2796 --- /dev/null +++ b/internal/config/files.go @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + _ "embed" + "os" + "os/user" + "path/filepath" + "regexp" + + "github.com/derailed/k9s/internal/config/data" + + "github.com/adrg/xdg" + "github.com/rs/zerolog/log" +) + +const ( + // K9sConfigDir represents k9s configuration dir env var. + K9sConfigDir = "K9S_CONFIG_DIR" + + // AppName tracks k9s app name. + AppName = "k9s" + + K9sLogsFile = "k9s.log" +) + +var ( + //go:embed templates/benchmarks.yaml + // benchmarkTpl tracks benchmark default config template + benchmarkTpl []byte + + //go:embed templates/aliases.yaml + // aliasesTpl tracks aliases default config template + aliasesTpl []byte + + //go:embed templates/hotkeys.yaml + // hotkeysTpl tracks hotkeys default config template + hotkeysTpl []byte + + //go:embed templates/stock-skin.yaml + // stockSkinTpl tracks stock skin template + stockSkinTpl []byte +) + +var ( + // AppConfigDir tracks main k9s config home directory. + AppConfigDir string + + // AppSkinsDir tracks skins data directory. + AppSkinsDir string + + // AppBenchmarksDir tracks benchmarks results directory. + AppBenchmarksDir string + + // AppDumpsDir tracks screen dumps data directory. + AppDumpsDir string + + // AppContextsDir tracks contexts data directory. + AppContextsDir string + + // AppConfigFile tracks k9s config file. + AppConfigFile string + + // AppLogFile tracks k9s logs file. + AppLogFile string + + // AppViewsFile tracks custom views config file. + AppViewsFile string + + // AppAliasesFile tracks aliases config file. + AppAliasesFile string + + // AppPluginsFile tracks plugins config file. + AppPluginsFile string + + // AppHotKeysFile tracks hotkeys config file. + AppHotKeysFile string +) + +// InitLogsLoc initializes K9s logs location. +func InitLogLoc() error { + if hasK9sConfigEnv() { + tmpDir, err := userTmpDir() + if err != nil { + return err + } + AppLogFile = filepath.Join(tmpDir, K9sLogsFile) + return nil + } + + var err error + AppLogFile, err = xdg.StateFile(filepath.Join(AppName, K9sLogsFile)) + + return err +} + +// InitLocs initializes k9s artifacts locations. +func InitLocs() error { + if hasK9sConfigEnv() { + return initK9sEnvLocs() + } + + return initXDGLocs() +} + +func initK9sEnvLocs() error { + AppConfigDir = os.Getenv(K9sConfigDir) + if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil { + return err + } + + AppDumpsDir = filepath.Join(AppConfigDir, "screen-dumps") + if err := data.EnsureFullPath(AppDumpsDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("Unable to create screen-dumps dir: %s", AppDumpsDir) + } + AppBenchmarksDir = filepath.Join(AppConfigDir, "benchmarks") + if err := data.EnsureFullPath(AppBenchmarksDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("Unable to create benchmarks dir: %s", AppBenchmarksDir) + } + AppSkinsDir = filepath.Join(AppConfigDir, "skins") + if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("Unable to create skins dir: %s", AppSkinsDir) + } + AppContextsDir = filepath.Join(AppConfigDir, "clusters") + if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("Unable to create clusters dir: %s", AppContextsDir) + } + + AppConfigFile = filepath.Join(AppConfigDir, data.MainConfigFile) + AppHotKeysFile = filepath.Join(AppConfigDir, "hotkeys.yaml") + AppAliasesFile = filepath.Join(AppConfigDir, "aliases.yaml") + AppPluginsFile = filepath.Join(AppConfigDir, "plugins.yaml") + AppViewsFile = filepath.Join(AppConfigDir, "views.yaml") + + return nil +} + +func initXDGLocs() error { + var err error + + AppConfigDir, err = xdg.ConfigFile(AppName) + if err != nil { + return err + } + + AppConfigFile, err = xdg.ConfigFile(filepath.Join(AppName, data.MainConfigFile)) + if err != nil { + return err + } + + AppHotKeysFile = filepath.Join(AppConfigDir, "hotkeys.yaml") + AppAliasesFile = filepath.Join(AppConfigDir, "aliases.yaml") + AppPluginsFile = filepath.Join(AppConfigDir, "plugins.yaml") + AppViewsFile = filepath.Join(AppConfigDir, "views.yaml") + + AppSkinsDir = filepath.Join(AppConfigDir, "skins") + if err := data.EnsureFullPath(AppSkinsDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("No skins dir detected") + } + + AppDumpsDir, err = xdg.StateFile(filepath.Join(AppName, "screen-dumps")) + if err != nil { + return err + } + + AppBenchmarksDir, err = xdg.StateFile(filepath.Join(AppName, "benchmarks")) + if err != nil { + log.Warn().Err(err).Msgf("No benchmarks dir detected") + } + + dataDir, err := xdg.DataFile(AppName) + if err != nil { + return err + } + AppContextsDir = filepath.Join(dataDir, "clusters") + if err := data.EnsureFullPath(AppContextsDir, data.DefaultDirMod); err != nil { + log.Warn().Err(err).Msgf("No context dir detected") + } + + return nil +} + +var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) + +// SanitizeFileName ensure file spec is valid. +func SanitizeFileName(name string) string { + return invalidPathCharsRX.ReplaceAllString(name, "-") +} + +// AppContextDir generates a valid context config dir. +func AppContextDir(cluster, context string) string { + return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context)) +} + +// AppContextAliasesFile generates a valid context specific aliases file path. +func AppContextAliasesFile(cluster, context string) string { + return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "aliases.yaml") +} + +// AppContextPluginsFile generates a valid context specific plugins file path. +func AppContextPluginsFile(cluster, context string) string { + return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "plugins.yaml") +} + +// AppContextHotkeysFile generates a valid context specific hotkeys file path. +func AppContextHotkeysFile(cluster, context string) string { + return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "hotkeys.yaml") +} + +// AppContextConfig generates a valid context config file path. +func AppContextConfig(cluster, context string) string { + return filepath.Join(AppContextDir(cluster, context), data.MainConfigFile) +} + +// DumpsDir generates a valid context dump directory. +func DumpsDir(cluster, context string) (string, error) { + dir := filepath.Join(AppDumpsDir, sanContextSubpath(cluster, context)) + + return dir, data.EnsureDirPath(dir, data.DefaultDirMod) +} + +// EnsureBenchmarksDir generates a valid benchmark results directory. +func EnsureBenchmarksDir(cluster, context string) (string, error) { + dir := filepath.Join(AppBenchmarksDir, sanContextSubpath(cluster, context)) + + return dir, data.EnsureDirPath(dir, data.DefaultDirMod) +} + +// EnsureBenchmarksCfgFile generates a valid benchmark file. +func EnsureBenchmarksCfgFile(cluster, context string) (string, error) { + f := filepath.Join(AppContextDir(cluster, context), "benchmarks.yaml") + if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { + return "", err + } + if _, err := os.Stat(f); os.IsNotExist(err) { + return f, os.WriteFile(f, benchmarkTpl, data.DefaultFileMod) + } + + return f, nil +} + +// EnsureAliasesCfgFile generates a valid aliases file. +func EnsureAliasesCfgFile() (string, error) { + f := filepath.Join(AppConfigDir, "aliases.yaml") + if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { + return "", err + } + if _, err := os.Stat(f); os.IsNotExist(err) { + return f, os.WriteFile(f, aliasesTpl, data.DefaultFileMod) + } + + return f, nil +} + +// EnsureHotkeysCfgFile generates a valid hotkeys file. +func EnsureHotkeysCfgFile() (string, error) { + f := filepath.Join(AppConfigDir, "hotkeys.yaml") + if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { + return "", err + } + if _, err := os.Stat(f); os.IsNotExist(err) { + return f, os.WriteFile(f, hotkeysTpl, data.DefaultFileMod) + } + + return f, nil +} + +// SkinFileFromName generate skin file path from spec. +func SkinFileFromName(n string) string { + return filepath.Join(AppSkinsDir, n+".yaml") +} + +// Helpers... + +func sanContextSubpath(cluster, context string) string { + return filepath.Join(SanitizeFileName(cluster), SanitizeFileName(context)) +} + +func hasK9sConfigEnv() bool { + return os.Getenv(K9sConfigDir) != "" +} + +func userTmpDir() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + + dir := filepath.Join(os.TempDir(), AppName, u.Username) + if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil { + return "", err + } + + return dir, nil +} diff --git a/internal/config/files_test.go b/internal/config/files_test.go new file mode 100644 index 0000000000..ca204e09ee --- /dev/null +++ b/internal/config/files_test.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config_test + +import ( + "os" + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" + "github.com/stretchr/testify/assert" +) + +func TestEnsureBenchmarkCfg(t *testing.T) { + os.Setenv(config.K9sConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) + defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + + assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod)) + assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod)) + + uu := map[string]struct { + cluster, context string + f, e string + }{ + "not-exist": { + cluster: "cl-1", + context: "ct-1", + f: "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", + e: "benchmarks:\n defaults:\n concurrency: 2\n requests: 200", + }, + "exist": { + cluster: "cl-1", + context: "ct-2", + f: "/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + f, err := config.EnsureBenchmarksCfgFile(u.cluster, u.context) + assert.NoError(t, err) + assert.Equal(t, u.f, f) + bb, err := os.ReadFile(f) + assert.NoError(t, err) + assert.Equal(t, u.e, string(bb)) + }) + } +} diff --git a/internal/config/flags.go b/internal/config/flags.go index aa2939ea47..8e7a78db45 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -3,12 +3,6 @@ package config -import ( - "fmt" - "os" - "path/filepath" -) - const ( // DefaultRefreshRate represents the refresh interval. DefaultRefreshRate = 2 // secs @@ -21,7 +15,7 @@ const ( ) // DefaultLogFile represents the default K9s log file. -var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser())) +// var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser())) // Flags represents K9s configuration flags. type Flags struct { @@ -43,7 +37,7 @@ func NewFlags() *Flags { return &Flags{ RefreshRate: intPtr(DefaultRefreshRate), LogLevel: strPtr(DefaultLogLevel), - LogFile: strPtr(DefaultLogFile), + LogFile: strPtr(AppLogFile), Headless: boolPtr(false), Logoless: boolPtr(false), Command: strPtr(DefaultCommand), @@ -51,7 +45,7 @@ func NewFlags() *Flags { ReadOnly: boolPtr(false), Write: boolPtr(false), Crumbsless: boolPtr(false), - ScreenDumpDir: strPtr(K9sDefaultScreenDumpDir), + ScreenDumpDir: strPtr(AppDumpsDir), } } diff --git a/internal/config/helpers.go b/internal/config/helpers.go index bc14a34898..4d2d239784 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -4,39 +4,18 @@ package config import ( - "os" "os/user" - "path/filepath" - "regexp" + "github.com/derailed/k9s/internal/config/data" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" ) -const ( - // DefaultDirMod default unix perms for k9s directory. - DefaultDirMod os.FileMode = 0755 - // DefaultFileMod default unix perms for k9s files. - DefaultFileMod os.FileMode = 0600 -) - -var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) - // SanitizeFilename sanitizes the dump filename. func SanitizeFilename(name string) string { return invalidPathCharsRX.ReplaceAllString(name, "-") } -// InList check if string is in a collection of strings. -func InList(ll []string, n string) bool { - for _, l := range ll { - if l == n { - return true - } - } - return false -} - // InNSList check if ns is in an ns collection. func InNSList(nn []interface{}, ns string) bool { ss := make([]string, len(nn)) @@ -45,7 +24,7 @@ func InNSList(nn []interface{}, ns string) bool { ss[i] = nsp.Name } } - return InList(ss, ns) + return data.InList(ss, ns) } // MustK9sUser establishes current user identity or fail. @@ -57,22 +36,6 @@ func MustK9sUser() string { return usr.Username } -// EnsureDirPath ensures a directory exist from the given path. -func EnsureDirPath(path string, mod os.FileMode) error { - return EnsureFullPath(filepath.Dir(path), mod) -} - -// EnsureFullPath ensures a directory exist from the given path. -func EnsureFullPath(path string, mod os.FileMode) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err = os.MkdirAll(path, mod); err != nil { - return err - } - } - - return nil -} - // IsBoolSet checks if a bool prt is set. func IsBoolSet(b *bool) bool { return b != nil && *b diff --git a/internal/config/helpers_test.go b/internal/config/helpers_test.go index 06dcf9442f..2c29e2d8d1 100644 --- a/internal/config/helpers_test.go +++ b/internal/config/helpers_test.go @@ -4,8 +4,6 @@ package config_test import ( - "os" - "path/filepath" "testing" "github.com/derailed/k9s/internal/config" @@ -14,24 +12,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestHelperInList(t *testing.T) { - uu := []struct { - item string - list []string - expected bool - }{ - {"a", []string{}, false}, - {"", []string{}, false}, - {"", []string{""}, true}, - {"a", []string{"a", "b", "c", "d"}, true}, - {"z", []string{"a", "b", "c", "d"}, false}, - } - - for _, u := range uu { - assert.Equal(t, u.expected, config.InList(u.list, u.item)) - } -} - func TestHelperInNSList(t *testing.T) { uu := []struct { item string @@ -58,30 +38,3 @@ func TestHelperInNSList(t *testing.T) { assert.Equal(t, u.expected, config.InNSList(u.list, u.item)) } } - -func TestEnsureDirPathNone(t *testing.T) { - var mod os.FileMode = 0744 - dir := filepath.Join("/tmp", "fred") - os.Remove(dir) - - path := filepath.Join(dir, "duh.yml") - assert.NoError(t, config.EnsureDirPath(path, mod)) - - p, err := os.Stat(dir) - assert.NoError(t, err) - assert.Equal(t, "drwxr--r--", p.Mode().String()) -} - -func TestEnsureDirPathNoOpt(t *testing.T) { - var mod os.FileMode = 0744 - dir := filepath.Join("/tmp", "blee") - os.Remove(dir) - assert.NoError(t, os.Mkdir(dir, mod)) - - path := filepath.Join(dir, "duh.yml") - assert.NoError(t, config.EnsureDirPath(path, mod)) - - p, err := os.Stat(dir) - assert.NoError(t, err) - assert.Equal(t, "drwxr--r--", p.Mode().String()) -} diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index ef3041de59..fa292a98f9 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -5,17 +5,13 @@ package config import ( "os" - "path/filepath" "gopkg.in/yaml.v2" ) -// K9sHotKeys manages K9s hotKeys. -var K9sHotKeys = YamlExtension(filepath.Join(K9sHome(), "hotkey.yml")) - // HotKeys represents a collection of plugins. type HotKeys struct { - HotKey map[string]HotKey `yaml:"hotKey"` + HotKey map[string]HotKey `yaml:"hotKeys"` } // HotKey describes a K9s hotkey. @@ -34,7 +30,7 @@ func NewHotKeys() HotKeys { // Load K9s plugins. func (h HotKeys) Load() error { - return h.LoadHotKeys(K9sHotKeys) + return h.LoadHotKeys(AppHotKeysFile) } // LoadHotKeys loads plugins from a given file. diff --git a/internal/config/hotkey_test.go b/internal/config/hotkey_test.go index 91fe6a4b5a..80b244c735 100644 --- a/internal/config/hotkey_test.go +++ b/internal/config/hotkey_test.go @@ -12,7 +12,7 @@ import ( func TestHotKeyLoad(t *testing.T) { h := config.NewHotKeys() - assert.Nil(t, h.LoadHotKeys("testdata/hot_key.yml")) + assert.Nil(t, h.LoadHotKeys("testdata/hotkeys.yaml")) assert.Equal(t, 1, len(h.HotKey)) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 8dbaacb638..2fae52b9d1 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -4,37 +4,28 @@ package config import ( - "github.com/derailed/k9s/internal/client" -) + "errors" + "path/filepath" -const ( - defaultRefreshRate = 2 - defaultMaxConnRetry = 5 + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config/data" ) // K9s tracks K9s configuration options. type K9s struct { - LiveViewAutoRefresh bool `yaml:"liveViewAutoRefresh"` - RefreshRate int `yaml:"refreshRate"` - MaxConnRetry int `yaml:"maxConnRetry"` - EnableMouse bool `yaml:"enableMouse"` - EnableImageScan bool `yaml:"enableImageScan"` - Headless bool `yaml:"headless"` - Logoless bool `yaml:"logoless"` - Crumbsless bool `yaml:"crumbsless"` - ReadOnly bool `yaml:"readOnly"` - NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"` - NoIcons bool `yaml:"noIcons"` - ShellPod *ShellPod `yaml:"shellPod"` - SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"` - Logger *Logger `yaml:"logger"` - CurrentContext string `yaml:"currentContext"` - CurrentCluster string `yaml:"currentCluster"` - KeepMissingClusters bool `yaml:"keepMissingClusters"` - Clusters map[string]*Cluster `yaml:"clusters,omitempty"` - Thresholds Threshold `yaml:"thresholds"` - ScreenDumpDir string `yaml:"screenDumpDir"` - DisablePodCounting bool `yaml:"disablePodCounting"` + LiveViewAutoRefresh bool `yaml:"liveViewAutoRefresh"` + ScreenDumpDir string `yaml:"screenDumpDir,omitempty"` + RefreshRate int `yaml:"refreshRate"` + MaxConnRetry int `yaml:"maxConnRetry"` + ReadOnly bool `yaml:"readOnly"` + NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"` + UI UI `yaml:"ui"` + SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"` + DisablePodCounting bool `yaml:"disablePodCounting"` + ShellPod *ShellPod `yaml:"shellPod"` + ImageScans *ImageScans `yaml:"imageScans"` + Logger *Logger `yaml:"logger"` + Thresholds Threshold `yaml:"thresholds"` manualRefreshRate int manualHeadless *bool manualLogoless *bool @@ -42,36 +33,151 @@ type K9s struct { manualReadOnly *bool manualCommand *string manualScreenDumpDir *string + dir *data.Dir + activeContextName string + activeConfig *data.Config + conn client.Connection + ks data.KubeSettings } // NewK9s create a new K9s configuration. -func NewK9s() *K9s { +func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { return &K9s{ RefreshRate: defaultRefreshRate, MaxConnRetry: defaultMaxConnRetry, + ScreenDumpDir: AppDumpsDir, Logger: NewLogger(), - Clusters: make(map[string]*Cluster), Thresholds: NewThreshold(), - ScreenDumpDir: K9sDefaultScreenDumpDir, ShellPod: NewShellPod(), + ImageScans: NewImageScans(), + dir: data.NewDir(AppContextsDir, conn, ks), + conn: conn, + ks: ks, + } +} + +func (k *K9s) Save() error { + if k.activeConfig != nil { + path := filepath.Join( + AppContextsDir, + k.activeConfig.Context.ClusterName, + k.activeContextName, + data.MainConfigFile, + ) + return k.activeConfig.Save(path) + } + + return nil +} + +func (k *K9s) Refine(k1 *K9s) { + k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh + k.ScreenDumpDir = k1.ScreenDumpDir + k.RefreshRate = k1.RefreshRate + k.MaxConnRetry = k1.MaxConnRetry + k.ReadOnly = k1.ReadOnly + k.NoExitOnCtrlC = k1.NoExitOnCtrlC + k.UI = k1.UI + k.SkipLatestRevCheck = k1.SkipLatestRevCheck + k.DisablePodCounting = k1.DisablePodCounting + k.ShellPod = k1.ShellPod + k.ImageScans = k1.ImageScans + k.Logger = k1.Logger + k.Thresholds = k1.Thresholds +} + +func (k *K9s) Generate(k9sFlags *Flags) { + if *k9sFlags.RefreshRate != DefaultRefreshRate { + k.OverrideRefreshRate(*k9sFlags.RefreshRate) } + + k.OverrideHeadless(*k9sFlags.Headless) + k.OverrideLogoless(*k9sFlags.Logoless) + k.OverrideCrumbsless(*k9sFlags.Crumbsless) + k.OverrideReadOnly(*k9sFlags.ReadOnly) + k.OverrideWrite(*k9sFlags.Write) + k.OverrideCommand(*k9sFlags.Command) + k.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir) } -func (k *K9s) CurrentContextDir() string { - return SanitizeFilename(k.CurrentContext) +// OverrideScreenDumpDir set the screen dump dir manually. +func (k *K9s) OverrideScreenDumpDir(dir string) { + k.manualScreenDumpDir = &dir } -// ActivateCluster initializes the active cluster is not present. -func (k *K9s) ActivateCluster(ns string) { - if k.Clusters == nil { - k.Clusters = map[string]*Cluster{} +func (k *K9s) GetScreenDumpDir() string { + screenDumpDir := k.ScreenDumpDir + if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" { + screenDumpDir = *k.manualScreenDumpDir + } + if screenDumpDir == "" { + screenDumpDir = AppDumpsDir + } + + return screenDumpDir +} + +func (k *K9s) Reset() { + k.activeConfig, k.activeContextName = nil, "" +} + +func (k *K9s) ActiveContextDir() string { + if k.activeConfig == nil { + return "na" + } + + return filepath.Join( + SanitizeFileName(k.activeConfig.Context.ClusterName), + SanitizeFileName(k.ActiveContextName()), + ) +} + +func (k *K9s) ActiveContextNamespace() (string, error) { + if k.activeConfig != nil { + return k.activeConfig.Context.Namespace.Active, nil + } + + return "", errors.New("context config is not set") +} + +func (k *K9s) ActiveContextName() string { + return k.activeContextName +} + +// ActiveContext returns the currently active context. +func (k *K9s) ActiveContext() (*data.Context, error) { + if k.activeConfig != nil { + return k.activeConfig.Context, nil + } + + ct, err := k.ActivateContext(k.activeContextName) + if err != nil { + return nil, err + } + + return ct, nil +} + +// ActivateContext initializes the active context is not present. +func (k *K9s) ActivateContext(n string) (*data.Context, error) { + k.activeContextName = n + ct, err := k.ks.GetContext(k.activeContextName) + if err != nil { + return nil, err } - if _, ok := k.Clusters[k.CurrentCluster]; ok { - return + cfg, err := k.dir.Load(n, ct) + if err != nil { + return nil, err + } + k.activeConfig = cfg + // If the context specifies a default namespace, use it! + if k.conn != nil { + if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace { + k.activeConfig.Context.Namespace.Active = ns + } } - cl := NewCluster() - cl.Namespace.Active = ns - k.Clusters[k.CurrentCluster] = cl + + return cfg.Context, nil } // OverrideRefreshRate set the refresh rate manually. @@ -114,14 +220,9 @@ func (k *K9s) OverrideCommand(cmd string) { k.manualCommand = &cmd } -// OverrideScreenDumpDir set the screen dump dir manually. -func (k *K9s) OverrideScreenDumpDir(dir string) { - k.manualScreenDumpDir = &dir -} - // IsHeadless returns headless setting. func (k *K9s) IsHeadless() bool { - h := k.Headless + h := k.UI.Headless if k.manualHeadless != nil && *k.manualHeadless { h = *k.manualHeadless } @@ -131,7 +232,7 @@ func (k *K9s) IsHeadless() bool { // IsLogoless returns logoless setting. func (k *K9s) IsLogoless() bool { - h := k.Logoless + h := k.UI.Logoless if k.manualLogoless != nil && *k.manualLogoless { h = *k.manualLogoless } @@ -141,7 +242,7 @@ func (k *K9s) IsLogoless() bool { // IsCrumbsless returns crumbsless setting. func (k *K9s) IsCrumbsless() bool { - h := k.Crumbsless + h := k.UI.Crumbsless if k.manualCrumbsless != nil && *k.manualCrumbsless { h = *k.manualCrumbsless } @@ -165,34 +266,11 @@ func (k *K9s) IsReadOnly() bool { if k.manualReadOnly != nil { readOnly = *k.manualReadOnly } - - return readOnly -} - -// ActiveCluster returns the currently active cluster. -func (k *K9s) ActiveCluster() *Cluster { - if k.Clusters == nil { - k.Clusters = map[string]*Cluster{} - } - if c, ok := k.Clusters[k.CurrentCluster]; ok { - return c - } - k.Clusters[k.CurrentCluster] = NewCluster() - - return k.Clusters[k.CurrentCluster] -} - -func (k *K9s) GetScreenDumpDir() string { - screenDumpDir := k.ScreenDumpDir - if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" { - screenDumpDir = *k.manualScreenDumpDir + if k.activeConfig != nil && k.activeConfig.Context.ReadOnly { + readOnly = true } - if screenDumpDir == "" { - return K9sDefaultScreenDumpDir - } - - return screenDumpDir + return readOnly } func (k *K9s) validateDefaults() { @@ -202,44 +280,19 @@ func (k *K9s) validateDefaults() { if k.MaxConnRetry <= 0 { k.MaxConnRetry = defaultMaxConnRetry } - if k.ScreenDumpDir == "" { - k.ScreenDumpDir = K9sDefaultScreenDumpDir - } -} - -func (k *K9s) validateClusters(c client.Connection, ks KubeSettings) { - cc, err := ks.ClusterNames() - if err != nil { - return - } - for key, cluster := range k.Clusters { - cluster.Validate(c, ks) - // if the cluster is defined in the $KUBECONFIG file, keep it in the k9s config file - if _, ok := cc[key]; ok { - continue - } - - // if we asked to keep the clusters in the config file - if k.KeepMissingClusters { - continue - } - - // else remove it from the k9s config file - if k.CurrentCluster == key { - k.CurrentCluster = "" - } - delete(k.Clusters, key) - } } // Validate the current configuration. -func (k *K9s) Validate(c client.Connection, ks KubeSettings) { +func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.validateDefaults() - if k.Clusters == nil { - k.Clusters = map[string]*Cluster{} + if k.activeConfig == nil { + if n, err := ks.CurrentContextName(); err == nil { + _, _ = k.ActivateContext(n) + } + } + if k.ImageScans == nil { + k.ImageScans = NewImageScans() } - k.validateClusters(c, ks) - if k.ShellPod == nil { k.ShellPod = NewShellPod() } @@ -254,18 +307,4 @@ func (k *K9s) Validate(c client.Connection, ks KubeSettings) { k.Thresholds = NewThreshold() } k.Thresholds.Validate(c, ks) - - if context, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 { - k.CurrentContext = context - k.CurrentCluster = "" - } - - if cl, err := ks.CurrentClusterName(); err == nil && len(k.CurrentCluster) == 0 { - k.CurrentCluster = cl - } - - if _, ok := k.Clusters[k.CurrentCluster]; !ok { - k.Clusters[k.CurrentCluster] = NewCluster() - } - k.Clusters[k.CurrentCluster].Validate(c, ks) } diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index b4c233ada8..67c1c461b3 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -7,165 +7,37 @@ import ( "testing" "github.com/derailed/k9s/internal/config" - m "github.com/petergtz/pegomock" + "github.com/derailed/k9s/internal/config/mock" "github.com/stretchr/testify/assert" ) -func TestIsReadOnly(t *testing.T) { - uu := map[string]struct { - config string - read, write bool - readOnly bool - }{ - "writable": { - config: "k9s.yml", - }, - "writable_read_override": { - config: "k9s.yml", - read: true, - readOnly: true, - }, - "writable_write_override": { - config: "k9s.yml", - write: true, - }, - "readonly": { - config: "k9s_readonly.yml", - readOnly: true, - }, - "readonly_read_override": { - config: "k9s_readonly.yml", - read: true, - readOnly: true, - }, - "readonly_write_override": { - config: "k9s_readonly.yml", - write: true, - }, - "readonly_both_override": { - config: "k9s_readonly.yml", - read: true, - write: true, - }, - } - - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Nil(t, cfg.Load("testdata/"+u.config)) - cfg.K9s.OverrideReadOnly(u.read) - cfg.K9s.OverrideWrite(u.write) - assert.Equal(t, u.readOnly, cfg.K9s.IsReadOnly()) - }) - } -} - -func TestK9sValidate(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) - m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) - m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil) - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) - - c := config.NewK9s() - c.Validate(mc, mk) - - assert.Equal(t, 2, c.RefreshRate) - assert.Equal(t, int64(100), c.Logger.TailCount) - assert.Equal(t, 5000, c.Logger.BufferSize) - assert.Equal(t, "ctx1", c.CurrentContext) - assert.Equal(t, "c1", c.CurrentCluster) - assert.Equal(t, 1, len(c.Clusters)) - assert.Equal(t, config.K9sDefaultScreenDumpDir, c.GetScreenDumpDir()) - _, ok := c.Clusters[c.CurrentCluster] - assert.True(t, ok) -} - -func TestK9sValidateBlank(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - - mk := NewMockKubeSettings() - m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil) - m.When(mk.CurrentClusterName()).ThenReturn("c1", nil) - m.When(mk.ClusterNames()).ThenReturn(map[string]struct{}{"c1": {}, "c2": {}}, nil) - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"}) - - var c config.K9s - c.Validate(mc, mk) - - assert.Equal(t, 2, c.RefreshRate) - assert.Equal(t, int64(100), c.Logger.TailCount) - assert.Equal(t, 5000, c.Logger.BufferSize) - assert.Equal(t, "ctx1", c.CurrentContext) - assert.Equal(t, "c1", c.CurrentCluster) - assert.Equal(t, 1, len(c.Clusters)) - _, ok := c.Clusters[c.CurrentCluster] - assert.True(t, ok) -} - -func TestK9sActiveClusterZero(t *testing.T) { - c := config.NewK9s() - c.CurrentCluster = "fred" - cl := c.ActiveCluster() - assert.NotNil(t, cl) - assert.Equal(t, "default", cl.Namespace.Active) - assert.Equal(t, 1, len(cl.Namespace.Favorites)) -} - -func TestK9sActiveClusterBlank(t *testing.T) { - var c config.K9s - cl := c.ActiveCluster() - assert.Equal(t, config.NewCluster(), cl) -} - -func TestK9sActiveCluster(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - - cl := cfg.K9s.ActiveCluster() - assert.NotNil(t, cl) - assert.Equal(t, "kube-system", cl.Namespace.Active) - assert.Equal(t, 5, len(cl.Namespace.Favorites)) -} - func TestGetScreenDumpDir(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) + cfg := mock.NewMockConfig() + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir()) } func TestGetScreenDumpDirOverride(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - cfg.K9s.OverrideScreenDumpDir("/override") + cfg := mock.NewMockConfig() + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + cfg.K9s.OverrideScreenDumpDir("/override") assert.Equal(t, "/override", cfg.K9s.GetScreenDumpDir()) } func TestGetScreenDumpDirOverrideEmpty(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s.yml")) - cfg.K9s.OverrideScreenDumpDir("") + cfg := mock.NewMockConfig() + assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + cfg.K9s.OverrideScreenDumpDir("") assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir()) } func TestGetScreenDumpDirEmpty(t *testing.T) { - mk := NewMockKubeSettings() - cfg := config.NewConfig(mk) - assert.Nil(t, cfg.Load("testdata/k9s1.yml")) - cfg.K9s.OverrideScreenDumpDir("") + cfg := mock.NewMockConfig() - assert.Equal(t, config.K9sDefaultScreenDumpDir, cfg.K9s.GetScreenDumpDir()) + assert.Nil(t, cfg.Load("testdata/k9s1.yaml")) + cfg.K9s.OverrideScreenDumpDir("") + assert.Equal(t, config.AppDumpsDir, cfg.K9s.GetScreenDumpDir()) } diff --git a/internal/config/logger.go b/internal/config/logger.go index 13cb96b108..4cca7e9736 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -5,6 +5,7 @@ package config import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config/data" ) const ( @@ -38,7 +39,7 @@ func NewLogger() *Logger { } // Validate checks thresholds and make sure we're cool. If not use defaults. -func (l *Logger) Validate(_ client.Connection, _ KubeSettings) { +func (l *Logger) Validate(_ client.Connection, _ data.KubeSettings) { if l.TailCount <= 0 { l.TailCount = DefaultLoggerTailCount } diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go new file mode 100644 index 0000000000..b5378bf2d5 --- /dev/null +++ b/internal/config/mock/test_helpers.go @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package mock + +import ( + "fmt" + "os" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + version "k8s.io/apimachinery/pkg/version" + "k8s.io/cli-runtime/pkg/genericclioptions" + disk "k8s.io/client-go/discovery/cached/disk" + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd/api" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" +) + +func EnsureDir(d string) error { + if _, err := os.Stat(d); os.IsNotExist(err) { + return os.MkdirAll(d, 0700) + } + if err := os.RemoveAll(d); err != nil { + return err + } + + return os.MkdirAll(d, 0700) +} + +func NewMockConfig() *config.Config { + config.AppContextsDir = "/tmp/test" + cl, ct := "cl-1", "ct-1" + flags := genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + } + cfg := config.NewConfig( + NewMockKubeSettings(&flags), + ) + + return cfg +} + +type mockKubeSettings struct { + flags *genericclioptions.ConfigFlags + cts map[string]*api.Context +} + +func NewMockKubeSettings(f *genericclioptions.ConfigFlags) mockKubeSettings { + _, idx, _ := strings.Cut(*f.ClusterName, "-") + ctId := "ct-" + idx + + return mockKubeSettings{ + flags: f, + cts: map[string]*api.Context{ + ctId + "-1": { + Cluster: *f.ClusterName, + Namespace: "", + }, + ctId + "-2": { + Cluster: *f.ClusterName, + Namespace: "ns-1", + }, + ctId + "-3": { + Cluster: *f.ClusterName, + Namespace: client.DefaultNamespace, + }, + }, + } +} +func (m mockKubeSettings) CurrentContextName() (string, error) { + return *m.flags.Context, nil +} +func (m mockKubeSettings) CurrentClusterName() (string, error) { + return *m.flags.ClusterName, nil +} +func (m mockKubeSettings) CurrentNamespaceName() (string, error) { + return "default", nil +} +func (m mockKubeSettings) GetContext(s string) (*api.Context, error) { + ct, ok := m.cts[s] + if !ok { + return nil, fmt.Errorf("no context found for: %q", s) + } + return ct, nil +} +func (m mockKubeSettings) CurrentContext() (*api.Context, error) { + return m.GetContext(*m.flags.Context) +} +func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) { + mm := make(map[string]struct{}, len(m.cts)) + for k := range m.cts { + mm[k] = struct{}{} + } + + return mm, nil +} + +type mockConnection struct{} + +func NewMockConnection() mockConnection { + return mockConnection{} +} +func (m mockConnection) CanI(ns, gvr string, verbs []string) (bool, error) { + return true, nil +} +func (m mockConnection) Config() *client.Config { + return nil +} +func (m mockConnection) ConnectionOK() bool { + return false +} +func (m mockConnection) Dial() (kubernetes.Interface, error) { + return nil, nil +} +func (m mockConnection) DialLogs() (kubernetes.Interface, error) { + return nil, nil +} +func (m mockConnection) SwitchContext(ctx string) error { + return nil +} +func (m mockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { + return nil, nil +} +func (m mockConnection) RestConfig() (*restclient.Config, error) { + return nil, nil +} +func (m mockConnection) MXDial() (*versioned.Clientset, error) { + return nil, nil +} +func (m mockConnection) DynDial() (dynamic.Interface, error) { + return nil, nil +} +func (m mockConnection) HasMetrics() bool { + return false +} +func (m mockConnection) ValidNamespaceNames() (client.NamespaceNames, error) { + return nil, nil +} +func (m mockConnection) IsValidNamespace(string) bool { + return true +} +func (m mockConnection) ServerVersion() (*version.Info, error) { + return nil, nil +} +func (m mockConnection) CheckConnectivity() bool { + return false +} +func (m mockConnection) ActiveContext() string { + return "" +} +func (m mockConnection) ActiveNamespace() string { + return "" +} +func (m mockConnection) IsActiveNamespace(string) bool { + return false +} diff --git a/internal/config/mock_connection_test.go b/internal/config/mock_connection_test.go deleted file mode 100644 index 77f4568b07..0000000000 --- a/internal/config/mock_connection_test.go +++ /dev/null @@ -1,674 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/client (interfaces: Connection) - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config_test - -import ( - client "github.com/derailed/k9s/internal/client" - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - version "k8s.io/apimachinery/pkg/version" - disk "k8s.io/client-go/discovery/cached/disk" - dynamic "k8s.io/client-go/dynamic" - kubernetes "k8s.io/client-go/kubernetes" - rest "k8s.io/client-go/rest" - versioned "k8s.io/metrics/pkg/client/clientset/versioned" - "reflect" - "time" -) - -type MockConnection struct { - fail func(message string, callerSkip ...int) -} - -func NewMockConnection(options ...pegomock.Option) *MockConnection { - mock := &MockConnection{} - for _, option := range options { - option.Apply(mock) - } - return mock -} - -func (mock *MockConnection) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } -func (mock *MockConnection) FailHandler() pegomock.FailHandler { return mock.fail } - -func (mock *MockConnection) ActiveCluster() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveCluster", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()}) - var ret0 string - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - } - return ret0 -} - -func (mock *MockConnection) ActiveNamespace() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveNamespace", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()}) - var ret0 string - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - } - return ret0 -} - -func (mock *MockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CachedDiscovery", params, []reflect.Type{reflect.TypeOf((**disk.CachedDiscoveryClient)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *disk.CachedDiscoveryClient - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*disk.CachedDiscoveryClient) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) CanI(_param0 string, _param1 string, _param2 []string) (bool, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0, _param1, _param2} - result := pegomock.GetGenericMockFrom(mock).Invoke("CanI", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 bool - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) CheckConnectivity() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CheckConnectivity", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) Config() *client.Config { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**client.Config)(nil)).Elem()}) - var ret0 *client.Config - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*client.Config) - } - } - return ret0 -} - -func (mock *MockConnection) ConnectionOK() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ConnectionOK", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) Dial() (kubernetes.Interface, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Dial", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 kubernetes.Interface - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(kubernetes.Interface) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) DialLogs() (kubernetes.Interface, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("DialLogs", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 kubernetes.Interface - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(kubernetes.Interface) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) DynDial() (dynamic.Interface, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("DynDial", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 dynamic.Interface - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.Interface) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) HasMetrics() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) IsActiveNamespace(_param0 string) bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("IsActiveNamespace", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *versioned.Clientset - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*versioned.Clientset) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) RestConfig() (*rest.Config, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfig", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *rest.Config - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*rest.Config) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) ServerVersion() (*version.Info, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ServerVersion", params, []reflect.Type{reflect.TypeOf((**version.Info)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *version.Info - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*version.Info) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) SwitchContext(_param0 string) error { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(error) - } - } - return ret0 -} - -func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 []v1.Namespace - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].([]v1.Namespace) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) VerifyWasCalledOnce() *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockConnection) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockConnection) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockConnection) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMockConnection struct { - mock *MockConnection - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMockConnection) ActiveCluster() *MockConnection_ActiveCluster_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveCluster", params, verifier.timeout) - return &MockConnection_ActiveCluster_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ActiveCluster_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ActiveCluster_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ActiveCluster_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ActiveNamespace() *MockConnection_ActiveNamespace_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveNamespace", params, verifier.timeout) - return &MockConnection_ActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ActiveNamespace_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ActiveNamespace_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ActiveNamespace_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) CachedDiscovery() *MockConnection_CachedDiscovery_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CachedDiscovery", params, verifier.timeout) - return &MockConnection_CachedDiscovery_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CachedDiscovery_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CachedDiscovery_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_CachedDiscovery_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) CanI(_param0 string, _param1 string, _param2 []string) *MockConnection_CanI_OngoingVerification { - params := []pegomock.Param{_param0, _param1, _param2} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanI", params, verifier.timeout) - return &MockConnection_CanI_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CanI_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CanI_OngoingVerification) GetCapturedArguments() (string, string, []string) { - _param0, _param1, _param2 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] -} - -func (c *MockConnection_CanI_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([]string, len(c.methodInvocations)) - for u, param := range params[1] { - _param1[u] = param.(string) - } - _param2 = make([][]string, len(c.methodInvocations)) - for u, param := range params[2] { - _param2[u] = param.([]string) - } - } - return -} - -func (verifier *VerifierMockConnection) CheckConnectivity() *MockConnection_CheckConnectivity_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CheckConnectivity", params, verifier.timeout) - return &MockConnection_CheckConnectivity_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CheckConnectivity_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CheckConnectivity_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_CheckConnectivity_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) Config() *MockConnection_Config_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) - return &MockConnection_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_Config_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_Config_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_Config_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ConnectionOK() *MockConnection_ConnectionOK_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ConnectionOK", params, verifier.timeout) - return &MockConnection_ConnectionOK_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ConnectionOK_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ConnectionOK_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ConnectionOK_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) Dial() *MockConnection_Dial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Dial", params, verifier.timeout) - return &MockConnection_Dial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_Dial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_Dial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_Dial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) DynDial() *MockConnection_DynDial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDial", params, verifier.timeout) - return &MockConnection_DynDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_DynDial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_DynDial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_DynDial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) - return &MockConnection_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_HasMetrics_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_HasMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_HasMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) IsActiveNamespace(_param0 string) *MockConnection_IsActiveNamespace_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "IsActiveNamespace", params, verifier.timeout) - return &MockConnection_IsActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_IsActiveNamespace_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockConnection) MXDial() *MockConnection_MXDial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) - return &MockConnection_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_MXDial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_MXDial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_MXDial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) RestConfig() *MockConnection_RestConfig_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfig", params, verifier.timeout) - return &MockConnection_RestConfig_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_RestConfig_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_RestConfig_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_RestConfig_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ServerVersion() *MockConnection_ServerVersion_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ServerVersion", params, verifier.timeout) - return &MockConnection_ServerVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ServerVersion_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ServerVersion_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ServerVersion_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) SwitchContext(_param0 string) *MockConnection_SwitchContext_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContext", params, verifier.timeout) - return &MockConnection_SwitchContext_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_SwitchContext_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_SwitchContext_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockConnection_SwitchContext_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockConnection) ValidNamespaces() *MockConnection_ValidNamespaces_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) - return &MockConnection_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ValidNamespaces_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ValidNamespaces_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { -} diff --git a/internal/config/mock_kubesettings_test.go b/internal/config/mock_kubesettings_test.go deleted file mode 100644 index 68d7fd0901..0000000000 --- a/internal/config/mock_kubesettings_test.go +++ /dev/null @@ -1,252 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/config (interfaces: KubeSettings) - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config_test - -import ( - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - "reflect" - "time" -) - -type MockKubeSettings struct { - fail func(message string, callerSkip ...int) -} - -func NewMockKubeSettings(options ...pegomock.Option) *MockKubeSettings { - mock := &MockKubeSettings{} - for _, option := range options { - option.Apply(mock) - } - return mock -} - -func (mock *MockKubeSettings) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } -func (mock *MockKubeSettings) FailHandler() pegomock.FailHandler { return mock.fail } - -func (mock *MockKubeSettings) ClusterNames() (map[string]struct{}, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockKubeSettings().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterNames", params, []reflect.Type{reflect.TypeOf((*map[string]struct{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 map[string]struct{} - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(map[string]struct{}) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockKubeSettings) CurrentClusterName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockKubeSettings().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentClusterName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockKubeSettings) CurrentContextName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockKubeSettings().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentContextName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockKubeSettings) CurrentNamespaceName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockKubeSettings().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentNamespaceName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockKubeSettings) NamespaceNames(_param0 []v1.Namespace) []string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockKubeSettings().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()}) - var ret0 []string - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].([]string) - } - } - return ret0 -} - -func (mock *MockKubeSettings) VerifyWasCalledOnce() *VerifierMockKubeSettings { - return &VerifierMockKubeSettings{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockKubeSettings) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockKubeSettings { - return &VerifierMockKubeSettings{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockKubeSettings) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockKubeSettings { - return &VerifierMockKubeSettings{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockKubeSettings) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockKubeSettings { - return &VerifierMockKubeSettings{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMockKubeSettings struct { - mock *MockKubeSettings - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMockKubeSettings) ClusterNames() *MockKubeSettings_ClusterNames_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterNames", params, verifier.timeout) - return &MockKubeSettings_ClusterNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockKubeSettings_ClusterNames_OngoingVerification struct { - mock *MockKubeSettings - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockKubeSettings_ClusterNames_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockKubeSettings_ClusterNames_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockKubeSettings) CurrentClusterName() *MockKubeSettings_CurrentClusterName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentClusterName", params, verifier.timeout) - return &MockKubeSettings_CurrentClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockKubeSettings_CurrentClusterName_OngoingVerification struct { - mock *MockKubeSettings - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockKubeSettings_CurrentClusterName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockKubeSettings_CurrentClusterName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockKubeSettings) CurrentContextName() *MockKubeSettings_CurrentContextName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentContextName", params, verifier.timeout) - return &MockKubeSettings_CurrentContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockKubeSettings_CurrentContextName_OngoingVerification struct { - mock *MockKubeSettings - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockKubeSettings_CurrentContextName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockKubeSettings_CurrentContextName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockKubeSettings) CurrentNamespaceName() *MockKubeSettings_CurrentNamespaceName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout) - return &MockKubeSettings_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockKubeSettings_CurrentNamespaceName_OngoingVerification struct { - mock *MockKubeSettings - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockKubeSettings_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockKubeSettings_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockKubeSettings) NamespaceNames(_param0 []v1.Namespace) *MockKubeSettings_NamespaceNames_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout) - return &MockKubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockKubeSettings_NamespaceNames_OngoingVerification struct { - mock *MockKubeSettings - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockKubeSettings_NamespaceNames_OngoingVerification) GetCapturedArguments() []v1.Namespace { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockKubeSettings_NamespaceNames_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Namespace) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([][]v1.Namespace, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.([]v1.Namespace) - } - } - return -} diff --git a/internal/config/ns_test.go b/internal/config/ns_test.go deleted file mode 100644 index c84ffe54cf..0000000000 --- a/internal/config/ns_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config_test - -import ( - "fmt" - "testing" - - "github.com/derailed/k9s/internal/config" - m "github.com/petergtz/pegomock" - "github.com/stretchr/testify/assert" -) - -func TestNSValidate(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2", "default"}) - - ns := config.NewNamespace() - ns.Validate(mc, mk) - - mk.VerifyWasCalledOnce() - assert.Equal(t, "default", ns.Active) - assert.Equal(t, []string{"default"}, ns.Favorites) -} - -func TestNSValidateMissing(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - mk := NewMockKubeSettings() - - ns := config.NewNamespace() - ns.Validate(mc, mk) - - assert.Equal(t, "default", ns.Active) - assert.Equal(t, []string{"default"}, ns.Favorites) -} - -func TestNSValidateNoNS(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), fmt.Errorf("Crap!")) - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"ns1", "ns2"}) - - ns := config.NewNamespace() - ns.Validate(mc, mk) - - mk.VerifyWasCalledOnce() - assert.Equal(t, "default", ns.Active) - assert.Equal(t, []string{"default"}, ns.Favorites) -} - -func TestNSSetActive(t *testing.T) { - allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"} - uu := []struct { - ns string - fav []string - }{ - {"all", []string{"all", "default"}}, - {"ns1", []string{"ns1", "all", "default"}}, - {"ns2", []string{"ns2", "ns1", "all", "default"}}, - {"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}}, - {"ns4", allNS}, - } - - mk := NewMockKubeSettings() - m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS) - - ns := config.NewNamespace() - for _, u := range uu { - err := ns.SetActive(u.ns, mk) - - assert.Nil(t, err) - assert.Equal(t, u.ns, ns.Active) - assert.Equal(t, u.fav, ns.Favorites) - } -} - -func TestNSValidateRmFavs(t *testing.T) { - mc := NewMockConnection() - m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) - mk := NewMockKubeSettings() - - ns := config.NewNamespace() - ns.Favorites = []string{"default", "fred", "blee"} - ns.Validate(mc, mk) - - assert.Equal(t, []string{"default", "fred"}, ns.Favorites) -} diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 99d9c0cf85..b947f8663e 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -4,6 +4,7 @@ package config import ( + "errors" "fmt" "os" "path/filepath" @@ -13,13 +14,11 @@ import ( "gopkg.in/yaml.v2" ) -// K9sPluginsFilePath manages K9s plugins. -var K9sPluginsFilePath = YamlExtension(filepath.Join(K9sHome(), "plugin.yml")) -var K9sPluginDirectory = filepath.Join("k9s", "plugins") +const k9sPluginsDir = "k9s/plugins" // Plugins represents a collection of plugins. type Plugins struct { - Plugin map[string]Plugin `yaml:"plugin"` + Plugins map[string]Plugin `yaml:"plugins"` } // Plugin describes a K9s plugin. @@ -41,22 +40,58 @@ func (p Plugin) String() string { // NewPlugins returns a new plugin. func NewPlugins() Plugins { return Plugins{ - Plugin: make(map[string]Plugin), + Plugins: make(map[string]Plugin), } } // Load K9s plugins. -func (p Plugins) Load() error { - pluginDirs := make([]string, 0, len(xdg.DataDirs)) +func (p Plugins) Load(path string) error { + var errs error + if err := p.load(AppPluginsFile); err != nil { + errs = errors.Join(errs, err) + } + if err := p.load(path); err != nil { + errs = errors.Join(errs, err) + } + for _, dataDir := range xdg.DataDirs { - pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory)) + if err := p.loadPluginDir(filepath.Join(dataDir, k9sPluginsDir)); err != nil { + errs = errors.Join(errs, err) + } } - return p.LoadPlugins(K9sPluginsFilePath, pluginDirs) + return errs } -// LoadPlugins loads plugins from a given file and a set of plugin directories. -func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { +func (p Plugins) loadPluginDir(dir string) error { + pluginFiles, err := os.ReadDir(dir) + if err != nil { + return nil + } + + var errs error + for _, file := range pluginFiles { + if file.IsDir() || !isYamlFile(file.Name()) { + continue + } + pluginFile, err := os.ReadFile(filepath.Join(dir, file.Name())) + if err != nil { + errs = errors.Join(errs, err) + } + var plugin Plugin + if err = yaml.Unmarshal(pluginFile, &plugin); err != nil { + return err + } + p.Plugins[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin + } + + return errs +} + +func (p *Plugins) load(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } f, err := os.ReadFile(path) if err != nil { return err @@ -66,29 +101,8 @@ func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { if err := yaml.Unmarshal(f, &pp); err != nil { return err } - for k, v := range pp.Plugin { - p.Plugin[k] = v - } - - for _, pluginDir := range pluginDirs { - pluginFiles, err := os.ReadDir(pluginDir) - if err != nil { - continue - } - for _, file := range pluginFiles { - if file.IsDir() || !isYamlFile(file.Name()) { - continue - } - pluginFile, err := os.ReadFile(filepath.Join(pluginDir, file.Name())) - if err != nil { - return err - } - var plugin Plugin - if err = yaml.Unmarshal(pluginFile, &plugin); err != nil { - return err - } - p.Plugin[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin - } + for k, v := range pp.Plugins { + p.Plugins[k] = v } return nil diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go index 175a8285ab..f0870e0142 100644 --- a/internal/config/plugin_test.go +++ b/internal/config/plugin_test.go @@ -1,16 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package config_test +package config import ( "testing" - "github.com/derailed/k9s/internal/config" "github.com/stretchr/testify/assert" ) -var pluginYmlTestData = config.Plugin{ +var pluginYmlTestData = Plugin{ Scopes: []string{"po", "dp"}, Args: []string{"-n", "$NAMESPACE", "-boolean"}, ShortCut: "shift-s", @@ -20,7 +19,7 @@ var pluginYmlTestData = config.Plugin{ Background: false, } -var test1YmlTestData = config.Plugin{ +var test1YmlTestData = Plugin{ Scopes: []string{"po", "dp"}, Args: []string{"-n", "$NAMESPACE", "-boolean"}, ShortCut: "shift-s", @@ -30,7 +29,7 @@ var test1YmlTestData = config.Plugin{ Background: false, } -var test2YmlTestData = config.Plugin{ +var test2YmlTestData = Plugin{ Scopes: []string{"svc", "ing"}, Args: []string{"-n", "$NAMESPACE", "-oyaml"}, ShortCut: "shift-r", @@ -41,29 +40,31 @@ var test2YmlTestData = config.Plugin{ } func TestSinglePluginFileLoad(t *testing.T) { - p := config.NewPlugins() - assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"/random/dir/not/exist"})) + p := NewPlugins() + assert.Nil(t, p.load("testdata/plugins.yaml")) + assert.Nil(t, p.loadPluginDir("/random/dir/not/exist")) - assert.Equal(t, 1, len(p.Plugin)) - k, ok := p.Plugin["blah"] + assert.Equal(t, 1, len(p.Plugins)) + k, ok := p.Plugins["blah"] assert.True(t, ok) assert.ObjectsAreEqual(pluginYmlTestData, k) } func TestMultiplePluginFilesLoad(t *testing.T) { - p := config.NewPlugins() - assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"testdata/plugins"})) + p := NewPlugins() + assert.Nil(t, p.load("testdata/plugins.yaml")) + assert.Nil(t, p.loadPluginDir("testdata/plugins")) - testPlugins := map[string]config.Plugin{ + testPlugins := map[string]Plugin{ "blah": pluginYmlTestData, "test1": test1YmlTestData, "test2": test2YmlTestData, } - assert.Equal(t, len(testPlugins), len(p.Plugin)) + assert.Equal(t, len(testPlugins), len(p.Plugins)) for name, expectedPlugin := range testPlugins { - k, ok := p.Plugin[name] + k, ok := p.Plugins[name] assert.True(t, ok) assert.ObjectsAreEqual(expectedPlugin, k) } diff --git a/internal/config/scans.go b/internal/config/scans.go new file mode 100644 index 0000000000..19e970d93f --- /dev/null +++ b/internal/config/scans.go @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +// Labels tracks a collection of labels. +type Labels map[string][]string + +func (l Labels) exclude(k, val string) bool { + vv, ok := l[k] + if !ok { + return false + } + + for _, v := range vv { + if v == val { + return true + } + } + + return false +} + +// Blacklist tracks vul scan exclusions. +type BlackList struct { + Namespaces []string `yaml:"namespaces"` + Labels Labels `yaml:"labels"` +} + +func newBlackList() BlackList { + return BlackList{ + Labels: make(Labels), + } +} + +func (b BlackList) exclude(ns string, ll map[string]string) bool { + for _, nss := range b.Namespaces { + if nss == ns { + return true + } + } + for k, v := range ll { + if b.Labels.exclude(k, v) { + return true + } + } + + return false +} + +// ImageScans tracks vul scans options. +type ImageScans struct { + Enable bool `yaml:"enable"` + BlackList BlackList `yaml:"blackList"` +} + +// NewImageScans returns a new instance. +func NewImageScans() *ImageScans { + return &ImageScans{ + BlackList: newBlackList(), + } +} + +// ShouldExclude checks if scan should be excluder given ns/labels +func (i *ImageScans) ShouldExclude(ns string, ll map[string]string) bool { + if !i.Enable { + return false + } + + return i.BlackList.exclude(ns, ll) +} diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index 919cedae76..19ad05a0c2 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -5,6 +5,7 @@ package config import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config/data" v1 "k8s.io/api/core/v1" ) @@ -36,7 +37,7 @@ func NewShellPod() *ShellPod { } // Validate validates the configuration. -func (s *ShellPod) Validate(client.Connection, KubeSettings) { +func (s *ShellPod) Validate(client.Connection, data.KubeSettings) { if s.Image == "" { s.Image = defaultDockerShellImage } diff --git a/internal/config/styles.go b/internal/config/styles.go index cefefcf085..2ad1956ca3 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -5,16 +5,12 @@ package config import ( "os" - "path/filepath" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "gopkg.in/yaml.v2" ) -// K9sStylesFile represents K9s skins file location. -var K9sStylesFile = YamlExtension(filepath.Join(K9sHome(), "skin.yml")) - // StyleListener represents a skin's listener. type StyleListener interface { // StylesChanged notifies listener the skin changed. @@ -434,6 +430,11 @@ func newMenu() Menu { // NewStyles creates a new default config. func NewStyles() *Styles { + var s Styles + if err := yaml.Unmarshal(stockSkinTpl, &s); err == nil { + return &s + } + return &Styles{ K9s: newStyle(), } @@ -446,7 +447,6 @@ func (s *Styles) Reset() { // DefaultSkin loads the default skin. func (s *Styles) DefaultSkin() { - s.K9s = newStyle() } // FgColor returns the foreground color. @@ -545,7 +545,6 @@ func (s *Styles) Load(path string) error { if err := yaml.Unmarshal(f, s); err != nil { return err } - // s.fireStylesChanged() return nil } diff --git a/internal/config/styles_test.go b/internal/config/styles_test.go index d3d8874a1e..572581c164 100644 --- a/internal/config/styles_test.go +++ b/internal/config/styles_test.go @@ -30,7 +30,7 @@ func TestColor(t *testing.T) { func TestSkinNone(t *testing.T) { s := config.NewStyles() - assert.Nil(t, s.Load("testdata/empty_skin.yml")) + assert.Nil(t, s.Load("testdata/empty_skin.yaml")) s.Update() assert.Equal(t, "#5f9ea0", s.Body().FgColor.String()) @@ -43,7 +43,7 @@ func TestSkinNone(t *testing.T) { func TestSkin(t *testing.T) { s := config.NewStyles() - assert.Nil(t, s.Load("testdata/black_and_wtf.yml")) + assert.Nil(t, s.Load("testdata/black_and_wtf.yaml")) s.Update() assert.Equal(t, "#ffffff", s.Body().FgColor.String()) @@ -56,10 +56,10 @@ func TestSkin(t *testing.T) { func TestSkinNotExits(t *testing.T) { s := config.NewStyles() - assert.NotNil(t, s.Load("testdata/blee.yml")) + assert.NotNil(t, s.Load("testdata/blee.yaml")) } func TestSkinBoarked(t *testing.T) { s := config.NewStyles() - assert.NotNil(t, s.Load("testdata/skin_boarked.yml")) + assert.NotNil(t, s.Load("testdata/skin_boarked.yaml")) } diff --git a/internal/config/templates/aliases.yaml b/internal/config/templates/aliases.yaml new file mode 100644 index 0000000000..81c781f970 --- /dev/null +++ b/internal/config/templates/aliases.yaml @@ -0,0 +1,9 @@ +aliases: + dp: apps/v1/deployments + sec: v1/secrets + jo: batch/v1/jobs + cr: rbac.authorization.k8s.io/v1/clusterroles + crb: rbac.authorization.k8s.io/v1/clusterrolebindings + ro: rbac.authorization.k8s.io/v1/roles + rb: rbac.authorization.k8s.io/v1/rolebindings + np: networking.k8s.io/v1/networkpolicies diff --git a/internal/config/templates/benchmarks.yaml b/internal/config/templates/benchmarks.yaml new file mode 100644 index 0000000000..9efba4cf3e --- /dev/null +++ b/internal/config/templates/benchmarks.yaml @@ -0,0 +1,4 @@ +benchmarks: + defaults: + concurrency: 2 + requests: 200 \ No newline at end of file diff --git a/internal/config/templates/hotkeys.yaml b/internal/config/templates/hotkeys.yaml new file mode 100644 index 0000000000..5c2723b673 --- /dev/null +++ b/internal/config/templates/hotkeys.yaml @@ -0,0 +1,6 @@ +hotKeys: + # Examples... + # shift-0: + # shortCut: Shift-0 + # description: View Workloads + # command: wk k8s-app=cilium \ No newline at end of file diff --git a/internal/config/templates/stock-skin.yaml b/internal/config/templates/stock-skin.yaml new file mode 100644 index 0000000000..02e68ab280 --- /dev/null +++ b/internal/config/templates/stock-skin.yaml @@ -0,0 +1,97 @@ +# ----------------------------------------------------------------------------- +# Stock skin +# ----------------------------------------------------------------------------- + +# Skin... +k9s: + body: + fgColor: cadetblue + bgColor: black + logoColor: orange + logoColorMsg: white + logoColorInfo: green + logoColorWarn: mediumvioletred + logoColorError: red + prompt: + fgColor: cadetblue + bgColor: black + suggestColor: dodgerblue + border: + default: seagreen + command: aqua + info: + fgColor: orange + sectionColor: white + dialog: + fgColor: dodgerblue + bgColor: black + buttonFgColor: black + buttonBgColor: dodgerblue + buttonFocusFgColor: white + buttonFocusBgColor: fuchsia + labelFgColor: fuchsia + fieldFgColor: dodgerblue + frame: + border: + fgColor: dodgerblue + focusColor: aqua + menu: + fgColor: white + keyColor: dodgerblue + numKeyColor: fuchsia + crumbs: + fgColor: black + bgColor: aqua + activeColor: orange + status: + newColor: lightskyblue + modifyColor: greenyellow + addColor: white + errorColor: orangered + pendingColor: darkorange + highlightColor: aqua + killColor: mediumpurple + completedColor: gray + title: + fgColor: aqua + highlightColor: fuchsia + counterColor: papayawhip + filterColor: steelblue + views: + # Charts skins... + charts: + bgColor: black + defaultDialColors: + - linegreen + - orangered + defaultChartColors: + - linegreen + - orangered + table: + fgColor: aqua + bgColor: black + cursorFgColor: white + cursorBgColor: black + markColor: darkgoldenrod + header: + fgColor: lightGray + bgColor: black + sorterColor: orange + xray: + fgColor: blue + bgColor: black + cursorColor: aqua + graphicColor: darkgoldenrod + showIcons: false + yaml: + keyColor: steelblue + colonColor: white + valueColor: papayawhip + logs: + fgColor: white + bgColor: black + indicator: + fgColor: dodgerblue + bgColor: black + toggleOnColor: limegreen + toggleOffColor: steelblue \ No newline at end of file diff --git a/internal/config/testdata/alias.yml b/internal/config/testdata/alias.yaml similarity index 83% rename from internal/config/testdata/alias.yml rename to internal/config/testdata/alias.yaml index 10835dee0e..185113e7da 100644 --- a/internal/config/testdata/alias.yml +++ b/internal/config/testdata/alias.yaml @@ -1,3 +1,3 @@ -alias: +aliases: dp: "apps.v1.deployments" pe: ".v1.pods" diff --git a/internal/config/testdata/b_containers.yml b/internal/config/testdata/b_containers.yaml similarity index 100% rename from internal/config/testdata/b_containers.yml rename to internal/config/testdata/b_containers.yaml diff --git a/internal/config/testdata/b_containers_1.yml b/internal/config/testdata/b_containers_1.yaml similarity index 100% rename from internal/config/testdata/b_containers_1.yml rename to internal/config/testdata/b_containers_1.yaml diff --git a/internal/config/testdata/b_good.yml b/internal/config/testdata/b_good.yaml similarity index 100% rename from internal/config/testdata/b_good.yml rename to internal/config/testdata/b_good.yaml diff --git a/internal/config/testdata/b_toast.yml b/internal/config/testdata/b_toast.yaml similarity index 100% rename from internal/config/testdata/b_toast.yml rename to internal/config/testdata/b_toast.yaml diff --git a/internal/config/testdata/bench-fred.yml b/internal/config/testdata/bench-fred.yaml similarity index 100% rename from internal/config/testdata/bench-fred.yml rename to internal/config/testdata/bench-fred.yaml diff --git a/internal/config/testdata/black_and_wtf.yml b/internal/config/testdata/black_and_wtf.yaml similarity index 100% rename from internal/config/testdata/black_and_wtf.yml rename to internal/config/testdata/black_and_wtf.yaml diff --git a/internal/config/testdata/empty_skin.yml b/internal/config/testdata/empty_skin.yaml similarity index 100% rename from internal/config/testdata/empty_skin.yml rename to internal/config/testdata/empty_skin.yaml diff --git a/internal/config/testdata/hot_key.yml b/internal/config/testdata/hotkeys.yaml similarity index 90% rename from internal/config/testdata/hot_key.yml rename to internal/config/testdata/hotkeys.yaml index 81f16319e4..255555b3e1 100644 --- a/internal/config/testdata/hot_key.yml +++ b/internal/config/testdata/hotkeys.yaml @@ -1,4 +1,4 @@ -hotKey: +hotKeys: pods: shortCut: shift-0 description: Launch pod view diff --git a/internal/config/testdata/k8s.yml b/internal/config/testdata/k8s.yaml similarity index 100% rename from internal/config/testdata/k8s.yml rename to internal/config/testdata/k8s.yaml diff --git a/internal/config/testdata/k9s.yml b/internal/config/testdata/k9s.yaml similarity index 91% rename from internal/config/testdata/k9s.yml rename to internal/config/testdata/k9s.yaml index e98a2180cf..8bb5df2e1e 100644 --- a/internal/config/testdata/k9s.yml +++ b/internal/config/testdata/k9s.yaml @@ -6,9 +6,9 @@ k9s: tail: 200 buffer: 2000 currentContext: minikube - currentCluster: minikube - clusters: + contexts: minikube: + cluster: minikube namespace: active: kube-system favorites: @@ -20,6 +20,7 @@ k9s: view: active: ctx fred: + cluster: fred namespace: active: default favorites: diff --git a/internal/config/testdata/k9s1.yml b/internal/config/testdata/k9s1.yaml similarity index 100% rename from internal/config/testdata/k9s1.yml rename to internal/config/testdata/k9s1.yaml diff --git a/internal/config/testdata/k9s_old.yml b/internal/config/testdata/k9s_old.yaml similarity index 100% rename from internal/config/testdata/k9s_old.yml rename to internal/config/testdata/k9s_old.yaml diff --git a/internal/config/testdata/k9s_readonly.yml b/internal/config/testdata/k9s_readonly.yaml similarity index 93% rename from internal/config/testdata/k9s_readonly.yml rename to internal/config/testdata/k9s_readonly.yaml index e8c5c1928b..e3edca141e 100644 --- a/internal/config/testdata/k9s_readonly.yml +++ b/internal/config/testdata/k9s_readonly.yaml @@ -5,8 +5,7 @@ k9s: tail: 200 buffer: 2000 currentContext: minikube - currentCluster: minikube - clusters: + contexts: minikube: namespace: active: kube-system diff --git a/internal/config/testdata/k9s_toast.yml b/internal/config/testdata/k9s_toast.yaml similarity index 92% rename from internal/config/testdata/k9s_toast.yml rename to internal/config/testdata/k9s_toast.yaml index 189706a655..ac84a5893f 100644 --- a/internal/config/testdata/k9s_toast.yml +++ b/internal/config/testdata/k9s_toast.yaml @@ -2,8 +2,7 @@ k9s: refreshRate: 2 logBufferSize: 200 currentContext: minikube - currentCluster: minikube - clusters: + contexts: minikube: namespace: active: kube-system diff --git a/internal/config/testdata/kubeconfig-test.yml b/internal/config/testdata/kubeconfig-test.yaml similarity index 73% rename from internal/config/testdata/kubeconfig-test.yml rename to internal/config/testdata/kubeconfig-test.yaml index a3c72a7af9..725d04bc7d 100644 --- a/internal/config/testdata/kubeconfig-test.yml +++ b/internal/config/testdata/kubeconfig-test.yaml @@ -4,19 +4,19 @@ clusters: - cluster: certificate-authority: /Users/test/ca.crt server: https://1.2.3.4:8443 - name: testCluster + name: cl-1 contexts: - context: - cluster: cluster1 + cluster: cl-1 user: user1 - namespace: ns1 - name: test1 + namespace: ns-1 + name: ct-1 - context: - cluster: cluster2 + cluster: cl-1 user: user2 - namespace: ns2 - name: test2 -current-context: test1 + namespace: ns-2 + name: ct-2 +current-context: ct-1 preferences: {} users: - name: user1 diff --git a/internal/config/testdata/plugin.yml b/internal/config/testdata/plugins.yaml similarity index 95% rename from internal/config/testdata/plugin.yml rename to internal/config/testdata/plugins.yaml index 0563f6f827..cfa4748967 100644 --- a/internal/config/testdata/plugin.yml +++ b/internal/config/testdata/plugins.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: blah: shortCut: shift-s confirm: true diff --git a/internal/config/testdata/plugins/test1.yml b/internal/config/testdata/plugins/test1.yaml similarity index 100% rename from internal/config/testdata/plugins/test1.yml rename to internal/config/testdata/plugins/test1.yaml diff --git a/internal/config/testdata/plugins/test2.yml b/internal/config/testdata/plugins/test2.yaml similarity index 100% rename from internal/config/testdata/plugins/test2.yml rename to internal/config/testdata/plugins/test2.yaml diff --git a/internal/config/testdata/skin_boarked.yml b/internal/config/testdata/skin_boarked.yaml similarity index 100% rename from internal/config/testdata/skin_boarked.yml rename to internal/config/testdata/skin_boarked.yaml diff --git a/internal/config/testdata/view_settings.yml b/internal/config/testdata/view_settings.yaml similarity index 100% rename from internal/config/testdata/view_settings.yml rename to internal/config/testdata/view_settings.yaml diff --git a/internal/config/threshold.go b/internal/config/threshold.go index f3300178bf..f15bd8c1fa 100644 --- a/internal/config/threshold.go +++ b/internal/config/threshold.go @@ -5,6 +5,7 @@ package config import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config/data" ) const ( @@ -65,7 +66,7 @@ func NewThreshold() Threshold { } // Validate a namespace is setup correctly. -func (t Threshold) Validate(c client.Connection, ks KubeSettings) { +func (t Threshold) Validate(c client.Connection, ks data.KubeSettings) { for _, k := range []string{"cpu", "memory"} { v, ok := t[k] if !ok { diff --git a/internal/config/types.go b/internal/config/types.go new file mode 100644 index 0000000000..9e8fea59b6 --- /dev/null +++ b/internal/config/types.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +const ( + defaultRefreshRate = 2 + defaultMaxConnRetry = 5 +) + +// UI tracks ui specific configs. +type UI struct { + // EnableMouse toggles mouse support. + EnableMouse bool `yaml:"enableMouse"` + + // Headless toggles top header display. + Headless bool `yaml:"headless"` + + // LogoLess toggles k9s logo. + Logoless bool `yaml:"logoless"` + + // Crumbsless toggles nav crumb display. + Crumbsless bool `yaml:"crumbsless"` + + // NoIcons toggles icons display. + NoIcons bool `yaml:"noIcons"` + + // Skin reference the general k9s skin name. + // Can be overridden per context. + Skin string `yaml:"skin,omitempty"` +} diff --git a/internal/config/views.go b/internal/config/views.go index 74907e4347..ae2ad4798e 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -5,17 +5,13 @@ package config import ( "os" - "path/filepath" "gopkg.in/yaml.v2" ) -// K9sViewConfigFile represents the location for the views configuration. -var K9sViewConfigFile = YamlExtension(filepath.Join(K9sHome(), "views.yml")) - // ViewConfigListener represents a view config listener. type ViewConfigListener interface { - // ConfigChanged notifies listener the view configuration changed. + // ViewSettingsChanged notifies listener the view configuration changed. ViewSettingsChanged(ViewSetting) } @@ -90,6 +86,8 @@ func (v *CustomView) fireConfigChanged() { for gvr, list := range v.listeners { if v, ok := v.K9s.Views[gvr]; ok { list.ViewSettingsChanged(v) + } else { + list.ViewSettingsChanged(ViewSetting{}) } } } diff --git a/internal/config/views_test.go b/internal/config/views_test.go index af3885fe77..c883fed95e 100644 --- a/internal/config/views_test.go +++ b/internal/config/views_test.go @@ -13,7 +13,7 @@ import ( func TestViewSettingsLoad(t *testing.T) { cfg := config.NewCustomView() - assert.Nil(t, cfg.Load("testdata/view_settings.yml")) + assert.Nil(t, cfg.Load("testdata/view_settings.yaml")) assert.Equal(t, 1, len(cfg.K9s.Views)) assert.Equal(t, 4, len(cfg.K9s.Views["v1/pods"].Columns)) } diff --git a/internal/dao/alias.go b/internal/dao/alias.go index bce58361a0..f07b86c655 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -14,6 +14,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/view/cmd" "k8s.io/apimachinery/pkg/runtime" ) @@ -22,21 +23,23 @@ var _ Accessor = (*Alias)(nil) // Alias tracks standard and custom command aliases. type Alias struct { NonResource + *config.Aliases } // NewAlias returns a new set of aliases. func NewAlias(f Factory) *Alias { - a := Alias{Aliases: config.NewAliases()} + a := Alias{ + Aliases: config.NewAliases(), + } a.Init(f, client.NewGVR("aliases")) return &a } // Check verifies an alias is defined for this command. -func (a *Alias) Check(cmd string) bool { - _, ok := a.Aliases.Get(cmd) - return ok +func (a *Alias) Check(cmd string) (string, bool) { + return a.Aliases.Get(cmd) } // List returns a collection of aliases. @@ -49,19 +52,30 @@ func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) { oo := make([]runtime.Object, 0, len(m)) for gvr, aliases := range m { sort.StringSlice(aliases).Sort() - oo = append(oo, render.AliasRes{GVR: gvr, Aliases: aliases}) + oo = append(oo, render.AliasRes{ + GVR: gvr, + Aliases: aliases, + }) } return oo, nil } // AsGVR returns a matching gvr if it exists. -func (a *Alias) AsGVR(cmd string) (client.GVR, bool) { - gvr, ok := a.Aliases.Get(cmd) - if ok { - return client.NewGVR(gvr), true +func (a *Alias) AsGVR(c string) (client.GVR, string, bool) { + exp, ok := a.Aliases.Get(c) + if !ok { + return client.NoGVR, "", ok } - return client.GVR{}, false + p := cmd.NewInterpreter(exp) + if strings.Contains(p.Cmd(), "/") { + return client.NewGVR(p.Cmd()), "", true + } + if gvr, ok := a.Aliases.Get(p.Cmd()); ok { + return client.NewGVR(gvr), exp, true + } + + return client.NoGVR, "", false } // Get fetch a resource. @@ -70,15 +84,15 @@ func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) { } // Ensure makes sure alias are loaded. -func (a *Alias) Ensure() (config.Alias, error) { +func (a *Alias) Ensure(path string) (config.Alias, error) { if err := MetaAccess.LoadResources(a.Factory); err != nil { return config.Alias{}, err } - return a.Alias, a.load() + return a.Alias, a.load(path) } -func (a *Alias) load() error { - if err := a.Load(); err != nil { +func (a *Alias) load(path string) error { + if err := a.Load(path); err != nil { return err } diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go index ceab249822..f28b16db6f 100644 --- a/internal/dao/alias_test.go +++ b/internal/dao/alias_test.go @@ -19,6 +19,48 @@ import ( "k8s.io/client-go/informers" ) +func TestAsGVR(t *testing.T) { + a := dao.NewAlias(makeFactory()) + a.Aliases.Define("v1/pods", "po", "pod", "pods") + a.Aliases.Define("workloads", "workloads", "workload", "wkl") + + uu := map[string]struct { + cmd string + ok bool + gvr client.GVR + }{ + "ok": { + cmd: "pods", + ok: true, + gvr: client.NewGVR("v1/pods"), + }, + "ok-short": { + cmd: "po", + ok: true, + gvr: client.NewGVR("v1/pods"), + }, + "missing": { + cmd: "zorg", + }, + "alias": { + cmd: "wkl", + ok: true, + gvr: client.NewGVR("workloads"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + gvr, _, ok := a.AsGVR(u.cmd) + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.gvr, gvr) + } + }) + } +} + func TestAliasList(t *testing.T) { a := dao.Alias{} a.Init(makeFactory(), client.NewGVR("aliases")) @@ -49,24 +91,24 @@ func makeAliases() *dao.Alias { type testFactory struct{} +func makeFactory() dao.Factory { + return testFactory{} +} + var _ dao.Factory = testFactory{} func (f testFactory) Client() client.Connection { return nil } - func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { return nil, nil } - func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { return nil, nil } - func (f testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) { return nil, nil } - func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { return nil, nil } @@ -75,7 +117,3 @@ func (f testFactory) Forwarders() watch.Forwarders { return nil } func (f testFactory) DeleteForwarder(string) {} - -func makeFactory() dao.Factory { - return testFactory{} -} diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go index 75dcdc6a81..683cb2a4b2 100644 --- a/internal/dao/benchmark.go +++ b/internal/dao/benchmark.go @@ -45,20 +45,21 @@ func (b *Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error if !ok { return nil, errors.New("no benchmark dir found in context") } - path, _ := ctx.Value(internal.KeyPath).(string) + path, ok := ctx.Value(internal.KeyPath).(string) + if !ok { + return nil, errors.New("no path specified in context") + } + pathMatch := BenchRx.ReplaceAllString(strings.Replace(path, "/", "_", 1), "_") ff, err := os.ReadDir(dir) if err != nil { return nil, err } - - fileName := BenchRx.ReplaceAllString(strings.Replace(path, "/", "_", 1), "_") oo := make([]runtime.Object, 0, len(ff)) for _, f := range ff { - if path != "" && !strings.HasPrefix(f.Name(), fileName) { + if !strings.HasPrefix(f.Name(), pathMatch) { continue } - if fi, err := f.Info(); err == nil { oo = append(oo, render.BenchInfo{File: fi, Path: filepath.Join(dir, f.Name())}) } diff --git a/internal/dao/benchmark_test.go b/internal/dao/benchmark_test.go index 0d4aae08c8..12fed066d8 100644 --- a/internal/dao/benchmark_test.go +++ b/internal/dao/benchmark_test.go @@ -19,6 +19,7 @@ func TestBenchmarkList(t *testing.T) { a.Init(makeFactory(), client.NewGVR("benchmarks")) ctx := context.WithValue(context.Background(), internal.KeyDir, "testdata/bench") + ctx = context.WithValue(ctx, internal.KeyPath, "") oo, err := a.List(ctx, "-") assert.Nil(t, err) diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go index 079e880450..6dcc8d25dd 100644 --- a/internal/dao/cluster.go +++ b/internal/dao/cluster.go @@ -19,7 +19,7 @@ type RefScanner interface { // Init initializes the scanner Init(Factory, client.GVR) // Scan scan the resource for references. - Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) + Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) } @@ -58,7 +58,7 @@ func ScanForRefs(ctx context.Context, f Factory) (Refs, error) { log.Debug().Msgf("Cluster Scan %v", time.Since(t)) }(time.Now()) - gvr, ok := ctx.Value(internal.KeyGVR).(string) + gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) if !ok { return nil, errors.New("expecting context GVR") } diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index 6765599942..25877dfd08 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -62,12 +62,14 @@ func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return n func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error) { return "", false, nil } -func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil } -func (c *conn) CurrentNamespaceName() (string, error) { return "", nil } -func (c *conn) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil } -func (c *conn) ActiveCluster() string { return "" } -func (c *conn) ActiveNamespace() string { return "" } -func (c *conn) IsActiveNamespace(string) bool { return false } +func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil } +func (c *conn) CurrentNamespaceName() (string, error) { return "", nil } +func (c *conn) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil } +func (c *conn) ActiveContext() string { return "" } +func (c *conn) ActiveNamespace() string { return "" } +func (c *conn) IsValidNamespace(string) bool { return true } +func (c *conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil } +func (c *conn) IsActiveNamespace(string) bool { return false } type podFactory struct{} diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 290e1bb9ea..99ae897e2e 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -173,7 +173,7 @@ func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error { } // Scan scans for cluster resource refs. -func (c *CronJob) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := c.GetFactory().List(c.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -188,7 +188,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, e return nil, errors.New("expecting CronJob resource") } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&cj.Spec.JobTemplate.Spec.Template.Spec, n) { continue } @@ -196,7 +196,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, e GVR: c.GVR(), FQN: client.FQN(cj.Namespace, cj.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(c.Factory, &cj.Spec.JobTemplate.Spec.Template.Spec, cj.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("locate secret %q", fqn) @@ -209,7 +209,7 @@ func (c *CronJob) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, e GVR: c.GVR(), FQN: client.FQN(cj.Namespace, cj.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&cj.Spec.JobTemplate.Spec.Template.Spec, n) { continue } diff --git a/internal/dao/describe.go b/internal/dao/describe.go index 32db74b440..d990dfd405 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -26,7 +26,7 @@ func Describe(c client.Connection, gvr client.GVR, path string) (string, error) ns, n := client.Namespaced(path) if client.IsClusterScoped(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind) if err != nil { diff --git a/internal/dao/dp.go b/internal/dao/dp.go index db1680aa2b..2eb5e56bbf 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -194,7 +194,7 @@ func (d *Deployment) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, e } // Scan scans for resource references. -func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -209,7 +209,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs return nil, errors.New("expecting Deployment resource") } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&dp.Spec.Template.Spec, n) { continue } @@ -217,7 +217,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs GVR: d.GVR(), FQN: client.FQN(dp.Namespace, dp.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(d.Factory, &dp.Spec.Template.Spec, dp.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("scanning secret %q", fqn) @@ -230,7 +230,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs GVR: d.GVR(), FQN: client.FQN(dp.Namespace, dp.Name), }) - case "v1/persistentvolumeclaims": + case PvcGVR: if !hasPVC(&dp.Spec.Template.Spec, n) { continue } @@ -238,7 +238,7 @@ func (d *Deployment) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs GVR: d.GVR(), FQN: client.FQN(dp.Namespace, dp.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&dp.Spec.Template.Spec, n) { continue } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 44c84e3915..0850a8ab3e 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -214,7 +214,7 @@ func (d *DaemonSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, er } // Scan scans for cluster refs. -func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -229,7 +229,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, return nil, errors.New("expecting StatefulSet resource") } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&ds.Spec.Template.Spec, n) { continue } @@ -237,7 +237,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, GVR: d.GVR(), FQN: client.FQN(ds.Namespace, ds.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(d.Factory, &ds.Spec.Template.Spec, ds.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("locate secret %q", fqn) @@ -250,7 +250,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, GVR: d.GVR(), FQN: client.FQN(ds.Namespace, ds.Name), }) - case "v1/persistentvolumeclaims": + case PvcGVR: if !hasPVC(&ds.Spec.Template.Spec, n) { continue } @@ -258,7 +258,7 @@ func (d *DaemonSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, GVR: d.GVR(), FQN: client.FQN(ds.Namespace, ds.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&ds.Spec.Template.Spec, n) { continue } diff --git a/internal/dao/generic.go b/internal/dao/generic.go index 6d3e7f411d..489c3f835c 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -40,7 +40,7 @@ type Generic struct { func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) { labelSel, _ := ctx.Value(internal.KeyLabels).(string) if client.IsAllNamespace(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } var ( diff --git a/internal/dao/job.go b/internal/dao/job.go index 4f70acc700..1444929a34 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -133,7 +133,7 @@ func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { } // Scan scans for resource references. -func (j *Job) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := j.GetFactory().List(j.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -148,7 +148,7 @@ func (j *Job) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error return nil, errors.New("expecting Job resource") } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&job.Spec.Template.Spec, n) { continue } @@ -156,7 +156,7 @@ func (j *Job) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: j.GVR(), FQN: client.FQN(job.Namespace, job.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(j.Factory, &job.Spec.Template.Spec, job.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("locate secret %q", fqn) @@ -169,7 +169,7 @@ func (j *Job) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: j.GVR(), FQN: client.FQN(job.Namespace, job.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&job.Spec.Template.Spec, n) { continue } diff --git a/internal/dao/node.go b/internal/dao/node.go index 6d2984679d..4f8058f4d5 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -189,7 +189,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) { // CountPods counts the pods scheduled on a given node. func (n *Node) CountPods(nodeName string) (int, error) { var count int - oo, err := n.GetFactory().List("v1/pods", client.AllNamespaces, false, labels.Everything()) + oo, err := n.GetFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) if err != nil { return 0, err } @@ -213,7 +213,7 @@ func (n *Node) CountPods(nodeName string) (int, error) { // GetPods returns all pods running on given node. func (n *Node) GetPods(nodeName string) ([]*v1.Pod, error) { - oo, err := n.GetFactory().List("v1/pods", client.AllNamespaces, false, labels.Everything()) + oo, err := n.GetFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/ns.go b/internal/dao/ns.go index 492e3df4de..4daec5f9bc 100644 --- a/internal/dao/ns.go +++ b/internal/dao/ns.go @@ -18,7 +18,7 @@ type Namespace struct { Generic } -// List returns a collection of nodes. +// List returns a collection of namespaces. func (n *Namespace) List(ctx context.Context, ns string) ([]runtime.Object, error) { oo, err := n.Generic.List(ctx, ns) if err != nil { diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 05ef713e6a..d4404f5f0b 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -95,10 +95,10 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { } var pmx client.PodsMetricsMap - if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok { + if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); false && ok && withMx { pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetricsMap(ctx, ns) } - sel, _ := ctx.Value(internal.KeyFields).(string) + sel, _ := ctx.Value(internal.KeyLabels).(string) fsel, err := labels.ConvertSelectorToLabelsMap(sel) if err != nil { return nil, err @@ -268,7 +268,7 @@ func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { } // Scan scans for cluster resource refs. -func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := p.GetFactory().List(p.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -287,7 +287,7 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error continue } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&pod.Spec, n) { continue } @@ -295,7 +295,7 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: p.GVR(), FQN: client.FQN(pod.Namespace, pod.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(p.Factory, &pod.Spec, pod.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("locate secret %q", fqn) @@ -308,7 +308,7 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: p.GVR(), FQN: client.FQN(pod.Namespace, pod.Name), }) - case "v1/persistentvolumeclaims": + case PvcGVR: if !hasPVC(&pod.Spec, n) { continue } @@ -316,7 +316,7 @@ func (p *Pod) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error GVR: p.GVR(), FQN: client.FQN(pod.Namespace, pod.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&pod.Spec, n) { continue } @@ -536,6 +536,8 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { switch render.PodStatus(&pod) { case render.PhaseCompleted: fallthrough + case render.PhasePending: + fallthrough case render.PhaseCrashLoop: fallthrough case render.PhaseError: @@ -543,9 +545,11 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { case render.PhaseImagePullBackOff: fallthrough case render.PhaseOOMKilled: + // !!BOZO!! Might need to bump timeout otherwise rev limit if too many?? log.Debug().Msgf("Sanitizing %s:%s", pod.Namespace, pod.Name) fqn := client.FQN(pod.Namespace, pod.Name) - if err := p.Resource.Delete(ctx, fqn, nil, NowGrace); err != nil { + if err := p.Delete(ctx, fqn, nil, 0); err != nil { + log.Debug().Msgf("Aborted! Sanitizer deleted %d pods", count) return count, err } count++ diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 2f50906c74..431065904d 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -68,9 +68,9 @@ func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) flags.Sections = §ions flags.ActiveNamespace = &ns } - spinach := cfg.YamlExtension(filepath.Join(cfg.K9sHome(), "spinach.yml")) + spinach := cfg.YamlExtension(filepath.Join(cfg.K9sHome(), "spinach.yaml")) if c, err := p.GetFactory().Client().Config().CurrentContextName(); err == nil { - spinach = cfg.YamlExtension(filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yml", c))) + spinach = cfg.YamlExtension(filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yaml", c))) } if _, err := os.Stat(spinach); err == nil { flags.Spinach = &spinach diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 3c62ccdc95..4efa7509ef 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -38,14 +38,14 @@ func (p *PortForward) Delete(_ context.Context, path string, _ *metav1.DeletionP // List returns a collection of port forwards. func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, error) { benchFile, ok := ctx.Value(internal.KeyBenchCfg).(string) - if !ok { - return nil, fmt.Errorf("no bench file found in context") + if !ok || benchFile == "" { + return nil, fmt.Errorf("no benchmark config file found in context") } path, _ := ctx.Value(internal.KeyPath).(string) config, err := config.NewBench(benchFile) if err != nil { - log.Warn().Msgf("No custom benchmark config file found") + log.Debug().Msgf("No custom benchmark config file found: %q", benchFile) } ff, cc := p.GetFactory().Forwarders(), config.Benchmarks.Containers @@ -92,7 +92,7 @@ func BenchConfigFor(benchFile, path string) config.BenchConfig { def := config.DefaultBenchSpec() cust, err := config.NewBench(benchFile) if err != nil { - log.Debug().Msgf("No custom benchmark config file found") + log.Debug().Msgf("No custom benchmark config file found. Using default: %q", benchFile) return def } if b, ok := cust.Benchmarks.Containers[PodToKey(path)]; ok { diff --git a/internal/dao/port_forward_test.go b/internal/dao/port_forward_test.go index 1fbb0d4721..2ce4de4234 100644 --- a/internal/dao/port_forward_test.go +++ b/internal/dao/port_forward_test.go @@ -17,7 +17,7 @@ func TestBenchForConfig(t *testing.T) { spec config.BenchConfig }{ "no_file": {file: "", key: "", spec: config.DefaultBenchSpec()}, - "spec": {file: "testdata/benchspec.yml", key: "default/nginx-123-456|nginx", spec: config.BenchConfig{ + "spec": {file: "testdata/benchspec.yaml", key: "default/nginx-123-456|nginx", spec: config.BenchConfig{ C: 2, N: 3000, HTTP: config.HTTP{ diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 3c6d2928a3..19382e58e4 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -15,7 +15,6 @@ import ( "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -52,8 +51,8 @@ func (p *PortForwarder) String() string { } // Age returns the port forward age. -func (p *PortForwarder) Age() string { - return time.Since(p.age).String() +func (p *PortForwarder) Age() time.Time { + return p.age } // Active returns the forward status. @@ -191,8 +190,8 @@ func codec() (serializer.CodecFactory, runtime.ParameterCodec) { scheme := runtime.NewScheme() gv := schema.GroupVersion{Group: "", Version: "v1"} metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) + scheme.AddKnownTypes(gv, &metav1.Table{}, &metav1.TableOptions{}) + scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{}) return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) } diff --git a/internal/dao/rbac.go b/internal/dao/rbac.go index b9637d7a79..68dcaddb64 100644 --- a/internal/dao/rbac.go +++ b/internal/dao/rbac.go @@ -36,7 +36,7 @@ type Rbac struct { // List lists out rbac resources. func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) { - gvr, ok := ctx.Value(internal.KeyGVR).(string) + gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) if !ok { return nil, fmt.Errorf("expecting a context gvr") } @@ -45,8 +45,7 @@ func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) { return r.Resource.List(ctx, ns) } - res := client.NewGVR(gvr) - switch res.R() { + switch gvr.R() { case "clusterrolebindings": return r.loadClusterRoleBinding(path) case "rolebindings": @@ -56,7 +55,7 @@ func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) { case "roles": return r.loadRole(path) default: - return nil, fmt.Errorf("expecting clusterrole/role but found %s", res.R()) + return nil, fmt.Errorf("expecting clusterrole/role but found %s", gvr.R()) } } diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go index 3233c0ba2b..e74f5b7718 100644 --- a/internal/dao/rbac_policy.go +++ b/internal/dao/rbac_policy.go @@ -181,13 +181,13 @@ func (p *Policy) fetchRoleBindingNamespaces(kind, name string) (map[string]strin // isSameSubject verifies if the incoming type name and namespace match a subject from a // cluster/roleBinding. A ServiceAccount will always have a namespace and needs to be validated to ensure // we don't display permissions for a ServiceAccount with the same name in a different namespace -func isSameSubject(kind, namespace, name string, subject *rbacv1.Subject) bool { +func isSameSubject(kind, ns, name string, subject *rbacv1.Subject) bool { if subject.Kind != kind || subject.Name != name { return false } if kind == rbacv1.ServiceAccountKind { // Kind and name were checked above, check the namespace - return subject.Namespace == namespace + return client.IsAllNamespaces(ns) || subject.Namespace == ns } return true } @@ -215,7 +215,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { func (p *Policy) fetchRoles() ([]rbacv1.Role, error) { const gvr = "rbac.authorization.k8s.io/v1/roles" - oo, err := p.GetFactory().List(gvr, client.AllNamespaces, false, labels.Everything()) + oo, err := p.GetFactory().List(gvr, client.BlankNamespace, false, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/reference.go b/internal/dao/reference.go index 25db064a9b..8ea76f5159 100644 --- a/internal/dao/reference.go +++ b/internal/dao/reference.go @@ -22,12 +22,12 @@ type Reference struct { // List collects all references. func (r *Reference) List(ctx context.Context, ns string) ([]runtime.Object, error) { - gvr, ok := ctx.Value(internal.KeyGVR).(string) + gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) if !ok { return nil, errors.New("No context GVR found") } switch gvr { - case "v1/serviceaccounts": + case SaGVR: return r.ScanSA(ctx) default: return r.Scan(ctx) diff --git a/internal/dao/registry.go b/internal/dao/registry.go index aee73f22ab..0fb80a0f8e 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -83,6 +83,7 @@ func NewMeta() *Meta { // Customize here for non resource types or types with metrics or logs. func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { m := Accessors{ + client.NewGVR("workloads"): &Workload{}, client.NewGVR("contexts"): &Context{}, client.NewGVR("containers"): &Container{}, client.NewGVR("scans"): &ImageScan{}, @@ -205,6 +206,14 @@ func loadNonResource(m ResourceMetas) { } func loadK9s(m ResourceMetas) { + m[client.NewGVR("workloads")] = metav1.APIResource{ + Name: "workloads", + Kind: "Workload", + SingularName: "workload", + Namespaced: true, + ShortNames: []string{"wk"}, + Categories: []string{k9sCat}, + } m[client.NewGVR("pulses")] = metav1.APIResource{ Name: "pulses", Kind: "Pulse", diff --git a/internal/dao/rest_mapper.go b/internal/dao/rest_mapper.go index 9e1391d4a9..d9e6f8e3cf 100644 --- a/internal/dao/rest_mapper.go +++ b/internal/dao/rest_mapper.go @@ -28,7 +28,7 @@ func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) { return nil, err } mapper := restmapper.NewDeferredDiscoveryRESTMapper(dial) - expander := restmapper.NewShortcutExpander(mapper, dial) + expander := restmapper.NewShortcutExpander(mapper, dial, nil) return expander, nil } diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 6e4b2fc822..3d3dbb4123 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -215,7 +215,7 @@ func (s *StatefulSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, } // Scan scans for cluster resource refs. -func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Refs, error) { +func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) oo, err := s.GetFactory().List(s.GVR(), ns, wait, labels.Everything()) if err != nil { @@ -230,7 +230,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Ref return nil, errors.New("expecting StatefulSet resource") } switch gvr { - case "v1/configmaps": + case CmGVR: if !hasConfigMap(&sts.Spec.Template.Spec, n) { continue } @@ -238,7 +238,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Ref GVR: s.GVR(), FQN: client.FQN(sts.Namespace, sts.Name), }) - case "v1/secrets": + case SecGVR: found, err := hasSecret(s.Factory, &sts.Spec.Template.Spec, sts.Namespace, n, wait) if err != nil { log.Warn().Err(err).Msgf("locate secret %q", fqn) @@ -251,7 +251,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Ref GVR: s.GVR(), FQN: client.FQN(sts.Namespace, sts.Name), }) - case "v1/persistentvolumeclaims": + case PvcGVR: for _, v := range sts.Spec.VolumeClaimTemplates { if !strings.HasPrefix(n, v.Name+"-"+sts.Name) { continue @@ -268,7 +268,7 @@ func (s *StatefulSet) Scan(ctx context.Context, gvr, fqn string, wait bool) (Ref GVR: s.GVR(), FQN: client.FQN(sts.Namespace, sts.Name), }) - case "scheduling.k8s.io/v1/priorityclasses": + case PcGVR: if !hasPC(&sts.Spec.Template.Spec, n) { continue } diff --git a/internal/dao/table.go b/internal/dao/table.go index 823408f9eb..c2569da183 100644 --- a/internal/dao/table.go +++ b/internal/dao/table.go @@ -10,7 +10,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest" @@ -25,7 +25,7 @@ type Table struct { // Get returns a given resource. func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { - a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) + a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) _, codec := t.codec() c, err := t.getClient() @@ -37,7 +37,7 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { SetHeader("Accept", a). Name(n). Resource(t.gvr.R()). - VersionedParams(&metav1beta1.TableOptions{}, codec) + VersionedParams(&metav1.TableOptions{}, codec) if ns != client.ClusterScope { req = req.Namespace(ns) } @@ -47,12 +47,8 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { // List all Resources in a given namespace. func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { - labelSel, ok := ctx.Value(internal.KeyLabels).(string) - if !ok { - labelSel = "" - } - - a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) + labelSel, _ := ctx.Value(internal.KeyLabels).(string) + a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) _, codec := t.codec() c, err := t.getClient() @@ -103,8 +99,8 @@ func (t *Table) codec() (serializer.CodecFactory, runtime.ParameterCodec) { scheme := runtime.NewScheme() gv := t.gvr.GV() metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) + scheme.AddKnownTypes(gv, &metav1.Table{}, &metav1.TableOptions{IncludeObject: v1.IncludeObject}) + scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{IncludeObject: v1.IncludeObject}) return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) } diff --git a/internal/dao/testdata/benchspec.yml b/internal/dao/testdata/benchspec.yaml similarity index 100% rename from internal/dao/testdata/benchspec.yml rename to internal/dao/testdata/benchspec.yaml diff --git a/internal/dao/testdata/dir/a.yml b/internal/dao/testdata/dir/a.yaml similarity index 100% rename from internal/dao/testdata/dir/a.yml rename to internal/dao/testdata/dir/a.yaml diff --git a/internal/dao/testdata/dir/a/b.yml b/internal/dao/testdata/dir/a/b.yaml similarity index 100% rename from internal/dao/testdata/dir/a/b.yml rename to internal/dao/testdata/dir/a/b.yaml diff --git a/internal/dao/workload.go b/internal/dao/workload.go new file mode 100644 index 0000000000..ae9693b831 --- /dev/null +++ b/internal/dao/workload.go @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "github.com/rs/zerolog/log" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + StatusOK = "OK" + DegradedStatus = "DEGRADED" +) + +var ( + SaGVR = client.NewGVR("v1/serviceaccounts") + PvcGVR = client.NewGVR("v1/persistentvolumeclaims") + PcGVR = client.NewGVR("scheduling.k8s.io/v1/priorityclasses") + CmGVR = client.NewGVR("v1/configmaps") + SecGVR = client.NewGVR("v1/secrets") + PodGVR = client.NewGVR("v1/pods") + SvcGVR = client.NewGVR("v1/services") + DsGVR = client.NewGVR("apps/v1/daemonsets") + StsGVR = client.NewGVR("apps/v1/statefulSets") + DpGVR = client.NewGVR("apps/v1/deployments") + RsGVR = client.NewGVR("apps/v1/replicasets") + resList = []client.GVR{PodGVR, SvcGVR, DsGVR, StsGVR, DpGVR, RsGVR} +) + +// Workload tracks a select set of resources in a given namespace. +type Workload struct { + Table +} + +func (w *Workload) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error { + gvr, _ := ctx.Value(internal.KeyGVR).(client.GVR) + ns, n := client.Namespaced(path) + auth, err := w.Client().CanI(ns, gvr.String(), []string{client.DeleteVerb}) + if err != nil { + return err + } + if !auth { + return fmt.Errorf("user is not authorized to delete %s", path) + } + + var gracePeriod *int64 + if grace != DefaultGrace { + gracePeriod = (*int64)(&grace) + } + opts := metav1.DeleteOptions{ + PropagationPolicy: propagation, + GracePeriodSeconds: gracePeriod, + } + + ctx, cancel := context.WithTimeout(ctx, w.Client().Config().CallTimeout()) + defer cancel() + + d, err := w.Client().DynDial() + if err != nil { + return err + } + dial := d.Resource(gvr.GVR()) + if client.IsClusterScoped(ns) { + return dial.Delete(ctx, n, opts) + } + + return dial.Namespace(ns).Delete(ctx, n, opts) +} + +func (a *Workload) fetch(ctx context.Context, gvr client.GVR, ns string) (*metav1.Table, error) { + a.Table.gvr = gvr + oo, err := a.Table.List(ctx, ns) + if err != nil { + return nil, err + } + if len(oo) == 0 { + return nil, fmt.Errorf("no table found for gvr: %s", gvr) + } + tt, ok := oo[0].(*metav1.Table) + if !ok { + return nil, errors.New("not a metav1.Table") + } + + return tt, nil +} + +// List fetch workloads. +func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error) { + oo := make([]runtime.Object, 0, 100) + for _, gvr := range resList { + table, err := a.fetch(ctx, gvr, ns) + if err != nil { + return nil, err + } + var ( + ns string + ts metav1.Time + ) + for _, r := range table.Rows { + if obj := r.Object.Object; obj != nil { + if m, err := meta.Accessor(obj); err == nil { + ns = m.GetNamespace() + ts = m.GetCreationTimestamp() + } + } else { + var m metav1.PartialObjectMetadata + if err := json.Unmarshal(r.Object.Raw, &m); err == nil { + ns = m.GetNamespace() + ts = m.CreationTimestamp + } + } + oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []interface{}{ + gvr.String(), + ns, + r.Cells[indexOf("Name", table.ColumnDefinitions)], + diagnose(gvr, r, table.ColumnDefinitions), + status(gvr, r, table.ColumnDefinitions), + ts, + }}}) + } + } + + return oo, nil +} + +// Helpers... + +func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { + switch gvr { + case PodGVR, DpGVR, StsGVR: + return r.Cells[indexOf("Ready", h)].(string) + case RsGVR, DsGVR: + c := r.Cells[indexOf("Ready", h)].(int64) + d := r.Cells[indexOf("Desired", h)].(int64) + return fmt.Sprintf("%d/%d", c, d) + case SvcGVR: + return "" + } + + return render.NAValue +} + +func diagnose(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { + switch gvr { + case PodGVR: + if !isReady(r.Cells[indexOf("Ready", h)].(string)) || r.Cells[indexOf("Status", h)] != render.PhaseRunning { + return DegradedStatus + } + case DpGVR, StsGVR: + if !isReady(r.Cells[indexOf("Ready", h)].(string)) { + return DegradedStatus + } + case RsGVR, DsGVR: + rd, ok1 := r.Cells[indexOf("Ready", h)].(int64) + de, ok2 := r.Cells[indexOf("Desired", h)].(int64) + if ok1 && ok2 { + if !isReady(fmt.Sprintf("%d/%d", rd, de)) { + return DegradedStatus + } + break + } + rds, oks1 := r.Cells[indexOf("Ready", h)].(string) + des, oks2 := r.Cells[indexOf("Desired", h)].(string) + if oks1 && oks2 { + if !isReady(fmt.Sprintf("%s/%s", rds, des)) { + return DegradedStatus + } + } + case SvcGVR: + default: + return render.MissingValue + } + + return StatusOK +} + +func isReady(s string) bool { + tt := strings.Split(s, "/") + if len(tt) != 2 { + return false + } + r, err := strconv.Atoi(tt[0]) + if err != nil { + log.Error().Msgf("invalid ready count: %q", tt[0]) + return false + } + c, err := strconv.Atoi(tt[1]) + if err != nil { + log.Error().Msgf("invalid expected count: %q", tt[1]) + return false + } + + if c == 0 { + return true + } + return r == c +} + +func indexOf(n string, defs []metav1.TableColumnDefinition) int { + for i, d := range defs { + if d.Name == n { + return i + } + } + + return -1 +} diff --git a/internal/model/cluster.go b/internal/model/cluster.go index d4d2ce30ec..3182a0e13c 100644 --- a/internal/model/cluster.go +++ b/internal/model/cluster.go @@ -58,7 +58,7 @@ func NewCluster(f dao.Factory) *Cluster { // Version returns the current K8s cluster version. func (c *Cluster) Version() string { info, err := c.factory.Client().ServerVersion() - if err != nil { + if err != nil || info == nil { return client.NA } @@ -74,7 +74,7 @@ func (c *Cluster) ContextName() string { return n } -// ClusterName returns the cluster name. +// ClusterName returns the context name. func (c *Cluster) ClusterName() string { n, err := c.factory.Client().Config().CurrentClusterName() if err != nil { diff --git a/internal/model/fish_buff.go b/internal/model/fish_buff.go index b21e2f497b..1bfb0ae003 100644 --- a/internal/model/fish_buff.go +++ b/internal/model/fish_buff.go @@ -136,5 +136,4 @@ func (f *FishBuff) fireSuggestionChanged(ss []string) { suggest = ss[f.suggestionIndex] } f.SetText(f.GetText(), suggest) - } diff --git a/internal/model/helpers_int_test.go b/internal/model/helpers_int_test.go index 3a21a4d2b4..5c3f39a911 100644 --- a/internal/model/helpers_int_test.go +++ b/internal/model/helpers_int_test.go @@ -4,9 +4,10 @@ package model import ( + "testing" + "github.com/sahilm/fuzzy" "github.com/stretchr/testify/assert" - "testing" ) func Test_rxFilter(t *testing.T) { diff --git a/internal/model/history.go b/internal/model/history.go index 04881796f5..7469e32af6 100644 --- a/internal/model/history.go +++ b/internal/model/history.go @@ -5,8 +5,6 @@ package model import ( "strings" - - "github.com/rs/zerolog/log" ) // MaxHistory tracks max command history. @@ -25,6 +23,14 @@ func NewHistory(limit int) *History { } } +func (h *History) Pop() string { + if h.Empty() { + return "" + } + + return h.commands[0] +} + // List returns the current command history. func (h *History) List() []string { return h.commands @@ -49,7 +55,6 @@ func (h *History) Push(c string) { // Clear clears out the stack. func (h *History) Clear() { - log.Debug().Msgf("History CLEARED!!!") h.commands = nil } diff --git a/internal/model/mock_clustermeta_test.go b/internal/model/mock_clustermeta_test.go deleted file mode 100644 index bec8892605..0000000000 --- a/internal/model/mock_clustermeta_test.go +++ /dev/null @@ -1,869 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/model (interfaces: ClusterMeta) - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package model_test - -import ( - "reflect" - "time" - - client "github.com/derailed/k9s/internal/client" - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - version "k8s.io/apimachinery/pkg/version" - disk "k8s.io/client-go/discovery/cached/disk" - dynamic "k8s.io/client-go/dynamic" - kubernetes "k8s.io/client-go/kubernetes" - rest "k8s.io/client-go/rest" - versioned "k8s.io/metrics/pkg/client/clientset/versioned" -) - -type MockClusterMeta struct { - fail func(message string, callerSkip ...int) -} - -func NewMockClusterMeta(options ...pegomock.Option) *MockClusterMeta { - mock := &MockClusterMeta{} - for _, option := range options { - option.Apply(mock) - } - return mock -} - -func (mock *MockClusterMeta) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } -func (mock *MockClusterMeta) FailHandler() pegomock.FailHandler { return mock.fail } - -func (mock *MockClusterMeta) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CachedDiscovery", params, []reflect.Type{reflect.TypeOf((**disk.CachedDiscoveryClient)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *disk.CachedDiscoveryClient - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*disk.CachedDiscoveryClient) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) CanI(_param0 string, _param1 string, _param2 []string) (bool, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0, _param1, _param2} - result := pegomock.GetGenericMockFrom(mock).Invoke("CanI", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 bool - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) ClusterName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) Config() *client.Config { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**client.Config)(nil)).Elem()}) - var ret0 *client.Config - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*client.Config) - } - } - return ret0 -} - -func (mock *MockClusterMeta) ContextName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ContextName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) CurrentNamespaceName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentNamespaceName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) DialOrDie() kubernetes.Interface { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("DialOrDie", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem()}) - var ret0 kubernetes.Interface - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(kubernetes.Interface) - } - } - return ret0 -} - -func (mock *MockClusterMeta) DynDialOrDie() dynamic.Interface { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("DynDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem()}) - var ret0 dynamic.Interface - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.Interface) - } - } - return ret0 -} - -func (mock *MockClusterMeta) GetNodes() (*v1.NodeList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("GetNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1.NodeList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1.NodeList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) HasMetrics() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockClusterMeta) IsNamespaced(_param0 string) bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("IsNamespaced", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockClusterMeta) MXDial() (*versioned.Clientset, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *versioned.Clientset - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*versioned.Clientset) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) NodePods(_param0 string) (*v1.PodList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("NodePods", params, []reflect.Type{reflect.TypeOf((**v1.PodList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1.PodList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1.PodList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) RestConfigOrDie() *rest.Config { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfigOrDie", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem()}) - var ret0 *rest.Config - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*rest.Config) - } - } - return ret0 -} - -func (mock *MockClusterMeta) ServerVersion() (*version.Info, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ServerVersion", params, []reflect.Type{reflect.TypeOf((**version.Info)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *version.Info - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*version.Info) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) SupportsRes(_param0 string, _param1 []string) (string, bool, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0, _param1} - result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsRes", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 bool - var ret2 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(bool) - } - if result[2] != nil { - ret2 = result[2].(error) - } - } - return ret0, ret1, ret2 -} - -func (mock *MockClusterMeta) SupportsResource(_param0 string) bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsResource", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockClusterMeta) SwitchContext(_param0 string) error { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{_param0} - pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) - - return nil -} - -func (mock *MockClusterMeta) UserName() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("UserName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) ValidNamespaces() ([]v1.Namespace, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 []v1.Namespace - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].([]v1.Namespace) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) Version() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Version", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 string - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockClusterMeta) VerifyWasCalledOnce() *VerifierMockClusterMeta { - return &VerifierMockClusterMeta{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockClusterMeta) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockClusterMeta { - return &VerifierMockClusterMeta{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockClusterMeta) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockClusterMeta { - return &VerifierMockClusterMeta{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockClusterMeta) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockClusterMeta { - return &VerifierMockClusterMeta{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMockClusterMeta struct { - mock *MockClusterMeta - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMockClusterMeta) CachedDiscovery() *MockClusterMeta_CachedDiscovery_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CachedDiscovery", params, verifier.timeout) - return &MockClusterMeta_CachedDiscovery_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_CachedDiscovery_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_CachedDiscovery_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_CachedDiscovery_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) CanI(_param0 string, _param1 string, _param2 []string) *MockClusterMeta_CanI_OngoingVerification { - params := []pegomock.Param{_param0, _param1, _param2} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanI", params, verifier.timeout) - return &MockClusterMeta_CanI_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_CanI_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_CanI_OngoingVerification) GetCapturedArguments() (string, string, []string) { - _param0, _param1, _param2 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] -} - -func (c *MockClusterMeta_CanI_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([]string, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(string) - } - _param2 = make([][]string, len(params[2])) - for u, param := range params[2] { - _param2[u] = param.([]string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) ClusterName() *MockClusterMeta_ClusterName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterName", params, verifier.timeout) - return &MockClusterMeta_ClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_ClusterName_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_ClusterName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_ClusterName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) Config() *MockClusterMeta_Config_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) - return &MockClusterMeta_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_Config_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_Config_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_Config_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) ContextName() *MockClusterMeta_ContextName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ContextName", params, verifier.timeout) - return &MockClusterMeta_ContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_ContextName_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_ContextName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_ContextName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) CurrentNamespaceName() *MockClusterMeta_CurrentNamespaceName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout) - return &MockClusterMeta_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_CurrentNamespaceName_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) DialOrDie() *MockClusterMeta_DialOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout) - return &MockClusterMeta_DialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_DialOrDie_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_DialOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_DialOrDie_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) DynDialOrDie() *MockClusterMeta_DynDialOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDialOrDie", params, verifier.timeout) - return &MockClusterMeta_DynDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_DynDialOrDie_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) GetNodes() *MockClusterMeta_GetNodes_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetNodes", params, verifier.timeout) - return &MockClusterMeta_GetNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_GetNodes_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_GetNodes_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_GetNodes_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) HasMetrics() *MockClusterMeta_HasMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) - return &MockClusterMeta_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_HasMetrics_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_HasMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_HasMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) IsNamespaced(_param0 string) *MockClusterMeta_IsNamespaced_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "IsNamespaced", params, verifier.timeout) - return &MockClusterMeta_IsNamespaced_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_IsNamespaced_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_IsNamespaced_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockClusterMeta_IsNamespaced_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) MXDial() *MockClusterMeta_MXDial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) - return &MockClusterMeta_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_MXDial_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_MXDial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_MXDial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) NodePods(_param0 string) *MockClusterMeta_NodePods_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodePods", params, verifier.timeout) - return &MockClusterMeta_NodePods_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_NodePods_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_NodePods_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockClusterMeta_NodePods_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) RestConfigOrDie() *MockClusterMeta_RestConfigOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfigOrDie", params, verifier.timeout) - return &MockClusterMeta_RestConfigOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_RestConfigOrDie_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_RestConfigOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_RestConfigOrDie_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) ServerVersion() *MockClusterMeta_ServerVersion_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ServerVersion", params, verifier.timeout) - return &MockClusterMeta_ServerVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_ServerVersion_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_ServerVersion_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_ServerVersion_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) SupportsRes(_param0 string, _param1 []string) *MockClusterMeta_SupportsRes_OngoingVerification { - params := []pegomock.Param{_param0, _param1} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SupportsRes", params, verifier.timeout) - return &MockClusterMeta_SupportsRes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_SupportsRes_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_SupportsRes_OngoingVerification) GetCapturedArguments() (string, []string) { - _param0, _param1 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1] -} - -func (c *MockClusterMeta_SupportsRes_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 [][]string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([][]string, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.([]string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) SupportsResource(_param0 string) *MockClusterMeta_SupportsResource_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SupportsResource", params, verifier.timeout) - return &MockClusterMeta_SupportsResource_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_SupportsResource_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_SupportsResource_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockClusterMeta_SupportsResource_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) SwitchContextOrDie(_param0 string) *MockClusterMeta_SwitchContextOrDie_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContextOrDie", params, verifier.timeout) - return &MockClusterMeta_SwitchContextOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_SwitchContextOrDie_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_SwitchContextOrDie_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockClusterMeta_SwitchContextOrDie_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockClusterMeta) UserName() *MockClusterMeta_UserName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UserName", params, verifier.timeout) - return &MockClusterMeta_UserName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_UserName_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_UserName_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_UserName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) ValidNamespaces() *MockClusterMeta_ValidNamespaces_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) - return &MockClusterMeta_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_ValidNamespaces_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_ValidNamespaces_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockClusterMeta) Version() *MockClusterMeta_Version_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout) - return &MockClusterMeta_Version_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_Version_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_Version_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_Version_OngoingVerification) GetAllCapturedArguments() { -} diff --git a/internal/model/mock_connection_test.go b/internal/model/mock_connection_test.go deleted file mode 100644 index e0ed4c9a60..0000000000 --- a/internal/model/mock_connection_test.go +++ /dev/null @@ -1,655 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/client (interfaces: Connection) - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package model_test - -import ( - client "github.com/derailed/k9s/internal/client" - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - version "k8s.io/apimachinery/pkg/version" - disk "k8s.io/client-go/discovery/cached/disk" - dynamic "k8s.io/client-go/dynamic" - kubernetes "k8s.io/client-go/kubernetes" - rest "k8s.io/client-go/rest" - versioned "k8s.io/metrics/pkg/client/clientset/versioned" - "reflect" - "time" -) - -type MockConnection struct { - fail func(message string, callerSkip ...int) -} - -func NewMockConnection(options ...pegomock.Option) *MockConnection { - mock := &MockConnection{} - for _, option := range options { - option.Apply(mock) - } - return mock -} - -func (mock *MockConnection) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } -func (mock *MockConnection) FailHandler() pegomock.FailHandler { return mock.fail } - -func (mock *MockConnection) ActiveCluster() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveCluster", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()}) - var ret0 string - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - } - return ret0 -} - -func (mock *MockConnection) ActiveNamespace() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveNamespace", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()}) - var ret0 string - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(string) - } - } - return ret0 -} - -func (mock *MockConnection) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CachedDiscovery", params, []reflect.Type{reflect.TypeOf((**disk.CachedDiscoveryClient)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *disk.CachedDiscoveryClient - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*disk.CachedDiscoveryClient) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) CanI(_param0 string, _param1 string, _param2 []string) (bool, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0, _param1, _param2} - result := pegomock.GetGenericMockFrom(mock).Invoke("CanI", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 bool - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) CheckConnectivity() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("CheckConnectivity", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) Config() *client.Config { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**client.Config)(nil)).Elem()}) - var ret0 *client.Config - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*client.Config) - } - } - return ret0 -} - -func (mock *MockConnection) ConnectionOK() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ConnectionOK", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) Dial() (kubernetes.Interface, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Dial", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 kubernetes.Interface - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(kubernetes.Interface) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) DynDial() (dynamic.Interface, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("DynDial", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 dynamic.Interface - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.Interface) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) HasMetrics() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) IsActiveNamespace(_param0 string) bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("IsActiveNamespace", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *versioned.Clientset - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*versioned.Clientset) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) RestConfig() (*rest.Config, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfig", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *rest.Config - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*rest.Config) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) ServerVersion() (*version.Info, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ServerVersion", params, []reflect.Type{reflect.TypeOf((**version.Info)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *version.Info - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*version.Info) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) SwitchContext(_param0 string) error { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(error) - } - } - return ret0 -} - -func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ValidNamespaces", params, []reflect.Type{reflect.TypeOf((*[]v1.Namespace)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 []v1.Namespace - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].([]v1.Namespace) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockConnection) VerifyWasCalledOnce() *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockConnection) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockConnection) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockConnection) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockConnection { - return &VerifierMockConnection{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMockConnection struct { - mock *MockConnection - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMockConnection) ActiveCluster() *MockConnection_ActiveCluster_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveCluster", params, verifier.timeout) - return &MockConnection_ActiveCluster_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ActiveCluster_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ActiveCluster_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ActiveCluster_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ActiveNamespace() *MockConnection_ActiveNamespace_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveNamespace", params, verifier.timeout) - return &MockConnection_ActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ActiveNamespace_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ActiveNamespace_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ActiveNamespace_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) CachedDiscovery() *MockConnection_CachedDiscovery_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CachedDiscovery", params, verifier.timeout) - return &MockConnection_CachedDiscovery_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CachedDiscovery_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CachedDiscovery_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_CachedDiscovery_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) CanI(_param0 string, _param1 string, _param2 []string) *MockConnection_CanI_OngoingVerification { - params := []pegomock.Param{_param0, _param1, _param2} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CanI", params, verifier.timeout) - return &MockConnection_CanI_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CanI_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CanI_OngoingVerification) GetCapturedArguments() (string, string, []string) { - _param0, _param1, _param2 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] -} - -func (c *MockConnection_CanI_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string, _param2 [][]string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - _param1 = make([]string, len(c.methodInvocations)) - for u, param := range params[1] { - _param1[u] = param.(string) - } - _param2 = make([][]string, len(c.methodInvocations)) - for u, param := range params[2] { - _param2[u] = param.([]string) - } - } - return -} - -func (verifier *VerifierMockConnection) CheckConnectivity() *MockConnection_CheckConnectivity_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CheckConnectivity", params, verifier.timeout) - return &MockConnection_CheckConnectivity_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_CheckConnectivity_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_CheckConnectivity_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_CheckConnectivity_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) Config() *MockConnection_Config_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) - return &MockConnection_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_Config_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_Config_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_Config_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ConnectionOK() *MockConnection_ConnectionOK_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ConnectionOK", params, verifier.timeout) - return &MockConnection_ConnectionOK_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ConnectionOK_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ConnectionOK_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ConnectionOK_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) Dial() *MockConnection_Dial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Dial", params, verifier.timeout) - return &MockConnection_Dial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_Dial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_Dial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_Dial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) DynDial() *MockConnection_DynDial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDial", params, verifier.timeout) - return &MockConnection_DynDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_DynDial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_DynDial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_DynDial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) - return &MockConnection_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_HasMetrics_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_HasMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_HasMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) IsActiveNamespace(_param0 string) *MockConnection_IsActiveNamespace_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "IsActiveNamespace", params, verifier.timeout) - return &MockConnection_IsActiveNamespace_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_IsActiveNamespace_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockConnection_IsActiveNamespace_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockConnection) MXDial() *MockConnection_MXDial_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) - return &MockConnection_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_MXDial_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_MXDial_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_MXDial_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) RestConfig() *MockConnection_RestConfig_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfig", params, verifier.timeout) - return &MockConnection_RestConfig_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_RestConfig_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_RestConfig_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_RestConfig_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) ServerVersion() *MockConnection_ServerVersion_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ServerVersion", params, verifier.timeout) - return &MockConnection_ServerVersion_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ServerVersion_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ServerVersion_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ServerVersion_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockConnection) SwitchContext(_param0 string) *MockConnection_SwitchContext_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContext", params, verifier.timeout) - return &MockConnection_SwitchContext_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_SwitchContext_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_SwitchContext_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockConnection_SwitchContext_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(c.methodInvocations)) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockConnection) ValidNamespaces() *MockConnection_ValidNamespaces_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ValidNamespaces", params, verifier.timeout) - return &MockConnection_ValidNamespaces_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_ValidNamespaces_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_ValidNamespaces_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_ValidNamespaces_OngoingVerification) GetAllCapturedArguments() { -} diff --git a/internal/model/mock_metricsserver_test.go b/internal/model/mock_metricsserver_test.go deleted file mode 100644 index f206c11b4b..0000000000 --- a/internal/model/mock_metricsserver_test.go +++ /dev/null @@ -1,314 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/model (interfaces: MetricsServer) - -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package model_test - -import ( - client "github.com/derailed/k9s/internal/client" - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" - "reflect" - "time" -) - -type MockMetricsServer struct { - fail func(message string, callerSkip ...int) -} - -func NewMockMetricsServer(options ...pegomock.Option) *MockMetricsServer { - mock := &MockMetricsServer{} - for _, option := range options { - option.Apply(mock) - } - return mock -} - -func (mock *MockMetricsServer) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh } -func (mock *MockMetricsServer) FailHandler() pegomock.FailHandler { return mock.fail } - -func (mock *MockMetricsServer) ClusterLoad(_param0 *v1.NodeList, _param1 *v1beta1.NodeMetricsList, _param2 *client.ClusterMetrics) error { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{_param0, _param1, _param2} - result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterLoad", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(error) - } - } - return ret0 -} - -func (mock *MockMetricsServer) FetchNodesMetrics() (*v1beta1.NodeMetricsList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodesMetrics", params, []reflect.Type{reflect.TypeOf((**v1beta1.NodeMetricsList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1beta1.NodeMetricsList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1beta1.NodeMetricsList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockMetricsServer) FetchPodsMetrics(_param0 string) (*v1beta1.PodMetricsList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("FetchPodsMetrics", params, []reflect.Type{reflect.TypeOf((**v1beta1.PodMetricsList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1beta1.PodMetricsList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1beta1.PodMetricsList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockMetricsServer) HasMetrics() bool { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) - var ret0 bool - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(bool) - } - } - return ret0 -} - -func (mock *MockMetricsServer) NodesMetrics(_param0 *v1.NodeList, _param1 *v1beta1.NodeMetricsList, _param2 client.NodesMetrics) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{_param0, _param1, _param2} - pegomock.GetGenericMockFrom(mock).Invoke("NodesMetrics", params, []reflect.Type{}) -} - -func (mock *MockMetricsServer) PodsMetrics(_param0 *v1beta1.PodMetricsList, _param1 client.PodsMetrics) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsServer().") - } - params := []pegomock.Param{_param0, _param1} - pegomock.GetGenericMockFrom(mock).Invoke("PodsMetrics", params, []reflect.Type{}) -} - -func (mock *MockMetricsServer) VerifyWasCalledOnce() *VerifierMockMetricsServer { - return &VerifierMockMetricsServer{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockMetricsServer) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockMetricsServer { - return &VerifierMockMetricsServer{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockMetricsServer) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockMetricsServer { - return &VerifierMockMetricsServer{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockMetricsServer) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockMetricsServer { - return &VerifierMockMetricsServer{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMockMetricsServer struct { - mock *MockMetricsServer - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMockMetricsServer) ClusterLoad(_param0 *v1.NodeList, _param1 *v1beta1.NodeMetricsList, _param2 *client.ClusterMetrics) *MockMetricsServer_ClusterLoad_OngoingVerification { - params := []pegomock.Param{_param0, _param1, _param2} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterLoad", params, verifier.timeout) - return &MockMetricsServer_ClusterLoad_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_ClusterLoad_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_ClusterLoad_OngoingVerification) GetCapturedArguments() (*v1.NodeList, *v1beta1.NodeMetricsList, *client.ClusterMetrics) { - _param0, _param1, _param2 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] -} - -func (c *MockMetricsServer_ClusterLoad_OngoingVerification) GetAllCapturedArguments() (_param0 []*v1.NodeList, _param1 []*v1beta1.NodeMetricsList, _param2 []*client.ClusterMetrics) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]*v1.NodeList, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(*v1.NodeList) - } - _param1 = make([]*v1beta1.NodeMetricsList, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(*v1beta1.NodeMetricsList) - } - _param2 = make([]*client.ClusterMetrics, len(params[2])) - for u, param := range params[2] { - _param2[u] = param.(*client.ClusterMetrics) - } - } - return -} - -func (verifier *VerifierMockMetricsServer) FetchNodesMetrics() *MockMetricsServer_FetchNodesMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodesMetrics", params, verifier.timeout) - return &MockMetricsServer_FetchNodesMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_FetchNodesMetrics_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_FetchNodesMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockMetricsServer_FetchNodesMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockMetricsServer) FetchPodsMetrics(_param0 string) *MockMetricsServer_FetchPodsMetrics_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchPodsMetrics", params, verifier.timeout) - return &MockMetricsServer_FetchPodsMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_FetchPodsMetrics_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_FetchPodsMetrics_OngoingVerification) GetCapturedArguments() string { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MockMetricsServer_FetchPodsMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]string, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(string) - } - } - return -} - -func (verifier *VerifierMockMetricsServer) HasMetrics() *MockMetricsServer_HasMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) - return &MockMetricsServer_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_HasMetrics_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_HasMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockMetricsServer_HasMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMockMetricsServer) NodesMetrics(_param0 *v1.NodeList, _param1 *v1beta1.NodeMetricsList, _param2 client.NodesMetrics) *MockMetricsServer_NodesMetrics_OngoingVerification { - params := []pegomock.Param{_param0, _param1, _param2} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodesMetrics", params, verifier.timeout) - return &MockMetricsServer_NodesMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_NodesMetrics_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_NodesMetrics_OngoingVerification) GetCapturedArguments() (*v1.NodeList, *v1beta1.NodeMetricsList, client.NodesMetrics) { - _param0, _param1, _param2 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] -} - -func (c *MockMetricsServer_NodesMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 []*v1.NodeList, _param1 []*v1beta1.NodeMetricsList, _param2 []client.NodesMetrics) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]*v1.NodeList, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(*v1.NodeList) - } - _param1 = make([]*v1beta1.NodeMetricsList, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(*v1beta1.NodeMetricsList) - } - _param2 = make([]client.NodesMetrics, len(params[2])) - for u, param := range params[2] { - _param2[u] = param.(client.NodesMetrics) - } - } - return -} - -func (verifier *VerifierMockMetricsServer) PodsMetrics(_param0 *v1beta1.PodMetricsList, _param1 client.PodsMetrics) *MockMetricsServer_PodsMetrics_OngoingVerification { - params := []pegomock.Param{_param0, _param1} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PodsMetrics", params, verifier.timeout) - return &MockMetricsServer_PodsMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockMetricsServer_PodsMetrics_OngoingVerification struct { - mock *MockMetricsServer - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockMetricsServer_PodsMetrics_OngoingVerification) GetCapturedArguments() (*v1beta1.PodMetricsList, client.PodsMetrics) { - _param0, _param1 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1], _param1[len(_param1)-1] -} - -func (c *MockMetricsServer_PodsMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 []*v1beta1.PodMetricsList, _param1 []client.PodsMetrics) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([]*v1beta1.PodMetricsList, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.(*v1beta1.PodMetricsList) - } - _param1 = make([]client.PodsMetrics, len(params[1])) - for u, param := range params[1] { - _param1[u] = param.(client.PodsMetrics) - } - } - return -} diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index ecac480c9a..3cf71c23de 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -12,7 +12,7 @@ import ( "github.com/derailed/k9s/internal/health" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -119,7 +119,7 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, c := health.NewCheck(gvr) if meta.Renderer.IsGeneric() { - table, ok := oo[0].(*metav1beta1.Table) + table, ok := oo[0].(*metav1.Table) if !ok { return nil, fmt.Errorf("expecting a meta table but got %T", oo[0]) } diff --git a/internal/model/registry.go b/internal/model/registry.go index 8b7e238116..9c333f3106 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -13,6 +13,10 @@ import ( // Registry tracks resources metadata. // BOZO!! Break up deps and merge into single registrar. var Registry = map[string]ResourceMeta{ + "workloads": { + DAO: &dao.Workload{}, + Renderer: &render.Workload{}, + }, // Custom... "references": { DAO: &dao.Reference{}, diff --git a/internal/model/rev_values.go b/internal/model/rev_values.go new file mode 100644 index 0000000000..e25ef7b410 --- /dev/null +++ b/internal/model/rev_values.go @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model + +import ( + "context" + "strings" + "sync/atomic" + "time" + + backoff "github.com/cenkalti/backoff/v4" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/rs/zerolog/log" + "github.com/sahilm/fuzzy" +) + +// RevValues tracks Helm values representations. +type RevValues struct { + gvr client.GVR + inUpdate int32 + path string + rev string + query string + lines []string + allValues bool + listeners []ResourceViewerListener + options ViewerToggleOpts +} + +// NewRevValues return a new Helm values resource model. +func NewRevValues(gvr client.GVR, path, rev string) *RevValues { + return &RevValues{ + gvr: gvr, + path: path, + rev: rev, + allValues: false, + lines: getRevValues(path, rev), + } +} + +func getHelmHistDao() *dao.HelmHistory { + return Registry["helm-history"].DAO.(*dao.HelmHistory) +} + +func getRevValues(path, rev string) []string { + vals, err := getHelmHistDao().GetValues(path, true) + if err != nil { + log.Error().Err(err).Msgf("Failed to get Helm values") + } + return strings.Split(string(vals), "\n") +} + +// GVR returns the resource gvr. +func (v *RevValues) GVR() client.GVR { + return v.gvr +} + +// GetPath returns the active resource path. +func (v *RevValues) GetPath() string { + return v.path +} + +// SetOptions toggle model options. +func (v *RevValues) SetOptions(ctx context.Context, opts ViewerToggleOpts) { + v.options = opts + if err := v.refresh(ctx); err != nil { + v.fireResourceFailed(err) + } +} + +// Filter filters the model. +func (v *RevValues) Filter(q string) { + v.query = q + v.filterChanged(v.lines) +} + +func (v *RevValues) filterChanged(lines []string) { + v.fireResourceChanged(lines, v.filter(v.query, lines)) +} + +func (v *RevValues) filter(q string, lines []string) fuzzy.Matches { + if q == "" { + return nil + } + if dao.IsFuzzySelector(q) { + return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + } + return rxFilter(q, lines) +} + +func (*RevValues) fuzzyFilter(q string, lines []string) fuzzy.Matches { + return fuzzy.Find(q, lines) +} + +func (v *RevValues) fireResourceChanged(lines []string, matches fuzzy.Matches) { + for _, l := range v.listeners { + l.ResourceChanged(lines, matches) + } +} + +func (v *RevValues) fireResourceFailed(err error) { + for _, l := range v.listeners { + l.ResourceFailed(err) + } +} + +// ClearFilter clear out the filter. +func (v *RevValues) ClearFilter() { + v.query = "" +} + +// Peek returns the current model data. +func (v *RevValues) Peek() []string { + return v.lines +} + +// Refresh updates model data. +func (v *RevValues) Refresh(ctx context.Context) error { + return v.refresh(ctx) +} + +// Watch watches for Values changes. +func (v *RevValues) Watch(ctx context.Context) error { + if err := v.refresh(ctx); err != nil { + return err + } + go v.updater(ctx) + + return nil +} + +func (v *RevValues) updater(ctx context.Context) { + defer log.Debug().Msgf("YAML canceled -- %q", v.gvr) + + backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval) + delay := defaultReaderRefreshRate + for { + select { + case <-ctx.Done(): + return + case <-time.After(delay): + if err := v.refresh(ctx); err != nil { + v.fireResourceFailed(err) + if delay = backOff.NextBackOff(); delay == backoff.Stop { + log.Error().Err(err).Msgf("giving up retrieving chart values") + return + } + } else { + backOff.Reset() + delay = defaultReaderRefreshRate + } + } + } +} + +func (v *RevValues) refresh(ctx context.Context) error { + if !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) { + log.Debug().Msgf("Dropping update...") + return nil + } + defer atomic.StoreInt32(&v.inUpdate, 0) + + if err := v.reconcile(ctx); err != nil { + return err + } + + return nil +} + +func (v *RevValues) reconcile(_ context.Context) error { + v.fireResourceChanged(v.lines, v.filter(v.query, v.lines)) + + return nil +} + +// AddListener adds a new model listener. +func (v *RevValues) AddListener(l ResourceViewerListener) { + v.listeners = append(v.listeners, l) +} + +// RemoveListener delete a listener from the list. +func (v *RevValues) RemoveListener(l ResourceViewerListener) { + victim := -1 + for i, lis := range v.listeners { + if lis == l { + victim = i + break + } + } + + if victim >= 0 { + v.listeners = append(v.listeners[:victim], v.listeners[victim+1:]...) + } +} diff --git a/internal/model/stack_test.go b/internal/model/stack_test.go index aaa43fbcc4..5a3eca62f4 100644 --- a/internal/model/stack_test.go +++ b/internal/model/stack_test.go @@ -301,11 +301,13 @@ func (c c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return func (c c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { return nil } -func (c c) SetRect(int, int, int, int) {} -func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 } -func (c c) GetFocusable() tview.Focusable { return nil } -func (c c) Focus(func(tview.Primitive)) {} -func (c c) Blur() {} -func (c c) Start() {} -func (c c) Stop() {} -func (c c) Init(context.Context) error { return nil } +func (c c) SetRect(int, int, int, int) {} +func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 } +func (c c) GetFocusable() tview.Focusable { return nil } +func (c c) Focus(func(tview.Primitive)) {} +func (c c) Blur() {} +func (c c) Start() {} +func (c c) Stop() {} +func (c c) Init(context.Context) error { return nil } +func (c c) SetFilter(string) {} +func (c c) SetLabelFilter(map[string]string) {} diff --git a/internal/model/table.go b/internal/model/table.go index 8924ef6c11..c6e84c9fcf 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -17,7 +17,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" ) @@ -57,8 +56,17 @@ func NewTable(gvr client.GVR) *Table { // SetLabelFilter sets the labels filter. func (t *Table) SetLabelFilter(f string) { t.mx.Lock() + defer t.mx.Unlock() + t.labelFilter = f - t.mx.Unlock() +} + +// GetLabelFilter sets the labels filter. +func (t *Table) GetLabelFilter() string { + t.mx.Lock() + defer t.mx.Unlock() + + return t.labelFilter } // SetInstance sets a single entry table. @@ -220,8 +228,9 @@ func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, err ns := client.CleanseNamespace(t.namespace) if client.IsClusterScoped(t.namespace) { - ns = client.AllNamespaces + ns = client.BlankNamespace } + ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) return a.List(ctx, ns) } @@ -230,9 +239,7 @@ func (t *Table) reconcile(ctx context.Context) error { t.mx.Lock() defer t.mx.Unlock() meta := resourceMeta(t.gvr) - if t.labelFilter != "" { - ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) - } + ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) var ( oo []runtime.Object err error @@ -250,7 +257,7 @@ func (t *Table) reconcile(ctx context.Context) error { var rows render.Rows if len(oo) > 0 { if meta.Renderer.IsGeneric() { - table, ok := oo[0].(*metav1beta1.Table) + table, ok := oo[0].(*metav1.Table) if !ok { return fmt.Errorf("expecting a meta table but got %T", oo[0]) } @@ -312,7 +319,7 @@ func hydrate(ns string, oo []runtime.Object, rr render.Rows, re Renderer) error // Generic represents a generic resource. type Generic interface { // SetTable sets up the resource tabular definition. - SetTable(ns string, table *metav1beta1.Table) + SetTable(ns string, table *metav1.Table) // Header returns a resource header. Header(ns string) render.Header @@ -321,7 +328,7 @@ type Generic interface { Render(o interface{}, ns string, row *render.Row) error } -func genericHydrate(ns string, table *metav1beta1.Table, rr render.Rows, re Renderer) error { +func genericHydrate(ns string, table *metav1.Table, rr render.Rows, re Renderer) error { gr, ok := re.(Generic) if !ok { return fmt.Errorf("expecting generic renderer but got %T", re) diff --git a/internal/model/table_int_test.go b/internal/model/table_int_test.go index abe9a606d2..522cd18c4c 100644 --- a/internal/model/table_int_test.go +++ b/internal/model/table_int_test.go @@ -35,7 +35,7 @@ func TestTableReconcile(t *testing.T) { err := ta.reconcile(ctx) assert.Nil(t, err) data := ta.Peek() - assert.Equal(t, 22, len(data.Header)) + assert.Equal(t, 23, len(data.Header)) assert.Equal(t, 1, len(data.RowEvents)) assert.Equal(t, client.NamespaceAll, data.Namespace) } @@ -108,7 +108,7 @@ func TestTableHydrate(t *testing.T) { assert.Nil(t, hydrate("blee", oo, rr, render.Pod{})) assert.Equal(t, 1, len(rr)) - assert.Equal(t, 22, len(rr[0].Fields)) + assert.Equal(t, 23, len(rr[0].Fields)) } func TestTableGenericHydrate(t *testing.T) { diff --git a/internal/model/table_test.go b/internal/model/table_test.go index b944cc6d5e..ec636b9ed9 100644 --- a/internal/model/table_test.go +++ b/internal/model/table_test.go @@ -36,7 +36,7 @@ func TestTableRefresh(t *testing.T) { ctx = context.WithValue(ctx, internal.KeyWithMetrics, false) assert.NoError(t, ta.Refresh(ctx)) data := ta.Peek() - assert.Equal(t, 22, len(data.Header)) + assert.Equal(t, 23, len(data.Header)) assert.Equal(t, 1, len(data.RowEvents)) assert.Equal(t, client.NamespaceAll, data.Namespace) assert.Equal(t, 1, l.count) diff --git a/internal/model/tree.go b/internal/model/tree.go index f1c60f686c..c013238e85 100644 --- a/internal/model/tree.go +++ b/internal/model/tree.go @@ -17,7 +17,7 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/xray" "github.com/rs/zerolog/log" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -213,7 +213,7 @@ func (t *Tree) reconcile(ctx context.Context) error { root := xray.NewTreeNode(res, res) ctx = context.WithValue(ctx, xray.KeyParent, root) if _, ok := meta.TreeRenderer.(*xray.Generic); ok { - table, ok := oo[0].(*metav1beta1.Table) + table, ok := oo[0].(*metav1.Table) if !ok { return fmt.Errorf("expecting a Table but got %T", oo[0]) } @@ -302,7 +302,7 @@ func treeHydrate(ctx context.Context, ns string, oo []runtime.Object, re TreeRen return nil } -func genericTreeHydrate(ctx context.Context, ns string, table *metav1beta1.Table, re TreeRenderer) error { +func genericTreeHydrate(ctx context.Context, ns string, table *metav1.Table, re TreeRenderer) error { tre, ok := re.(*xray.Generic) if !ok { return fmt.Errorf("expecting xray.Generic renderer but got %T", re) diff --git a/internal/model/types.go b/internal/model/types.go index 00ab43d7d8..0484def5df 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -84,6 +84,12 @@ type Component interface { Igniter Hinter Commander + Filterer +} + +type Filterer interface { + SetFilter(string) + SetLabelFilter(map[string]string) } // Renderer represents a resource renderer. diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index c13f3a0a52..5440e53a90 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -11,10 +11,11 @@ import ( "net/http" "os" "path/filepath" + "strings" "sync" "time" - "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" @@ -29,11 +30,6 @@ const ( k9sUA = "k9s/" ) -var ( - // K9sBenchDir directory to store K9s Benchmark files. - K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser())) -) - // Benchmark puts a workload under load. type Benchmark struct { canceled bool @@ -111,39 +107,43 @@ func (b *Benchmark) Canceled() bool { } // Run starts a benchmark. -func (b *Benchmark) Run(cluster string, done func()) { - log.Debug().Msgf("Running benchmark on cluster %s", cluster) +func (b *Benchmark) Run(cluster, context string, done func()) { + log.Debug().Msgf("Running benchmark on context %s", cluster) buff := new(bytes.Buffer) b.worker.Writer = buff // this call will block until the benchmark is complete or times out. b.worker.Run() b.worker.Stop() if buff.Len() > 0 { - if err := b.save(cluster, buff); err != nil { + if err := b.save(cluster, context, buff); err != nil { log.Error().Err(err).Msg("Saving Benchmark") } } done() } -func (b *Benchmark) save(cluster string, r io.Reader) error { - dir := filepath.Join(K9sBenchDir, cluster) - if err := os.MkdirAll(dir, 0744); err != nil { +func (b *Benchmark) save(cluster, context string, r io.Reader) error { + ns, n := client.Namespaced(b.config.Name) + n = strings.Replace(n, "|", "_", -1) + n = strings.Replace(n, ":", "_", -1) + dir, err := config.EnsureBenchmarksDir(cluster, context) + if err != nil { + return err + } + bf := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, n, time.Now().UnixNano())) + if err := data.EnsureDirPath(bf, data.DefaultDirMod); err != nil { return err } - ns, n := client.Namespaced(b.config.Name) - file := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, dao.BenchRx.ReplaceAllString(n, "_"), time.Now().UnixNano())) - f, err := os.Create(file) + f, err := os.Create(bf) if err != nil { return err } defer func() { if e := f.Close(); e != nil { - log.Fatal().Err(e).Msg("Bench save") + log.Error().Err(e).Msgf("Benchmark file close failed: %q", bf) } }() - if _, err = io.Copy(f, r); err != nil { return err } diff --git a/internal/port/pf.go b/internal/port/pf.go index 26d4ffbc4c..76f8b6419a 100644 --- a/internal/port/pf.go +++ b/internal/port/pf.go @@ -13,10 +13,10 @@ import ( ) const ( - // K9sAutoPortForwardKey represents an auto portforwards annotation. + // K9sAutoPortForwardsKey represents an auto portforwards annotation. K9sAutoPortForwardsKey = "k9scli.io/auto-port-forwards" - // K9sPortForwardKey represents a portforwards annotation. + // K9sPortForwardsKey represents a portforwards annotation. K9sPortForwardsKey = "k9scli.io/port-forwards" ) diff --git a/internal/render/alias.go b/internal/render/alias.go index 78855128c5..ce8f386d05 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -22,7 +22,7 @@ func (Alias) Header(ns string) Header { return Header{ HeaderColumn{Name: "RESOURCE"}, HeaderColumn{Name: "COMMAND"}, - HeaderColumn{Name: "APIGROUP"}, + HeaderColumn{Name: "API-GROUP"}, } } diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go index a9c87a842e..85e61f86ef 100644 --- a/internal/render/alias_test.go +++ b/internal/render/alias_test.go @@ -26,17 +26,17 @@ func TestAliasColorer(t *testing.T) { e tcell.Color }{ "addAll": { - ns: client.AllNamespaces, + ns: client.NamespaceAll, re: render.RowEvent{Kind: render.EventAdd, Row: r}, e: tcell.ColorBlue, }, "deleteAll": { - ns: client.AllNamespaces, + ns: client.NamespaceAll, re: render.RowEvent{Kind: render.EventDelete, Row: r}, e: tcell.ColorGray, }, "updateAll": { - ns: client.AllNamespaces, + ns: client.NamespaceAll, re: render.RowEvent{Kind: render.EventUpdate, Row: r}, e: tcell.ColorDefault, }, @@ -54,12 +54,12 @@ func TestAliasHeader(t *testing.T) { h := render.Header{ render.HeaderColumn{Name: "RESOURCE"}, render.HeaderColumn{Name: "COMMAND"}, - render.HeaderColumn{Name: "APIGROUP"}, + render.HeaderColumn{Name: "API-GROUP"}, } var a render.Alias assert.Equal(t, h, a.Header("fred")) - assert.Equal(t, h, a.Header(client.AllNamespaces)) + assert.Equal(t, h, a.Header(client.NamespaceAll)) } func TestAliasRender(t *testing.T) { diff --git a/internal/render/benchmark.go b/internal/render/benchmark.go index 0f60d4decf..d8a3c75490 100644 --- a/internal/render/benchmark.go +++ b/internal/render/benchmark.go @@ -14,6 +14,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/tcell/v2" "github.com/derailed/tview" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -116,7 +117,7 @@ func (b Benchmark) initRow(row Fields, f os.FileInfo) error { row[0] = tokens[0] row[1] = tokens[1] row[7] = f.Name() - row[9] = timeToAge(f.ModTime()) + row[9] = ToAge(metav1.Time{Time: f.ModTime()}) return nil } diff --git a/internal/render/container.go b/internal/render/container.go index 662441c0e6..b9bd968e1b 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -149,17 +149,30 @@ func (Container) diagnose(state, ready string) error { // ---------------------------------------------------------------------------- // Helpers... +func containerRequests(co *v1.Container) v1.ResourceList { + req := co.Resources.Requests + if len(req) != 0 { + return req + } + lim := co.Resources.Limits + if len(lim) != 0 { + return lim + } + + return nil +} + func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, r metric) { rList, lList := containerRequests(co), co.Resources.Limits if rList.Cpu() != nil { r.cpu = rList.Cpu().MilliValue() } - if lList.Cpu() != nil { - r.lcpu = lList.Cpu().MilliValue() - } if rList.Memory() != nil { r.mem = rList.Memory().Value() } + if lList.Cpu() != nil { + r.lcpu = lList.Cpu().MilliValue() + } if lList.Memory() != nil { r.lmem = lList.Memory().Value() } diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go index aaf2e8fa4d..e75d74f8c8 100644 --- a/internal/render/cronjob.go +++ b/internal/render/cronjob.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -26,7 +25,7 @@ func (CronJob) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "SCHEDULE"}, HeaderColumn{Name: "SUSPEND"}, HeaderColumn{Name: "ACTIVE"}, @@ -38,9 +37,6 @@ func (CronJob) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h @@ -67,7 +63,7 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ cj.Namespace, cj.Name, - computeVulScore(&cj.Spec.JobTemplate.Spec.Template.Spec), + computeVulScore(cj.ObjectMeta, &cj.Spec.JobTemplate.Spec.Template.Spec), cj.Spec.Schedule, boolPtrToStr(cj.Spec.Suspend), strconv.Itoa(len(cj.Status.Active)), @@ -79,9 +75,6 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { "", ToAge(cj.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) - } return nil } diff --git a/internal/render/cronjob_test.go b/internal/render/cronjob_test.go index e24ac7852a..ff11bd9bf8 100644 --- a/internal/render/cronjob_test.go +++ b/internal/render/cronjob_test.go @@ -16,5 +16,5 @@ func TestCronJobRender(t *testing.T) { assert.NoError(t, c.Render(load(t, "cj"), "", &r)) assert.Equal(t, "default/hello", r.ID) - assert.Equal(t, render.Fields{"default", "hello", "*/1 * * * *", "false", "0"}, r.Fields[:5]) + assert.Equal(t, render.Fields{"default", "hello", "0", "*/1 * * * *", "false", "0"}, r.Fields[:6]) } diff --git a/internal/render/dp.go b/internal/render/dp.go index 3d1b2b5534..01eb96e69f 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -6,9 +6,10 @@ package render import ( "fmt" "strconv" + "strings" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" + "github.com/derailed/tcell/v2" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -22,7 +23,23 @@ type Deployment struct { // ColorerFunc colors a resource row. func (d Deployment) ColorerFunc() ColorerFunc { - return DefaultColorer + return func(ns string, h Header, re RowEvent) tcell.Color { + c := DefaultColorer(ns, h, re) + if !Happy(ns, h, re.Row) { + return ErrColor + } + rdCol := h.IndexOf("READY", true) + if rdCol == -1 { + return c + } + ready := strings.TrimSpace(re.Row.Fields[rdCol]) + tt := strings.Split(ready, "/") + if len(tt) == 2 && tt[1] == "0" { + return PendingColor + } + + return c + } } // Header returns a header row. @@ -30,7 +47,7 @@ func (Deployment) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, @@ -38,9 +55,6 @@ func (Deployment) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -62,7 +76,7 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ dp.Namespace, dp.Name, - computeVulScore(&dp.Spec.Template.Spec), + computeVulScore(dp.ObjectMeta, &dp.Spec.Template.Spec), strconv.Itoa(int(dp.Status.AvailableReplicas)) + "/" + strconv.Itoa(int(dp.Status.Replicas)), strconv.Itoa(int(dp.Status.UpdatedReplicas)), strconv.Itoa(int(dp.Status.AvailableReplicas)), @@ -70,9 +84,6 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { AsStatus(d.diagnose(dp.Status.Replicas, dp.Status.AvailableReplicas)), ToAge(dp.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) - } return nil } @@ -81,5 +92,6 @@ func (Deployment) diagnose(desired, avail int32) error { if desired != avail { return fmt.Errorf("desiring %d replicas got %d available", desired, avail) } + return nil } diff --git a/internal/render/dp_test.go b/internal/render/dp_test.go index c82a1defa4..92653b1864 100644 --- a/internal/render/dp_test.go +++ b/internal/render/dp_test.go @@ -16,7 +16,7 @@ func TestDpRender(t *testing.T) { assert.Nil(t, c.Render(load(t, "dp"), "", &r)) assert.Equal(t, "icx/icx-db", r.ID) - assert.Equal(t, render.Fields{"icx", "icx-db", "1/1", "1", "1"}, r.Fields[:5]) + assert.Equal(t, render.Fields{"icx", "icx-db", "0", "1/1", "1", "1"}, r.Fields[:6]) } func BenchmarkDpRender(b *testing.B) { diff --git a/internal/render/ds.go b/internal/render/ds.go index 87f92494ae..d14a1569b5 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -8,7 +8,6 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -25,7 +24,7 @@ func (DaemonSet) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, @@ -35,9 +34,6 @@ func (DaemonSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -58,7 +54,7 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ ds.Namespace, ds.Name, - computeVulScore(&ds.Spec.Template.Spec), + computeVulScore(ds.ObjectMeta, &ds.Spec.Template.Spec), strconv.Itoa(int(ds.Status.DesiredNumberScheduled)), strconv.Itoa(int(ds.Status.CurrentNumberScheduled)), strconv.Itoa(int(ds.Status.NumberReady)), @@ -68,9 +64,6 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { AsStatus(d.diagnose(ds.Status.DesiredNumberScheduled, ds.Status.NumberReady)), ToAge(ds.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) - } return nil } diff --git a/internal/render/ds_test.go b/internal/render/ds_test.go index a493544545..5753bcb6a4 100644 --- a/internal/render/ds_test.go +++ b/internal/render/ds_test.go @@ -16,5 +16,5 @@ func TestDaemonSetRender(t *testing.T) { assert.NoError(t, c.Render(load(t, "ds"), "", &r)) assert.Equal(t, "kube-system/fluentd-gcp-v3.2.0", r.ID) - assert.Equal(t, render.Fields{"kube-system", "fluentd-gcp-v3.2.0", "2", "2", "2", "2", "2"}, r.Fields[:7]) + assert.Equal(t, render.Fields{"kube-system", "fluentd-gcp-v3.2.0", "0", "2", "2", "2", "2", "2"}, r.Fields[:8]) } diff --git a/internal/render/ev.go b/internal/render/ev.go index f028c23844..f2e8b9b1f1 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -9,7 +9,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/tcell/v2" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Event renders a K8s Event to screen. @@ -68,7 +68,7 @@ func (e *Event) Header(ns string) Header { // Render renders a K8s resource to screen. func (e *Event) Render(o interface{}, ns string, r *Row) error { - row, ok := o.(metav1beta1.TableRow) + row, ok := o.(metav1.TableRow) if !ok { return fmt.Errorf("expecting a TableRow but got %T", o) } diff --git a/internal/render/generic.go b/internal/render/generic.go index 0297f940f4..fc89fdbefa 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -11,8 +11,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" - - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ageTableCol = "Age" @@ -20,7 +19,7 @@ const ageTableCol = "Age" // Generic renders a generic resource to screen. type Generic struct { Base - table *metav1beta1.Table + table *metav1.Table header Header ageIndex int } @@ -30,7 +29,7 @@ func (*Generic) IsGeneric() bool { } // SetTable sets the tabular resource. -func (g *Generic) SetTable(ns string, t *metav1beta1.Table) { +func (g *Generic) SetTable(ns string, t *metav1.Table) { g.table = t g.header = g.Header(ns) } @@ -68,7 +67,7 @@ func (g *Generic) Header(ns string) Header { // Render renders a K8s resource to screen. func (g *Generic) Render(o interface{}, ns string, r *Row) error { - row, ok := o.(metav1beta1.TableRow) + row, ok := o.(metav1.TableRow) if !ok { return fmt.Errorf("expecting a TableRow but got %T", o) } diff --git a/internal/render/generic_test.go b/internal/render/generic_test.go index 07172d2965..851aaa7290 100644 --- a/internal/render/generic_test.go +++ b/internal/render/generic_test.go @@ -46,7 +46,7 @@ func TestGenericRender(t *testing.T) { }, }, "allNS": { - ns: client.AllNamespaces, + ns: client.NamespaceAll, table: makeNSGeneric(), eID: "ns1/fred", eFields: render.Fields{"ns1", "c1", "c2", "c3"}, diff --git a/internal/render/header.go b/internal/render/header.go index 780cc3d073..5ac9bcd2fb 100644 --- a/internal/render/header.go +++ b/internal/render/header.go @@ -21,6 +21,7 @@ type HeaderColumn struct { MX bool Time bool Capacity bool + VS bool } // Clone copies a header. diff --git a/internal/render/helpers.go b/internal/render/helpers.go index e6945c33a0..548912f8e0 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -23,8 +23,8 @@ import ( "k8s.io/apimachinery/pkg/util/duration" ) -func computeVulScore(spec *v1.PodSpec) string { - if vul.ImgScanner == nil { +func computeVulScore(m metav1.ObjectMeta, spec *v1.PodSpec) string { + if vul.ImgScanner == nil || vul.ImgScanner.ShouldExcludes(m) { return "0" } ii := ExtractImages(spec) diff --git a/internal/render/job.go b/internal/render/job.go index b6325d8cf9..8fda057b98 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -10,7 +10,6 @@ import ( "time" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,7 +28,7 @@ func (Job) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "COMPLETIONS"}, HeaderColumn{Name: "DURATION"}, HeaderColumn{Name: "SELECTOR", Wide: true}, @@ -38,9 +37,6 @@ func (Job) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -64,7 +60,7 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ job.Namespace, job.Name, - computeVulScore(&job.Spec.Template.Spec), + computeVulScore(job.ObjectMeta, &job.Spec.Template.Spec), ready, toDuration(job.Status), jobSelector(job.Spec), @@ -73,9 +69,6 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { AsStatus(j.diagnose(ready, job.Status.CompletionTime)), ToAge(job.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) - } return nil } diff --git a/internal/render/job_test.go b/internal/render/job_test.go index 5479291270..b26179966a 100644 --- a/internal/render/job_test.go +++ b/internal/render/job_test.go @@ -16,5 +16,5 @@ func TestJobRender(t *testing.T) { assert.NoError(t, c.Render(load(t, "job"), "", &r)) assert.Equal(t, "default/hello-1567179180", r.ID) - assert.Equal(t, render.Fields{"default", "hello-1567179180", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:7]) + assert.Equal(t, render.Fields{"default", "hello-1567179180", "0", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8]) } diff --git a/internal/render/ns.go b/internal/render/ns.go index ddf34cf6e4..77954a5eda 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -79,5 +79,6 @@ func (Namespace) diagnose(phase v1.NamespacePhase) error { if phase != v1.NamespaceActive && phase != v1.NamespaceTerminating { return errors.New("namespace not ready") } + return nil } diff --git a/internal/render/pod.go b/internal/render/pod.go index a79cafe252..fb7ab88d57 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -18,7 +18,6 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" ) const ( @@ -41,6 +40,7 @@ const ( PhaseError = "Error" PhaseImagePullBackOff = "ImagePullBackOff" PhaseOOMKilled = "OOMKilled" + PhasePending = "Pending" ) // Pod renders a K8s Pod to screen. @@ -59,9 +59,9 @@ func (p Pod) ColorerFunc() ColorerFunc { } status := strings.TrimSpace(re.Row.Fields[statusCol]) switch status { - case Pending: + case Pending, ContainerCreating: c = PendingColor - case ContainerCreating, PodInitializing: + case PodInitializing: c = AddColor case Initialized: c = HighlightColor @@ -88,15 +88,11 @@ func (Pod) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "PF"}, HeaderColumn{Name: "READY"}, HeaderColumn{Name: "STATUS"}, HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, - HeaderColumn{Name: "IP"}, - HeaderColumn{Name: "NODE"}, - HeaderColumn{Name: "NOMINATED NODE", Wide: true}, - HeaderColumn{Name: "READINESS GATES", Wide: true}, HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight, Wide: true}, @@ -105,14 +101,15 @@ func (Pod) Header(ns string) Header { HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, + HeaderColumn{Name: "IP", Wide: true}, + HeaderColumn{Name: "NODE", Wide: true}, + HeaderColumn{Name: "NOMINATED NODE", Wide: true}, + HeaderColumn{Name: "READINESS GATES", Wide: true}, HeaderColumn{Name: "QOS", Wide: true}, HeaderColumn{Name: "LABELS", Wide: true}, HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -151,15 +148,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { row.Fields = Fields{ po.Namespace, po.ObjectMeta.Name, - computeVulScore(&po.Spec), + computeVulScore(po.ObjectMeta, &po.Spec), "●", strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)), phase, strconv.Itoa(rc + irc), - na(po.Status.PodIP), - na(po.Spec.NodeName), - asNominated(po.Status.NominatedNodeName), - asReadinessGate(po), toMc(c.cpu), toMi(c.mem), toMc(r.cpu) + ":" + toMc(r.lcpu), @@ -168,14 +161,15 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { client.ToPercentageStr(c.cpu, r.lcpu), client.ToPercentageStr(c.mem, r.mem), client.ToPercentageStr(c.mem, r.lmem), + na(po.Status.PodIP), + na(po.Spec.NodeName), + asNominated(po.Status.NominatedNodeName), + asReadinessGate(po), p.mapQOS(po.Status.QOSClass), mapToStr(po.Labels), AsStatus(p.diagnose(phase, cr, len(cs))), ToAge(po.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...) - } return nil } @@ -239,9 +233,12 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object { } func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, r metric) { - rcpu, rmem := podRequests(pod.Spec) - lcpu, lmem := podLimits(pod.Spec) - r.cpu, r.lcpu, r.mem, r.lmem = rcpu.MilliValue(), lcpu.MilliValue(), rmem.Value(), lmem.Value() + rcpu, rmem := podRequests(pod.Spec.Containers) + r.cpu, r.mem = rcpu.MilliValue(), rmem.Value() + + lcpu, lmem := podLimits(pod.Spec.Containers) + r.lcpu, r.lmem = lcpu.MilliValue(), lmem.Value() + if mx != nil { ccpu, cmem := currentRes(mx) c.cpu, c.mem = ccpu.MilliValue(), cmem.Value() @@ -250,23 +247,10 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, r metric) { return } -func containerRequests(co *v1.Container) v1.ResourceList { - req := co.Resources.Requests - if len(req) != 0 { - return req - } - lim := co.Resources.Limits - if len(lim) != 0 { - return lim - } - - return nil -} - -func podLimits(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { +func podLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) - for _, co := range spec.Containers { - limits := co.Resources.Limits + for _, c := range cc { + limits := c.Resources.Limits if len(limits) == 0 { return resource.Quantity{}, resource.Quantity{} } @@ -280,10 +264,11 @@ func podLimits(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { return *cpu, *mem } -func podRequests(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { +func podRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) - for i := range spec.Containers { - rl := containerRequests(&spec.Containers[i]) + for _, c := range cc { + co := c + rl := containerRequests(&co) if rl.Cpu() != nil { cpu.Add(*rl.Cpu()) } @@ -291,6 +276,7 @@ func podRequests(spec v1.PodSpec) (resource.Quantity, resource.Quantity) { mem.Add(*rl.Memory()) } } + return *cpu, *mem } diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index c442c2e50f..32a89b1b8a 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -162,8 +162,8 @@ func TestPodRender(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "default/nginx", r.ID) - e := render.Fields{"default", "nginx", "●", "1/1", "Running", "0", "172.17.0.6", "minikube", "", "", "100", "50", "100:0", "70:170", "100", "n/a", "71"} - assert.Equal(t, e, r.Fields[:17]) + e := render.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "", ""} + assert.Equal(t, e, r.Fields[:19]) } func BenchmarkPodRender(b *testing.B) { @@ -193,8 +193,8 @@ func TestPodInitRender(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "default/nginx", r.ID) - e := render.Fields{"default", "nginx", "●", "1/1", "Init:0/1", "0", "172.17.0.6", "minikube", "", "", "10", "10", "100:0", "70:170", "10", "n/a", "14"} - assert.Equal(t, e, r.Fields[:17]) + e := render.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "", ""} + assert.Equal(t, e, r.Fields[:19]) } func TestCheckPodStatus(t *testing.T) { diff --git a/internal/render/policy.go b/internal/render/policy.go index 568ac540c8..e750bcb0fd 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -44,7 +44,7 @@ func (Policy) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "API GROUP"}, + HeaderColumn{Name: "API-GROUP"}, HeaderColumn{Name: "BINDING"}, } h = append(h, rbacVerbHeader()...) diff --git a/internal/render/port_forward_test.go b/internal/render/port_forward_test.go index 4084cb40c5..c3d0d9c226 100644 --- a/internal/render/port_forward_test.go +++ b/internal/render/port_forward_test.go @@ -5,14 +5,13 @@ package render_test import ( "testing" + "time" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestPortForwardRender(t *testing.T) { - var p render.PortForward - var r render.Row o := render.ForwardRes{ Forwarder: fwd{}, Config: render.BenchCfg{ @@ -23,6 +22,8 @@ func TestPortForwardRender(t *testing.T) { }, } + var p render.PortForward + var r render.Row assert.Nil(t, p.Render(o, "fred", &r)) assert.Equal(t, "blee/fred", r.ID) assert.Equal(t, render.Fields{ @@ -34,8 +35,7 @@ func TestPortForwardRender(t *testing.T) { "1", "1", "", - "2m", - }, r.Fields) + }, r.Fields[:8]) } // Helpers... @@ -62,6 +62,6 @@ func (f fwd) Active() bool { return true } -func (f fwd) Age() string { - return "2m" +func (f fwd) Age() time.Time { + return testTime() } diff --git a/internal/render/portforward.go b/internal/render/portforward.go index d3908bb9d9..5ae2f71eee 100644 --- a/internal/render/portforward.go +++ b/internal/render/portforward.go @@ -6,9 +6,11 @@ package render import ( "fmt" "strings" + "time" "github.com/derailed/k9s/internal/client" "github.com/derailed/tcell/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -28,7 +30,7 @@ type Forwarder interface { Active() bool // Age returns forwarder age. - Age() string + Age() time.Time } // PortForward renders a portforwards to screen. @@ -78,7 +80,7 @@ func (f PortForward) Render(o interface{}, gvr string, r *Row) error { AsThousands(int64(pf.Config.C)), AsThousands(int64(pf.Config.N)), "", - pf.Age(), + ToAge(metav1.Time{Time: pf.Age()}), } return nil diff --git a/internal/render/rbac.go b/internal/render/rbac.go index 5af09c87e7..ec23c8ddb2 100644 --- a/internal/render/rbac.go +++ b/internal/render/rbac.go @@ -46,7 +46,7 @@ func (Rbac) Header(ns string) Header { h := make(Header, 0, 10) h = append(h, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "APIGROUP"}, + HeaderColumn{Name: "API-GROUP"}, ) h = append(h, rbacVerbHeader()...) diff --git a/internal/render/rs.go b/internal/render/rs.go index a8287e4566..6dd7f38f1e 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -8,7 +8,6 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -30,7 +29,7 @@ func (ReplicaSet) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, HeaderColumn{Name: "READY", Align: tview.AlignRight}, @@ -38,9 +37,6 @@ func (ReplicaSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -61,7 +57,7 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { row.Fields = Fields{ rs.Namespace, rs.Name, - computeVulScore(&rs.Spec.Template.Spec), + computeVulScore(rs.ObjectMeta, &rs.Spec.Template.Spec), strconv.Itoa(int(*rs.Spec.Replicas)), strconv.Itoa(int(rs.Status.Replicas)), strconv.Itoa(int(rs.Status.ReadyReplicas)), @@ -69,9 +65,6 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { AsStatus(r.diagnose(rs)), ToAge(rs.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - row.Fields = append(row.Fields[:vulIdx], row.Fields[vulIdx+1:]...) - } return nil } diff --git a/internal/render/rs_test.go b/internal/render/rs_test.go index 066f5e0878..8a85dc599a 100644 --- a/internal/render/rs_test.go +++ b/internal/render/rs_test.go @@ -16,5 +16,5 @@ func TestReplicaSetRender(t *testing.T) { assert.NoError(t, c.Render(load(t, "rs"), "", &r)) assert.Equal(t, "icx/icx-db-7d4b578979", r.ID) - assert.Equal(t, render.Fields{"icx", "icx-db-7d4b578979", "1", "1", "1"}, r.Fields[:5]) + assert.Equal(t, render.Fields{"icx", "icx-db-7d4b578979", "0", "1", "1", "1"}, r.Fields[:6]) } diff --git a/internal/render/sts.go b/internal/render/sts.go index 08704bfd9c..cc6b05232a 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -8,7 +8,6 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/vul" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -24,7 +23,7 @@ func (StatefulSet) Header(ns string) Header { h := Header{ HeaderColumn{Name: "NAMESPACE"}, HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS"}, + HeaderColumn{Name: "VS", VS: true}, HeaderColumn{Name: "READY"}, HeaderColumn{Name: "SELECTOR", Wide: true}, HeaderColumn{Name: "SERVICE"}, @@ -34,9 +33,6 @@ func (StatefulSet) Header(ns string) Header { HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } - if vul.ImgScanner == nil { - h = append(h[:vulIdx], h[vulIdx+1:]...) - } return h } @@ -57,7 +53,7 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { r.Fields = Fields{ sts.Namespace, sts.Name, - computeVulScore(&sts.Spec.Template.Spec), + computeVulScore(sts.ObjectMeta, &sts.Spec.Template.Spec), strconv.Itoa(int(sts.Status.ReadyReplicas)) + "/" + strconv.Itoa(int(sts.Status.Replicas)), asSelector(sts.Spec.Selector), na(sts.Spec.ServiceName), @@ -67,9 +63,6 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { AsStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)), ToAge(sts.GetCreationTimestamp()), } - if vul.ImgScanner == nil { - r.Fields = append(r.Fields[:vulIdx], r.Fields[vulIdx+1:]...) - } return nil } diff --git a/internal/render/sts_test.go b/internal/render/sts_test.go index f3f9c7bd39..0070d1a818 100644 --- a/internal/render/sts_test.go +++ b/internal/render/sts_test.go @@ -16,5 +16,5 @@ func TestStatefulSetRender(t *testing.T) { assert.Nil(t, c.Render(load(t, "sts"), "", &r)) assert.Equal(t, "default/nginx-sts", r.ID) - assert.Equal(t, render.Fields{"default", "nginx-sts", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) + assert.Equal(t, render.Fields{"default", "nginx-sts", "0", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) } diff --git a/internal/render/workload.go b/internal/render/workload.go new file mode 100644 index 0000000000..058cfd4078 --- /dev/null +++ b/internal/render/workload.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "strings" + + "github.com/derailed/tcell/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Workload renders a workload to screen. +type Workload struct { + Base +} + +// ColorerFunc colors a resource row. +func (n Workload) ColorerFunc() ColorerFunc { + return func(ns string, h Header, re RowEvent) tcell.Color { + c := DefaultColorer(ns, h, re) + + statusCol := h.IndexOf("STATUS", true) + if statusCol == -1 { + return c + } + status := strings.TrimSpace(re.Row.Fields[statusCol]) + if status == "DEGRADED" { + c = PendingColor + } + + return c + } +} + +// Header returns a header rbw. +func (Workload) Header(string) Header { + return Header{ + HeaderColumn{Name: "KIND"}, + HeaderColumn{Name: "NAMESPACE"}, + HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "STATUS"}, + HeaderColumn{Name: "READY"}, + HeaderColumn{Name: "AGE", Time: true}, + } +} + +// Render renders a K8s resource to screen. +func (n Workload) Render(o interface{}, _ string, r *Row) error { + res, ok := o.(*WorkloadRes) + if !ok { + return fmt.Errorf("expected allRes but got %T", o) + } + + r.ID = fmt.Sprintf("%s|%s|%s", res.Row.Cells[0].(string), res.Row.Cells[1].(string), res.Row.Cells[2].(string)) + r.Fields = Fields{ + res.Row.Cells[0].(string), + res.Row.Cells[1].(string), + res.Row.Cells[2].(string), + res.Row.Cells[3].(string), + res.Row.Cells[4].(string), + ToAge(res.Row.Cells[5].(metav1.Time)), + } + + return nil +} + +type WorkloadRes struct { + Row metav1.TableRow +} + +// GetObjectKind returns a schema object. +func (a *WorkloadRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (a *WorkloadRes) DeepCopyObject() runtime.Object { + return a +} diff --git a/internal/ui/app.go b/internal/ui/app.go index 3b656af2d1..b7b4d22816 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -44,7 +44,7 @@ func NewApp(cfg *config.Config, context string) *App { a.views = map[string]tview.Primitive{ "menu": NewMenu(a.Styles), "logo": NewLogo(a.Styles), - "prompt": NewPrompt(&a, a.Config.K9s.NoIcons, a.Styles), + "prompt": NewPrompt(&a, a.Config.K9s.UI.NoIcons, a.Styles), "crumbs": NewCrumbs(a.Styles), } @@ -58,7 +58,7 @@ func (a *App) Init() { a.cmdBuff.AddListener(a) a.Styles.AddListener(a) - a.SetRoot(a.Main, true).EnableMouse(a.Config.K9s.EnableMouse) + a.SetRoot(a.Main, true).EnableMouse(a.Config.K9s.UI.EnableMouse) } // QueueUpdate queues up a ui action. diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index a49cb34f58..36d5c6ff02 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -6,13 +6,13 @@ package ui_test import ( "testing" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/ui" "github.com/stretchr/testify/assert" ) func TestAppGetCmd(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() a.CmdBuff().SetText("blee", "") @@ -20,7 +20,7 @@ func TestAppGetCmd(t *testing.T) { } func TestAppInCmdMode(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() a.CmdBuff().SetText("blee", "") assert.False(t, a.InCmdMode()) @@ -30,7 +30,7 @@ func TestAppInCmdMode(t *testing.T) { } func TestAppResetCmd(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() a.CmdBuff().SetText("blee", "") @@ -40,7 +40,7 @@ func TestAppResetCmd(t *testing.T) { } func TestAppHasCmd(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() a.ActivateCmd(true) @@ -51,7 +51,7 @@ func TestAppHasCmd(t *testing.T) { } func TestAppGetActions(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() a.AddActions(ui.KeyActions{ui.KeyZ: ui.KeyAction{Description: "zorg"}}) @@ -60,7 +60,7 @@ func TestAppGetActions(t *testing.T) { } func TestAppViews(t *testing.T) { - a := ui.NewApp(config.NewConfig(nil), "") + a := ui.NewApp(mock.NewMockConfig(), "") a.Init() vv := []string{"crumbs", "logo", "prompt", "menu"} diff --git a/internal/ui/config.go b/internal/ui/config.go index b00196fff7..d6f3bc7698 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -6,9 +6,7 @@ package ui import ( "context" "errors" - "fmt" "os" - "path/filepath" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" @@ -47,16 +45,18 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e for { select { case evt := <-w.Events: - if evt.Name == config.K9sViewConfigFile { + if evt.Name == config.AppViewsFile { s.QueueUpdateDraw(func() { - c.RefreshCustomViews() + if err := c.RefreshCustomViews(); err != nil { + log.Warn().Err(err).Msgf("Custom views refresh failed") + } }) } case err := <-w.Errors: log.Warn().Err(err).Msg("CustomView watcher failed") return case <-ctx.Done(): - log.Debug().Msgf("CustomViewWatcher CANCELED `%s!!", config.K9sViewConfigFile) + log.Debug().Msgf("CustomViewWatcher CANCELED `%s!!", config.AppViewsFile) if err := w.Close(); err != nil { log.Error().Err(err).Msg("Closing CustomView watcher") } @@ -65,23 +65,21 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e } }() - log.Debug().Msgf("CustomView watching `%s", config.K9sViewConfigFile) - c.RefreshCustomViews() - return w.Add(config.K9sHome()) + if err := c.RefreshCustomViews(); err != nil { + return err + } + return w.Add(config.AppViewsFile) } // RefreshCustomViews load view configuration changes. -func (c *Configurator) RefreshCustomViews() { +func (c *Configurator) RefreshCustomViews() error { if c.CustomView == nil { c.CustomView = config.NewCustomView() } else { c.CustomView.Reset() } - if err := c.CustomView.Load(config.K9sViewConfigFile); err != nil { - log.Warn().Err(err).Msgf("Custom view load failed %s", config.K9sViewConfigFile) - return - } + return c.CustomView.Load(config.AppViewsFile) } // StylesWatcher watches for skin file changes. @@ -102,7 +100,7 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod { log.Debug().Msgf("Skin changed: %s", c.skinFile) s.QueueUpdateDraw(func() { - c.RefreshStyles(c.Config.K9s.CurrentCluster) + c.RefreshStyles(c.Config.K9s.ActiveContextName()) }) } case err := <-w.Errors: @@ -122,42 +120,25 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error if err := w.Add(config.K9sHome()); err != nil { return err } - log.Debug().Msgf("SkinWatcher watching %q", config.K9sSkinDir) - return w.Add(config.K9sSkinDir) + log.Debug().Msgf("SkinWatcher watching %q", config.AppSkinsDir) + return w.Add(config.AppSkinsDir) } -// BenchConfig location of the benchmarks configuration file. -func BenchConfig(context string) string { - return filepath.Join(config.K9sHome(), config.K9sBench+"-"+context+".yml") -} - -func (c *Configurator) clusterFromContext(name string) (*config.Cluster, error) { - if c.Config == nil || c.Config.GetConnection() == nil { - return nil, fmt.Errorf("No config set in configurator") - } - - cc, err := c.Config.GetConnection().Config().Contexts() - if err != nil { - return nil, errors.New("unable to retrieve contexts map") - } - - context, ok := cc[name] - if !ok { - return nil, fmt.Errorf("no context named %s found", name) +// RefreshStyles load for skin configuration changes. +func (c *Configurator) RefreshStyles(context string) { + cluster := "na" + if c.Config != nil { + if ct, err := c.Config.K9s.ActiveContext(); err == nil { + cluster = ct.ClusterName + } } - cl, ok := c.Config.K9s.Clusters[context.Cluster] - if !ok { - return nil, fmt.Errorf("no cluster named %s found", context.Cluster) + if bc, err := config.EnsureBenchmarksCfgFile(cluster, context); err != nil { + log.Warn().Err(err).Msgf("No benchmark config file found for context: %s", context) + } else { + c.BenchFile = bc } - return cl, nil -} - -// RefreshStyles load for skin configuration changes. -func (c *Configurator) RefreshStyles(context string) { - c.BenchFile = BenchConfig(context) - if c.Styles == nil { c.Styles = config.NewStyles() } else { @@ -165,39 +146,29 @@ func (c *Configurator) RefreshStyles(context string) { } var skin string - cl, err := c.clusterFromContext(context) - if err != nil { - log.Warn().Err(err).Msgf("No cluster found. Using default skin") - } else { - skin = cl.Skin - } - - var ( - skinFile = filepath.Join(config.K9sSkinDir, skin+".yml") - ) - if skin != "" { - if err := c.Styles.Load(skinFile); err != nil { - if errors.Is(err, os.ErrNotExist) { - log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.K9sSkinDir) - } else { - log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err) - } - } else { - c.updateStyles(skinFile) - return + if c.Config != nil { + skin = c.Config.K9s.UI.Skin + ct, err := c.Config.K9s.ActiveContext() + if err != nil { + log.Warn().Msgf("No active context found. Using default skin") + } else if ct.Skin != "" { + skin = ct.Skin } } - - if err := c.Styles.Load(config.K9sStylesFile); err != nil { + if skin == "" { + c.updateStyles("") + return + } + var skinFile = config.SkinFileFromName(skin) + if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { - log.Warn().Msgf("No skin file found -- %s. Loading stock skins.", config.K9sStylesFile) + log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.AppSkinsDir) } else { - log.Error().Msgf("Failed to parse skin file -- %s. %s. Loading stock skins.", config.K9sStylesFile, err) + log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err) } - c.updateStyles("") - return + } else { + c.updateStyles(skinFile) } - c.updateStyles(config.K9sStylesFile) } func (c *Configurator) updateStyles(f string) { diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 25355d535f..9234131738 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -9,22 +9,49 @@ import ( "testing" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" ) func TestBenchConfig(t *testing.T) { - os.Setenv(config.K9sConfig, "/tmp/blee") - assert.Equal(t, "/tmp/blee/bench-fred.yml", ui.BenchConfig("fred")) + os.Setenv(config.K9sConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) + defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + + bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1") + assert.NoError(t, error) + assert.Equal(t, "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", bc) } -func TestConfiguratorRefreshStyle(t *testing.T) { - config.K9sStylesFile = filepath.Join("..", "config", "testdata", "black_and_wtf.yml") +func TestSkinnedContext(t *testing.T) { + os.Setenv(config.K9sConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) + defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + + sf := filepath.Join("..", "config", "testdata", "black_and_wtf.yaml") + raw, err := os.ReadFile(sf) + assert.NoError(t, err) + tf := filepath.Join(config.AppSkinsDir, "black_and_wtf.yaml") + assert.NoError(t, os.WriteFile(tf, raw, data.DefaultFileMod)) + + var cfg ui.Configurator + cfg.Config = mock.NewMockConfig() + cl, ct := "cl-1", "ct-1" + flags := genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + } - cfg := ui.Configurator{} - cfg.RefreshStyles("") + cfg.Config.K9s = config.NewK9s( + mock.NewMockConnection(), + mock.NewMockKubeSettings(&flags)) + cfg.Config.K9s.UI = config.UI{Skin: "black_and_wtf"} + cfg.RefreshStyles("ct-1") assert.True(t, cfg.HasSkin()) assert.Equal(t, tcell.ColorGhostWhite.TrueColor(), render.StdColor) diff --git a/internal/ui/crumbs_test.go b/internal/ui/crumbs_test.go index 7d992768db..644f27459f 100644 --- a/internal/ui/crumbs_test.go +++ b/internal/ui/crumbs_test.go @@ -49,11 +49,13 @@ func (c c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return func (c c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { return nil } -func (c c) SetRect(int, int, int, int) {} -func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 } -func (c c) GetFocusable() tview.Focusable { return c } -func (c c) Focus(func(tview.Primitive)) {} -func (c c) Blur() {} -func (c c) Start() {} -func (c c) Stop() {} -func (c c) Init(context.Context) error { return nil } +func (c c) SetRect(int, int, int, int) {} +func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 } +func (c c) GetFocusable() tview.Focusable { return c } +func (c c) Focus(func(tview.Primitive)) {} +func (c c) Blur() {} +func (c c) Start() {} +func (c c) Stop() {} +func (c c) Init(context.Context) error { return nil } +func (c c) SetFilter(string) {} +func (c c) SetLabelFilter(map[string]string) {} diff --git a/internal/ui/flash.go b/internal/ui/flash.go index 33c9a8c1e4..f188464193 100644 --- a/internal/ui/flash.go +++ b/internal/ui/flash.go @@ -85,7 +85,7 @@ func (f *Flash) SetMessage(m model.LevelMessage) { } func (f *Flash) flashEmoji(l model.FlashLevel) string { - if f.app.Config.K9s.NoIcons { + if f.app.Config.K9s.UI.NoIcons { return "" } // nolint:exhaustive diff --git a/internal/ui/flash_test.go b/internal/ui/flash_test.go index 1b5cd6e3ce..5122152efc 100644 --- a/internal/ui/flash_test.go +++ b/internal/ui/flash_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" "github.com/stretchr/testify/assert" @@ -25,7 +25,7 @@ func TestFlash(t *testing.T) { "err": {l: model.FlashErr, i: "hello", e: "😡 hello\n"}, } - a := ui.NewApp(config.NewConfig(nil), "test") + a := ui.NewApp(mock.NewMockConfig(), "test") f := ui.NewFlash(a) f.SetTestMode(true) ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/ui/indicator_test.go b/internal/ui/indicator_test.go index ad782274d2..40032517e6 100644 --- a/internal/ui/indicator_test.go +++ b/internal/ui/indicator_test.go @@ -7,12 +7,13 @@ import ( "testing" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/ui" "github.com/stretchr/testify/assert" ) func TestIndicatorReset(t *testing.T) { - i := ui.NewStatusIndicator(ui.NewApp(config.NewConfig(nil), ""), config.NewStyles()) + i := ui.NewStatusIndicator(ui.NewApp(mock.NewMockConfig(), ""), config.NewStyles()) i.SetPermanent("Blee") i.Info("duh") i.Reset() @@ -21,21 +22,21 @@ func TestIndicatorReset(t *testing.T) { } func TestIndicatorInfo(t *testing.T) { - i := ui.NewStatusIndicator(ui.NewApp(config.NewConfig(nil), ""), config.NewStyles()) + i := ui.NewStatusIndicator(ui.NewApp(mock.NewMockConfig(), ""), config.NewStyles()) i.Info("Blee") assert.Equal(t, "[lawngreen::b] \n", i.GetText(false)) } func TestIndicatorWarn(t *testing.T) { - i := ui.NewStatusIndicator(ui.NewApp(config.NewConfig(nil), ""), config.NewStyles()) + i := ui.NewStatusIndicator(ui.NewApp(mock.NewMockConfig(), ""), config.NewStyles()) i.Warn("Blee") assert.Equal(t, "[mediumvioletred::b] \n", i.GetText(false)) } func TestIndicatorErr(t *testing.T) { - i := ui.NewStatusIndicator(ui.NewApp(config.NewConfig(nil), ""), config.NewStyles()) + i := ui.NewStatusIndicator(ui.NewApp(mock.NewMockConfig(), ""), config.NewStyles()) i.Err("Blee") assert.Equal(t, "[orangered::b] \n", i.GetText(false)) diff --git a/internal/ui/pages.go b/internal/ui/pages.go index 580bb09628..446457156c 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -102,7 +102,7 @@ func (p *Pages) StackTop(top model.Component) { func componentID(c model.Component) string { if c.Name() == "" { - panic("Component has no name") + log.Error().Msg("Component has no name") } return fmt.Sprintf("%s-%p", c.Name(), c) } diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go index 1348ca7301..56a6e25025 100644 --- a/internal/ui/prompt_test.go +++ b/internal/ui/prompt_test.go @@ -70,7 +70,10 @@ func TestPromptColor(t *testing.T) { app := ui.App{} // Make sure to have different values to be sure that the prompt color actually changes depending on its type - assert.NotEqual(t, styles.Prompt().Border.DefaultColor.Color(), styles.Prompt().Border.CommandColor.Color()) + assert.NotEqual(t, + styles.Prompt().Border.DefaultColor.Color(), + styles.Prompt().Border.CommandColor.Color(), + ) testCases := []struct { kind model.BufferKind diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index cf07c9f4d1..25f33c0f19 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -15,7 +15,8 @@ type SelectTable struct { model Tabular selectedFn func(string) string marks map[string]struct{} - fgColor tcell.Color + selFgColor tcell.Color + selBgColor tcell.Color } // SetModel sets the table model. @@ -102,7 +103,7 @@ func (s *SelectTable) GetSelectedRowIndex() int { } // SelectRow select a given row by index. -func (s *SelectTable) SelectRow(r int, broadcast bool) { +func (s *SelectTable) SelectRow(r, c int, broadcast bool) { if !broadcast { s.SetSelectionChangedFunc(nil) } @@ -110,13 +111,13 @@ func (s *SelectTable) SelectRow(r int, broadcast bool) { r = c + 1 } defer s.SetSelectionChangedFunc(s.selectionChanged) - s.Select(r, 0) + s.Select(r, c) } // UpdateSelection refresh selected row. func (s *SelectTable) updateSelection(broadcast bool) { - r, _ := s.GetSelection() - s.SelectRow(r, broadcast) + r, c := s.GetSelection() + s.SelectRow(r, c, broadcast) } func (s *SelectTable) selectionChanged(r, c int) { @@ -124,7 +125,9 @@ func (s *SelectTable) selectionChanged(r, c int) { return } if cell := s.GetCell(r, c); cell != nil { - s.SetSelectedStyle(tcell.StyleDefault.Foreground(s.fgColor).Background(cell.Color).Attributes(tcell.AttrBold)) + s.SetSelectedStyle( + tcell.StyleDefault.Foreground(s.selFgColor). + Background(cell.Color).Attributes(tcell.AttrBold)) } } diff --git a/internal/ui/table.go b/internal/ui/table.go index 47d1714b0b..d2f2f7adb0 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -14,6 +14,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/vul" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" @@ -98,8 +99,11 @@ func (t *Table) StylesChanged(s *config.Styles) { t.SetBackgroundColor(s.Table().BgColor.Color()) t.SetBorderColor(s.Frame().Border.FgColor.Color()) t.SetBorderFocusColor(s.Frame().Border.FocusColor.Color()) - t.SetSelectedStyle(tcell.StyleDefault.Foreground(t.styles.Table().CursorFgColor.Color()).Background(t.styles.Table().CursorBgColor.Color()).Attributes(tcell.AttrBold)) - t.fgColor = s.Table().CursorFgColor.Color() + t.SetSelectedStyle( + tcell.StyleDefault.Foreground(t.styles.Table().CursorFgColor.Color()). + Background(t.styles.Table().CursorBgColor.Color()).Attributes(tcell.AttrBold)) + t.selFgColor = s.Table().CursorFgColor.Color() + t.selBgColor = s.Table().CursorBgColor.Color() t.Refresh() } @@ -241,6 +245,10 @@ func (t *Table) doUpdate(data *render.TableData) { if h.MX && !t.hasMetrics { continue } + if h.VS && vul.ImgScanner == nil { + continue + } + t.AddHeaderCell(col, h) c := t.GetCell(0, col) c.SetBackgroundColor(bg) @@ -286,6 +294,9 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M if h[c].MX && !t.hasMetrics { continue } + if h[c].VS && vul.ImgScanner == nil { + continue + } if !re.Deltas.IsBlank() && !h.IsTimeCol(c) { field += Deltas(re.Deltas[c], field) @@ -427,7 +438,7 @@ func (t *Table) UpdateTitle() { } func (t *Table) styleTitle() string { - rc := t.GetRowCount() + rc := int64(t.GetRowCount()) if rc > 0 { rc-- } @@ -451,17 +462,20 @@ func (t *Table) styleTitle() string { } var title string if ns == client.ClusterScope { - title = SkinTitle(fmt.Sprintf(TitleFmt, base, rc), t.styles.Frame()) + title = SkinTitle(fmt.Sprintf(TitleFmt, base, render.AsThousands(rc)), t.styles.Frame()) } else { - title = SkinTitle(fmt.Sprintf(NSTitleFmt, base, ns, rc), t.styles.Frame()) + title = SkinTitle(fmt.Sprintf(NSTitleFmt, base, ns, render.AsThousands(rc)), t.styles.Frame()) } buff := t.cmdBuff.GetText() - if buff == "" { - return title - } if IsLabelSelector(buff) { buff = TrimLabelSelector(buff) + } else if l := t.GetModel().GetLabelFilter(); l != "" { + buff = l + } + + if buff == "" { + return title } return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), t.styles.Frame()) diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 5575c94528..85a6e6896b 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -24,10 +24,10 @@ const ( SearchFmt = "<[filter:bg:r]/%s[fg:bg:-]> " // NSTitleFmt represents a namespaced view title. - NSTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] " + NSTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] " // TitleFmt represents a standard view title. - TitleFmt = "[fg:bg:b] %s[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] " + TitleFmt = "[fg:bg:b] %s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] " descIndicator = "↓" ascIndicator = "↑" @@ -71,7 +71,12 @@ func IsLabelSelector(s string) bool { if s == "" { return false } - return LabelRx.MatchString(s) + + if LabelRx.MatchString(s) { + return true + } + + return !strings.Contains(s, " ") && strings.Contains(s, "=") } // IsFuzzySelector checks if query is fuzzy. @@ -92,7 +97,11 @@ func IsInverseSelector(s string) bool { // TrimLabelSelector extracts label query. func TrimLabelSelector(s string) string { - return strings.TrimSpace(s[2:]) + if strings.Index(s, "-l") == 0 { + return strings.TrimSpace(s[2:]) + } + + return s } // SkinTitle decorates a title. diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index f532643ef8..072e72970d 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -11,19 +11,20 @@ import ( func TestIsLabelSelector(t *testing.T) { uu := map[string]struct { - sel string - e bool + s string + ok bool }{ - "cool": {"-l app=fred,env=blee", true}, - "noMode": {"app=fred,env=blee", false}, - "noSpace": {"-lapp=fred,env=blee", true}, - "wrongLabel": {"-f app=fred,env=blee", false}, + "empty": {s: ""}, + "cool": {s: "-l app=fred,env=blee", ok: true}, + "no-flag": {s: "app=fred,env=blee", ok: true}, + "no-space": {s: "-lapp=fred,env=blee", ok: true}, + "wrong-flag": {s: "-f app=fred,env=blee"}, } for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, IsLabelSelector(u.sel)) + assert.Equal(t, u.ok, IsLabelSelector(u.s)) }) } } diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index ab5e09ba01..2962de9339 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -44,7 +44,7 @@ func TestTableSelection(t *testing.T) { m := &mockModel{} v.SetModel(m) v.Update(m.Peek(), false) - v.SelectRow(1, true) + v.SelectRow(1, 0, true) r, ok := v.GetSelectedRow("r1") assert.True(t, ok) @@ -68,6 +68,7 @@ var _ ui.Tabular = &mockModel{} func (t *mockModel) SetInstance(string) {} func (t *mockModel) SetLabelFilter(string) {} +func (t *mockModel) GetLabelFilter() string { return "" } func (t *mockModel) Empty() bool { return false } func (t *mockModel) Count() int { return 1 } func (t *mockModel) HasMetrics() bool { return true } @@ -83,15 +84,12 @@ func (t *mockModel) Watch(context.Context) error { return nil } func (t *mockModel) Get(ctx context.Context, path string) (runtime.Object, error) { return nil, nil } - func (t *mockModel) Delete(context.Context, string, *metav1.DeletionPropagation, dao.Grace) error { return nil } - func (t *mockModel) Describe(context.Context, string) (string, error) { return "", nil } - func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) { return "", nil } diff --git a/internal/ui/types.go b/internal/ui/types.go index b426bc24cf..8013c5e7e8 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -57,6 +57,9 @@ type Tabular interface { // SetLabelFilter sets the label filter. SetLabelFilter(string) + // GetLabelFilter fetch the label filter. + GetLabelFilter() string + // Empty returns true if model has no data. Empty() bool diff --git a/internal/view/actions.go b/internal/view/actions.go index 713778175e..324e623318 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -90,11 +90,11 @@ func gotoCmd(r Runner, cmd, path string) ui.ActionHandler { func pluginActions(r Runner, aa ui.KeyActions) { pp := config.NewPlugins() - if err := pp.Load(); err != nil { + if err := pp.Load(r.App().Config.ContextPluginsPath()); err != nil { return } - for k, plugin := range pp.Plugin { + for k, plugin := range pp.Plugins { if !inScope(plugin.Scopes, r.Aliases()) { continue } diff --git a/internal/view/alias.go b/internal/view/alias.go index fc33dc9237..7e654cba4d 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -54,7 +54,7 @@ func (a *Alias) bindKeys(aa ui.KeyActions) { tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true), ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd("RESOURCE", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd("COMMAND", true), false), - ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd("APIGROUP", true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd("API-GROUP", true), false), }) } diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 1b82b378bf..15cd393694 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -10,7 +10,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" @@ -18,7 +18,6 @@ import ( "github.com/derailed/k9s/internal/view" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -74,33 +73,11 @@ func (b *buffL) BufferActive(state bool, kind model.BufferKind) { } func makeContext() context.Context { - a := view.NewApp(config.NewConfig(ks{})) + a := view.NewApp(mock.NewMockConfig()) ctx := context.WithValue(context.Background(), internal.KeyApp, a) return context.WithValue(ctx, internal.KeyStyles, a.Styles) } -type ks struct{} - -func (k ks) CurrentContextName() (string, error) { - return "test", nil -} - -func (k ks) CurrentClusterName() (string, error) { - return "test", nil -} - -func (k ks) CurrentNamespaceName() (string, error) { - return "test", nil -} - -func (k ks) ClusterNames() (map[string]struct{}, error) { - return map[string]struct{}{"test": {}}, nil -} - -func (k ks) NamespaceNames(nn []v1.Namespace) []string { - return []string{"test"} -} - type mockModel struct{} var ( @@ -114,6 +91,7 @@ func (t *mockModel) PrevSuggestion() (string, bool) { return "", false } func (t *mockModel) ClearSuggestions() {} func (t *mockModel) SetInstance(string) {} func (t *mockModel) SetLabelFilter(string) {} +func (t *mockModel) GetLabelFilter() string { return "" } func (t *mockModel) Empty() bool { return false } func (t *mockModel) Count() int { return 1 } func (t *mockModel) HasMetrics() bool { return true } diff --git a/internal/view/app.go b/internal/view/app.go index 374f0ba74c..9cbf742dd2 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -23,12 +23,12 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" + "github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/vul" "github.com/derailed/k9s/internal/watch" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ExitStatus indicates UI exit conditions. @@ -61,7 +61,7 @@ type App struct { // NewApp returns a K9s app instance. func NewApp(cfg *config.Config) *App { a := App{ - App: ui.NewApp(cfg, cfg.K9s.CurrentContext), + App: ui.NewApp(cfg, cfg.K9s.ActiveContextName()), cmdHistory: model.NewHistory(model.MaxHistory), filterHistory: model.NewHistory(model.MaxHistory), Content: NewPageStack(), @@ -113,7 +113,7 @@ func (a *App) Init(version string, rate int) error { } a.command = NewCommand(a) - if err := a.command.Init(); err != nil { + if err := a.command.Init(a.Config.ContextAliasesPath()); err != nil { return err } a.CmdBuff().SetSuggestionFn(a.suggestCommand()) @@ -121,7 +121,7 @@ func (a *App) Init(version string, rate int) error { a.layout(ctx) a.initSignals() - if a.Config.K9s.EnableImageScan { + if a.Config.K9s.ImageScans.Enable { a.initImgScanner(version) } @@ -139,7 +139,7 @@ func (a *App) initImgScanner(version string) { log.Debug().Msgf("Scanner init time %s", time.Since(t)) }(time.Now()) - vul.ImgScanner = vul.NewImageScanner() + vul.ImgScanner = vul.NewImageScanner(a.Config.K9s.ImageScans) go vul.ImgScanner.Init("k9s", version) } @@ -186,39 +186,30 @@ func (a *App) suggestCommand() model.SuggestionFunc { s = strings.ToLower(s) for _, k := range a.command.alias.Aliases.Keys() { - if suggest, ok := shouldAddSuggest(s, k); ok { + if suggest, ok := cmd.ShouldAddSuggest(s, k); ok { entries = append(entries, suggest) } } - namespaceNames, err := a.namespaceNames() + namespaceNames, err := a.factory.Client().ValidNamespaceNames() if err != nil { log.Error().Err(err).Msg("failed to list namespaces") } - entries = append(entries, suggestSubCommand(s, namespaceNames, contextNames)...) + entries = append(entries, cmd.SuggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { return nil } + entries.Sort() return } } -func (a *App) namespaceNames() ([]string, error) { - namespaces, err := a.factory.Client().ValidNamespaces() - if err != nil { - return nil, err - } - - namespaceNames := make([]string, 0, len(namespaces)) - for _, namespace := range namespaces { - namespaceNames = append(namespaceNames, namespace.Name) - } - return namespaceNames, nil -} - func (a *App) contextNames() ([]string, error) { + if !a.Conn().ConnectionOK() { + return nil, errors.New("no connection") + } contexts, err := a.factory.Client().Config().Contexts() if err != nil { return nil, err @@ -302,11 +293,13 @@ func (a *App) buildHeader() tview.Primitive { } clWidth := clusterInfoWidth - n, err := a.Conn().Config().CurrentClusterName() - if err == nil { - size := len(n) + clusterInfoPad - if size > clWidth { - clWidth = size + if a.Conn().ConnectionOK() { + n, err := a.Conn().Config().CurrentClusterName() + if err == nil { + size := len(n) + clusterInfoPad + if size > clWidth { + clWidth = size + } } } header.AddItem(a.clusterInfo(), clWidth, 1, false) @@ -338,6 +331,8 @@ func (a *App) Resume() { } if err := a.CustomViewsWatcher(ctx, a); err != nil { log.Warn().Err(err).Msgf("CustomView watcher failed") + } else { + log.Debug().Msgf("CustomViews watching `%s", config.AppViewsFile) } } @@ -400,7 +395,7 @@ func (a *App) refreshCluster(context.Context) error { // Reload alias go func() { - if err := a.command.Reset(false); err != nil { + if err := a.command.Reset(a.Config.ContextAliasesPath(), false); err != nil { log.Error().Err(err).Msgf("Command reset failed") } }() @@ -413,12 +408,8 @@ func (a *App) refreshCluster(context.Context) error { func (a *App) switchNS(ns string) error { if ns == client.ClusterScope { - ns = client.AllNamespaces - } - if ns == a.Config.ActiveNamespace() { - return nil + ns = client.BlankNamespace } - ok, err := a.isValidNS(ns) if err != nil { return err @@ -437,56 +428,51 @@ func (a *App) switchNS(ns string) error { } func (a *App) isValidNS(ns string) (bool, error) { - if ns == client.AllNamespaces || ns == client.NamespaceAll { + if ns == client.BlankNamespace || ns == client.NamespaceAll { return true, nil } - ctx, cancel := context.WithTimeout(context.Background(), a.Conn().Config().CallTimeout()) - defer cancel() - dial, err := a.Conn().Dial() - if err != nil { - return false, err - } - _, err = dial.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{}) - if err != nil { - log.Warn().Err(err).Msgf("Validation failed for namespace: %q", ns) + if !a.Conn().IsValidNamespace(ns) { + return false, fmt.Errorf("invalid namespace: %q", ns) } return true, nil } -func (a *App) switchContext(name string) error { +func (a *App) switchContext(ci *cmd.Interpreter) error { + name, ok := ci.HasContext() + if !ok { + return nil + } + log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView()) a.Halt() defer a.Resume() { - ns, err := a.Conn().Config().CurrentNamespaceName() - if err != nil { - log.Warn().Msg("No namespace specified in context. Using K9s config") - ns = a.Config.ActiveNamespace() - } - a.initFactory(ns) - - if e := a.command.Reset(true); e != nil { - return e - } - if a.Config.ActiveView() == "" || isContextCmd(a.Config.ActiveView()) { + p := cmd.NewInterpreter(a.Config.ActiveView()) + if p.IsContextCmd() { a.Config.SetActiveView("pod") } + p.ResetContextArg() + a.Config.Reset() - a.Config.K9s.CurrentContext = name - cluster, err := a.Conn().Config().CurrentClusterName() + ct, err := a.Config.K9s.ActivateContext(name) if err != nil { return err } - a.Config.K9s.CurrentCluster = cluster - if err := a.Config.SetActiveNamespace(ns); err != nil { - log.Error().Err(err).Msg("unable to set active ns") + if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil { + return err + } + if cns, ok := ci.NSArg(); ok { + ct.Namespace.Active = cns } if err := a.Config.Save(); err != nil { log.Error().Err(err).Msg("config save failed!") } + ns := a.Config.ActiveNamespace() + a.initFactory(ns) + a.Flash().Infof("Switching context to %s", name) a.ReloadStyles(name) a.gotoResource(a.Config.ActiveView(), "", true) @@ -682,10 +668,6 @@ func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { } func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { - if a.CmdBuff().InCmdMode() { - return evt - } - if a.Content.Top() != nil && a.Content.Top().Name() == aliasTitle { a.Content.Pop() return nil @@ -698,8 +680,8 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (a *App) gotoResource(cmd, path string, clearStack bool) { - err := a.command.run(cmd, path, clearStack) +func (a *App) gotoResource(c, path string, clearStack bool) { + err := a.command.run(cmd.NewInterpreter(c), path, clearStack) if err != nil { dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error()) } @@ -727,45 +709,3 @@ func (a *App) clusterInfo() *ClusterInfo { func (a *App) statusIndicator() *ui.StatusIndicator { return a.Views()["statusIndicator"].(*ui.StatusIndicator) } - -// ---------------------------------------------------------------------------- -// Helpers - -func suggestSubCommand(command string, namespaces, contexts []string) []string { - cmds := strings.Fields(command) - if len(cmds[0]) == 0 || len(cmds) != 2 { - return nil - } - - var suggests []string - switch strings.ToLower(cmds[0]) { - case "cow", "q", "q!", "qa", "Q", "quit", "?", "h", "help", "a", "alias", "x", "xray", "dir": - return nil // ignore special commands - case "ctx", "context", "contexts": - for _, ctxName := range contexts { - if suggest, ok := shouldAddSuggest(cmds[1], ctxName); ok { - suggests = append(suggests, suggest) - } - } - default: - if suggest, ok := shouldAddSuggest(cmds[1], client.NamespaceAll); ok { - suggests = append(suggests, suggest) - } - - for _, ns := range namespaces { - if suggest, ok := shouldAddSuggest(cmds[1], ns); ok { - suggests = append(suggests, suggest) - } - } - } - - return suggests -} - -func shouldAddSuggest(command, suggest string) (string, bool) { - if command != suggest && strings.HasPrefix(suggest, command) { - return strings.TrimPrefix(suggest, command), true - } - - return "", false -} diff --git a/internal/view/app_int_test.go b/internal/view/app_int_test.go deleted file mode 100644 index f20a26c098..0000000000 --- a/internal/view/app_int_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package view - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_suggestSubCommand(t *testing.T) { - namespaceNames := []string{"kube-system", "kube-public", "default", "nginx-ingress"} - contextNames := []string{"develop", "test", "pre", "prod"} - - tests := []struct { - Command string - Suggestions []string - }{ - {Command: "q", Suggestions: nil}, - {Command: "xray dp", Suggestions: nil}, - {Command: "help k", Suggestions: nil}, - {Command: "ctx p", Suggestions: []string{"re", "rod"}}, - {Command: "ctx p", Suggestions: []string{"re", "rod"}}, - {Command: "ctx pr", Suggestions: []string{"e", "od"}}, - {Command: "context d", Suggestions: []string{"evelop"}}, - {Command: "contexts t", Suggestions: []string{"est"}}, - {Command: "po ", Suggestions: nil}, - {Command: "po x", Suggestions: nil}, - {Command: "po k", Suggestions: []string{"ube-system", "ube-public"}}, - {Command: "po kube-", Suggestions: []string{"system", "public"}}, - } - - for _, tt := range tests { - got := suggestSubCommand(tt.Command, namespaceNames, contextNames) - assert.Equal(t, tt.Suggestions, got) - } -} diff --git a/internal/view/app_test.go b/internal/view/app_test.go index e214d7c4db..924fb8f2cc 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -6,13 +6,13 @@ package view_test import ( "testing" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" ) func TestAppNew(t *testing.T) { - a := view.NewApp(config.NewConfig(ks{})) + a := view.NewApp(mock.NewMockConfig()) _ = a.Init("blee", 10) assert.Equal(t, 11, len(a.GetActions())) diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index daa26be302..8a5232eca5 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -12,9 +12,9 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/perf" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" + "github.com/rs/zerolog/log" ) // Benchmark represents a service benchmark results view. @@ -40,7 +40,7 @@ func (b *Benchmark) benchContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config)) } -func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) { +func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr client.GVR, path string) { data, err := readBenchFile(app.Config, b.benchFile()) if err != nil { app.Flash().Errf("Unable to load bench file %s", err) @@ -68,7 +68,15 @@ func fileToSubject(path string) string { } func benchDir(cfg *config.Config) string { - return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) + ct, err := cfg.K9s.ActiveContext() + if err != nil { + log.Error().Err(err).Msgf("no active context located") + } + return filepath.Join( + config.AppBenchmarksDir, + config.SanitizeFileName(ct.ClusterName), + config.SanitizeFilename(cfg.K9s.ActiveContextName()), + ) } func readBenchFile(cfg *config.Config, n string) (string, error) { diff --git a/internal/view/browser.go b/internal/view/browser.go index 240f4dffb0..429e3e94bb 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -14,7 +14,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" @@ -166,6 +166,15 @@ func (b *Browser) Stop() { b.Table.Stop() } +func (b *Browser) SetFilter(s string) { + b.CmdBuff().SetText(s, "") +} + +func (b *Browser) SetLabelFilter(labels map[string]string) { + b.CmdBuff().SetText(toLabelsStr(labels), "") + b.GetModel().SetLabelFilter(toLabelsStr(labels)) +} + // BufferChanged indicates the buffer was changed. func (b *Browser) BufferChanged(_, _ string) {} @@ -280,7 +289,9 @@ func (b *Browser) helpCmd(evt *tcell.EventKey) *tcell.EventKey { func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey { if !b.CmdBuff().InCmdMode() { b.CmdBuff().ClearText(false) + b.GetModel().SetLabelFilter("") return b.App().PrevCmd(evt) + } b.CmdBuff().Reset() @@ -317,7 +328,7 @@ func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey { if b.enterFn != nil { f = b.enterFn } - f(b.app, b.GetModel(), b.GVR().String(), path) + f(b.app, b.GetModel(), b.GVR(), path) return nil } @@ -357,7 +368,7 @@ func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey { if path == "" { return evt } - describeResource(b.app, b.GetModel(), b.GVR().String(), path) + describeResource(b.app, b.GetModel(), b.GVR(), path) return nil } @@ -383,7 +394,7 @@ func editRes(app *App, gvr client.GVR, path string) error { } ns, n := client.Namespaced(path) if client.IsClusterScoped(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } if gvr.String() == "v1/namespaces" { ns = n @@ -395,7 +406,7 @@ func editRes(app *App, gvr client.GVR, path string) error { args := make([]string, 0, 10) args = append(args, "edit") args = append(args, gvr.FQN(n)) - if ns != client.AllNamespaces { + if ns != client.BlankNamespace { args = append(args, "-n", ns) } if err := runK(app, shellOpts{clear: true, args: args}); err != nil { @@ -434,7 +445,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { b.app.Flash().Infof("Viewing namespace `%s`...", ns) b.refresh() b.UpdateTitle() - b.SelectRow(1, true) + b.SelectRow(1, 0, true) b.app.CmdBuff().Reset() if err := b.app.Config.SetActiveNamespace(b.GetModel().GetNamespace()); err != nil { log.Error().Err(err).Msg("Config save NS failed!") @@ -462,14 +473,13 @@ func (b *Browser) setNamespace(ns string) { func (b *Browser) defaultContext() context.Context { ctx := context.WithValue(context.Background(), internal.KeyFactory, b.app.factory) - ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR().String()) - if b.Path != "" { - ctx = context.WithValue(ctx, internal.KeyPath, b.Path) - } + ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR()) + ctx = context.WithValue(ctx, internal.KeyPath, b.Path) if ui.IsLabelSelector(b.CmdBuff().GetText()) { ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText())) } ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace())) + ctx = context.WithValue(ctx, internal.KeyWithMetrics, b.app.factory.Client().HasMetrics()) return ctx } @@ -514,7 +524,7 @@ func (b *Browser) namespaceActions(aa ui.KeyActions) { if !b.meta.Namespaced || b.GetTable().Path != "" { return } - b.namespaces = make(map[int]string, config.MaxFavoritesNS) + b.namespaces = make(map[int]string, data.MaxFavoritesNS) aa[ui.Key0] = ui.NewKeyAction(client.NamespaceAll, b.switchNamespaceCmd, true) b.namespaces[0] = client.NamespaceAll index := 1 diff --git a/internal/view/cm.go b/internal/view/cm.go index c1694ca6dc..05c36da2a1 100644 --- a/internal/view/cm.go +++ b/internal/view/cm.go @@ -35,10 +35,10 @@ func (s *ConfigMap) bindKeys(aa ui.KeyActions) { } func (s *ConfigMap) refCmd(evt *tcell.EventKey) *tcell.EventKey { - return scanRefs(evt, s.App(), s.GetTable(), "v1/configmaps") + return scanRefs(evt, s.App(), s.GetTable(), dao.CmGVR) } -func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr string) *tcell.EventKey { +func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.EventKey { path := t.GetSelectedItem() if path == "" { return evt @@ -64,7 +64,7 @@ func scanRefs(evt *tcell.EventKey, a *App, t *Table, gvr string) *tcell.EventKey return nil } -func refContext(gvr, path string, wait bool) ContextFunc { +func refContext(gvr client.GVR, path string, wait bool) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) ctx = context.WithValue(ctx, internal.KeyGVR, gvr) diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go new file mode 100644 index 0000000000..73afbffbb2 --- /dev/null +++ b/internal/view/cmd/args.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import ( + "strings" +) + +const ( + nsKey = "ns" + topicKey = "topic" + filterKey = "filter" + labelKey = "labels" + contextKey = "context" +) + +type args map[string]string + +func newArgs(p *Interpreter, aa []string) args { + args := make(args, len(aa)) + if len(aa) == 0 { + return args + } + + for i := 0; i < len(aa); i++ { + a := strings.TrimSpace(aa[i]) + switch { + case strings.Index(a, contextFlag) == 0: + args[contextKey] = a[1:] + + case strings.Index(a, fuzzyFlag) == 0: + i++ + args[filterKey] = strings.TrimSpace(aa[i]) + continue + + case strings.Index(a, filterFlag) == 0: + args[filterKey] = a[1:] + + case strings.Contains(a, labelFlag): + if ll := toLabels(a); len(ll) != 0 { + args[labelKey] = a + } + + default: + a := strings.TrimSpace(aa[i]) + switch { + case p.IsContextCmd(): + args[contextKey] = a + case p.IsDirCmd(): + if _, ok := args[topicKey]; !ok { + args[topicKey] = a + } + case p.IsXrayCmd(): + if _, ok := args[topicKey]; ok { + args[nsKey] = a + } else { + args[topicKey] = a + } + default: + args[nsKey] = a + } + } + } + + return args +} + +func (a args) hasFilters() bool { + _, fok := a[filterKey] + _, lok := a[labelKey] + + return fok || lok +} diff --git a/internal/view/cmd/args_test.go b/internal/view/cmd/args_test.go new file mode 100644 index 0000000000..df4e4c319b --- /dev/null +++ b/internal/view/cmd/args_test.go @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlagsNew(t *testing.T) { + uu := map[string]struct { + i *Interpreter + aa []string + ll args + }{ + "empty": { + i: NewInterpreter("po"), + ll: make(args), + }, + "ns": { + i: NewInterpreter("po"), + aa: []string{"ns1"}, + ll: args{nsKey: "ns1"}, + }, + "ns+spaces": { + i: NewInterpreter("po"), + aa: []string{" ns1 "}, + ll: args{nsKey: "ns1"}, + }, + "filter": { + i: NewInterpreter("po"), + aa: []string{"/fred"}, + ll: args{filterKey: "fred"}, + }, + "inverse-filter": { + i: NewInterpreter("po"), + aa: []string{"/!fred"}, + ll: args{filterKey: "!fred"}, + }, + "fuzzy-filter": { + i: NewInterpreter("po"), + aa: []string{"-f", "fred"}, + ll: args{filterKey: "fred"}, + }, + "filter+ns": { + i: NewInterpreter("po"), + aa: []string{"/fred", " ns1 "}, + ll: args{nsKey: "ns1", filterKey: "fred"}, + }, + "label": { + i: NewInterpreter("po"), + aa: []string{"app=fred"}, + ll: args{labelKey: "app=fred"}, + }, + "label-toast": { + i: NewInterpreter("po"), + aa: []string{"="}, + ll: make(args), + }, + "multi-labels": { + i: NewInterpreter("po"), + aa: []string{"app=fred,blee=duh"}, + ll: args{labelKey: "app=fred,blee=duh"}, + }, + "label+ns": { + i: NewInterpreter("po"), + aa: []string{"a=b,c=d", " ns1 "}, + ll: args{labelKey: "a=b,c=d", nsKey: "ns1"}, + }, + "full-monty": { + i: NewInterpreter("po"), + aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg"}, + ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1"}, + }, + "full-monty+ctx": { + i: NewInterpreter("po"), + aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg", "@ctx1"}, + ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1", contextKey: "ctx1"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + l := newArgs(u.i, u.aa) + assert.Equal(t, len(u.ll), len(l)) + assert.Equal(t, u.ll, l) + }) + } +} + +func TestFlagsHasFilters(t *testing.T) { + uu := map[string]struct { + i *Interpreter + aa []string + ok bool + }{ + "empty": {}, + "ns": { + i: NewInterpreter("po"), + aa: []string{"ns1"}, + }, + "filter": { + i: NewInterpreter("po"), + aa: []string{"/fred"}, + ok: true, + }, + "inverse-filter": { + i: NewInterpreter("po"), + aa: []string{"/!fred"}, + ok: true, + }, + "fuzzy-filter": { + i: NewInterpreter("po"), + aa: []string{"-f", "fred"}, + ok: true, + }, + "filter+ns": { + i: NewInterpreter("po"), + aa: []string{"/fred", "ns1"}, + ok: true, + }, + "label": { + i: NewInterpreter("po"), + aa: []string{"app=fred"}, + ok: true, + }, + "multi-labels": { + i: NewInterpreter("po"), + aa: []string{"app=fred,blee=duh"}, + ok: true, + }, + "label+ns": { + i: NewInterpreter("po"), + aa: []string{"app=fred", "ns1"}, + ok: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + l := newArgs(u.i, u.aa) + assert.Equal(t, u.ok, l.hasFilters()) + }) + } +} diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go new file mode 100644 index 0000000000..5d53d63398 --- /dev/null +++ b/internal/view/cmd/helpers.go @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import ( + "slices" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/rs/zerolog/log" +) + +func toLabels(s string) map[string]string { + ll := strings.Split(s, ",") + lbls := make(map[string]string, len(ll)) + for _, l := range ll { + kv := strings.Split(l, "=") + if len(kv) < 2 || kv[0] == "" || kv[1] == "" { + continue + } + lbls[kv[0]] = kv[1] + } + + if len(lbls) == 0 { + return nil + } + + return lbls +} + +// ShouldAddSuggest checks if a suggestion match the given command. +func ShouldAddSuggest(command, suggest string) (string, bool) { + if command != suggest && strings.HasPrefix(suggest, command) { + return strings.TrimPrefix(suggest, command), true + } + + return "", false +} + +// SuggestSubCommand suggests namespaces or contexts based on current command. +func SuggestSubCommand(command string, namespaces client.NamespaceNames, contexts []string) []string { + p := NewInterpreter(command) + var suggests []string + switch { + case p.IsCowCmd(): + fallthrough + case p.IsHelpCmd(): + fallthrough + case p.IsAliasCmd(): + fallthrough + case p.IsBailCmd(): + fallthrough + case p.IsDirCmd(): + fallthrough + case p.IsAliasCmd(): + return nil + + case p.IsXrayCmd(): + _, ns, ok := p.XrayArgs() + if !ok || ns == "" { + return nil + } + suggests = completeNS(ns, namespaces) + + case p.IsContextCmd(): + n, ok := p.ContextArg() + if !ok { + return nil + } + suggests = completeCtx(n, contexts) + + case p.HasNS(): + if n, ok := p.HasContext(); ok { + suggests = completeCtx(n, contexts) + } + log.Debug().Msgf("!!SUGG CTX!! %#v", suggests) + if len(suggests) > 0 { + break + } + + ns, ok := p.NSArg() + if !ok { + return nil + } + suggests = completeNS(ns, namespaces) + log.Debug().Msgf("!!SUGG NS!! %#v", suggests) + + default: + if n, ok := p.HasContext(); ok { + suggests = completeCtx(n, contexts) + } + } + slices.Sort(suggests) + + return suggests +} + +func completeNS(s string, nn client.NamespaceNames) []string { + var suggests []string + if suggest, ok := ShouldAddSuggest(s, client.NamespaceAll); ok { + suggests = append(suggests, suggest) + } + for ns := range nn { + if suggest, ok := ShouldAddSuggest(s, ns); ok { + suggests = append(suggests, suggest) + } + } + + return suggests +} + +func completeCtx(s string, cc []string) []string { + var suggests []string + for _, ctxName := range cc { + if suggest, ok := ShouldAddSuggest(s, ctxName); ok { + suggests = append(suggests, suggest) + } + } + + return suggests +} diff --git a/internal/view/cmd/helpers_test.go b/internal/view/cmd/helpers_test.go new file mode 100644 index 0000000000..9b9f5dc406 --- /dev/null +++ b/internal/view/cmd/helpers_test.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import ( + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" +) + +func init() { + zerolog.SetGlobalLevel(zerolog.FatalLevel) +} + +func Test_toLabels(t *testing.T) { + uu := map[string]struct { + s string + ll map[string]string + }{ + "empty": {}, + "toast": { + s: "=", + }, + "toast-1": { + s: "=,", + }, + "toast-2": { + s: ",", + }, + "toast-3": { + s: ",=", + }, + "simple": { + s: "a=b", + ll: map[string]string{"a": "b"}, + }, + "multi": { + s: "a=b,c=d", + ll: map[string]string{"a": "b", "c": "d"}, + }, + "multi-toast1": { + s: "a=,c=d", + ll: map[string]string{"c": "d"}, + }, + "multi-toast2": { + s: "a=b,=d", + ll: map[string]string{"a": "b"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.ll, toLabels(u.s)) + }) + } +} + +func TestSuggestSubCommand(t *testing.T) { + namespaceNames := map[string]struct{}{ + "kube-system": {}, + "kube-public": {}, + "default": {}, + "nginx-ingress": {}, + } + contextNames := []string{"develop", "test", "pre", "prod"} + + tests := []struct { + Command string + Suggestions []string + }{ + {Command: "q", Suggestions: nil}, + {Command: "xray dp", Suggestions: nil}, + {Command: "help k", Suggestions: nil}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx p", Suggestions: []string{"re", "rod"}}, + {Command: "ctx pr", Suggestions: []string{"e", "od"}}, + {Command: "context d", Suggestions: []string{"evelop"}}, + {Command: "contexts t", Suggestions: []string{"est"}}, + {Command: "po ", Suggestions: nil}, + {Command: "po x", Suggestions: nil}, + {Command: "po k", Suggestions: []string{"ube-public", "ube-system"}}, + {Command: "po kube-", Suggestions: []string{"public", "system"}}, + } + + for _, tt := range tests { + got := SuggestSubCommand(tt.Command, namespaceNames, contextNames) + assert.Equal(t, tt.Suggestions, got) + } +} diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go new file mode 100644 index 0000000000..fce8980ebd --- /dev/null +++ b/internal/view/cmd/interpreter.go @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import ( + "strings" +) + +type Interpreter struct { + line string + cmd string + args args +} + +func NewInterpreter(s string) *Interpreter { + c := Interpreter{ + line: strings.ToLower(s), + args: make(args), + } + c.grok() + + return &c +} + +func (c *Interpreter) grok() { + ff := strings.Fields(c.line) + if len(ff) == 0 { + return + } + c.cmd = ff[0] + c.args = newArgs(c, ff[1:]) +} + +func (c *Interpreter) HasNS() bool { + ns, ok := c.args[nsKey] + + return ok && ns != "" +} + +func (c *Interpreter) Cmd() string { + return c.cmd +} + +func (c *Interpreter) IsBlank() bool { + return c.line == "" +} + +func (c *Interpreter) Amend(c1 *Interpreter) { + c.cmd = c1.cmd + if c.args == nil { + c.args = make(args, len(c1.args)) + } + for k, v := range c1.args { + if v != "" { + c.args[k] = v + } + } +} + +func (c *Interpreter) Reset(s string) *Interpreter { + c.line = strings.ToLower(s) + c.grok() + + return c +} + +func (c *Interpreter) GetLine() string { + return strings.TrimSpace(c.line) +} + +func (c *Interpreter) IsCowCmd() bool { + return c.cmd == cowCmd +} + +func (c *Interpreter) IsHelpCmd() bool { + _, ok := helpCmd[c.cmd] + return ok +} + +func (c *Interpreter) IsBailCmd() bool { + _, ok := bailCmd[c.cmd] + return ok +} + +func (c *Interpreter) IsAliasCmd() bool { + _, ok := aliasCmd[c.cmd] + return ok +} + +func (c *Interpreter) IsXrayCmd() bool { + _, ok := xrayCmd[c.cmd] + + return ok +} + +func (c *Interpreter) IsContextCmd() bool { + _, ok := contextCmd[c.cmd] + + return ok +} + +func (c *Interpreter) IsDirCmd() bool { + _, ok := dirCmd[c.cmd] + return ok +} + +func (c *Interpreter) IsRBACCmd() bool { + return c.cmd == canCmd +} + +func (c *Interpreter) ContextArg() (string, bool) { + if !c.IsContextCmd() { + return "", false + } + + return c.args[contextKey], true +} + +func (c *Interpreter) ResetContextArg() { + delete(c.args, contextFlag) +} + +func (c *Interpreter) DirArg() (string, bool) { + if !c.IsDirCmd() || c.args[topicKey] == "" { + return "", false + } + + return c.args[topicKey], true +} + +func (c *Interpreter) CowArg() (string, bool) { + if !c.IsCowCmd() || c.args[nsKey] == "" { + return "", false + } + + return c.args[nsKey], true +} + +func (c *Interpreter) RBACArgs() (string, string, bool) { + if !c.IsRBACCmd() { + return "", "", false + } + tt := rbacRX.FindStringSubmatch(c.line) + if len(tt) < 3 { + return "", "", false + } + + return tt[1], tt[2], true +} + +func (c *Interpreter) XrayArgs() (string, string, bool) { + if !c.IsXrayCmd() { + return "", "", false + } + gvr, ok1 := c.args[topicKey] + if !ok1 { + return "", "", false + } + + ns, ok2 := c.args[nsKey] + switch { + case ok1 && ok2: + return gvr, ns, true + case ok1 && !ok2: + return gvr, "", true + default: + return "", "", false + } +} + +func (c *Interpreter) FilterArg() (string, bool) { + f, ok := c.args[filterKey] + + return f, ok +} + +func (c *Interpreter) NSArg() (string, bool) { + ns, ok := c.args[nsKey] + + return ns, ok +} + +func (c *Interpreter) HasContext() (string, bool) { + ctx, ok := c.args[contextKey] + if !ok || ctx == "" { + return "", false + } + + return ctx, ok +} + +func (c *Interpreter) LabelsArg() (map[string]string, bool) { + ll, ok := c.args[labelKey] + if !ok { + return nil, false + } + + return toLabels(ll), true +} diff --git a/internal/view/cmd/interpreter_test.go b/internal/view/cmd/interpreter_test.go new file mode 100644 index 0000000000..bb78da78c0 --- /dev/null +++ b/internal/view/cmd/interpreter_test.go @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/view/cmd" + "github.com/stretchr/testify/assert" +) + +func TestRbacCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + args []string + }{ + "empty": {}, + "user": { + cmd: "can u:fernand", + ok: true, + args: []string{"u", "fernand"}, + }, + "user_spacing": { + cmd: "can u: fernand ", + ok: true, + args: []string{"u", "fernand"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsRBACCmd()) + + c, s, ok := p.RBACArgs() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.args[0], c) + assert.Equal(t, u.args[1], s) + } + }) + } +} + +func TestNsCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + ns string + }{ + "empty": {}, + "happy": { + cmd: "pod fred", + ok: true, + ns: "fred", + }, + "ns-arg-spaced": { + cmd: "pod fred ", + ok: true, + ns: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + ns, ok := p.NSArg() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.ns, ns) + } + }) + } +} + +func TestFilterCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + filter string + }{ + "empty": {}, + "normal": { + cmd: "pod /fred", + ok: true, + filter: "fred", + }, + "caps": { + cmd: "POD /FRED", + ok: true, + filter: "fred", + }, + "filter+ns": { + cmd: "pod /fred ns1", + ok: true, + filter: "fred", + }, + "ns+filter": { + cmd: "pod ns1 /fred", + ok: true, + filter: "fred", + }, + "ns+filter+labels": { + cmd: "pod ns1 /fred app=blee,fred=zorg", + ok: true, + filter: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + f, ok := p.FilterArg() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.filter, f) + } + }) + } +} + +func TestLabelCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + labels map[string]string + }{ + "empty": {}, + "plain": { + cmd: "pod fred=blee", + ok: true, + labels: map[string]string{"fred": "blee"}, + }, + "multi": { + cmd: "pod fred=blee,zorg=duh", + ok: true, + labels: map[string]string{"fred": "blee", "zorg": "duh"}, + }, + "multi-ns": { + cmd: "pod fred=blee,zorg=duh ns1", + ok: true, + labels: map[string]string{"fred": "blee", "zorg": "duh"}, + }, + "l-arg-spaced": { + cmd: "pod fred=blee ", + ok: true, + labels: map[string]string{"fred": "blee"}, + }, + "l-arg-caps": { + cmd: "POD FRED=BLEE ", + ok: true, + labels: map[string]string{"fred": "blee"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + ll, ok := p.LabelsArg() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.labels, ll) + } + }) + } +} + +func TestXRayCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + res, ns string + }{ + "empty": {}, + + "happy": { + cmd: "xray po", + ok: true, + res: "po", + }, + + "happy+ns": { + cmd: "xray po ns1", + ok: true, + res: "po", + ns: "ns1", + }, + + "toast": { + cmd: "xrayzor po", + }, + + "toast-1": { + cmd: "xray", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + res, ns, ok := p.XrayArgs() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.res, res) + assert.Equal(t, u.ns, ns) + } + }) + } +} + +func TestDirCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + dir string + }{ + "empty": {}, + + "happy": { + cmd: "dir dir1", + ok: true, + dir: "dir1", + }, + + "extra-ns": { + cmd: "dir dir1 ns1", + ok: true, + dir: "dir1", + }, + + "toast": { + cmd: "dirdel dir1", + }, + + "toast-nodir": { + cmd: "dir", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + dir, ok := p.DirArg() + assert.Equal(t, u.ok, ok) + assert.Equal(t, u.dir, dir) + }) + } +} + +func TestRBACCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + cat, sub string + }{ + "empty": {}, + "toast": { + cmd: "canopy u:bozo", + }, + "toast-1": { + cmd: "can u:", + }, + "toast-2": { + cmd: "can bozo", + }, + "user": { + cmd: "can u:bozo", + ok: true, + cat: "u", + sub: "bozo", + }, + "group": { + cmd: "can g:bozo", + ok: true, + cat: "g", + sub: "bozo", + }, + "sa": { + cmd: "can s:bozo", + ok: true, + cat: "s", + sub: "bozo", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + cat, sub, ok := p.RBACArgs() + assert.Equal(t, u.ok, ok) + if u.ok { + assert.Equal(t, u.cat, cat) + assert.Equal(t, u.sub, sub) + } + }) + } +} + +func TestContextCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + ctx string + }{ + "empty": {}, + "plain": { + cmd: "context", + ok: true, + ctx: "", + }, + "happy-full": { + cmd: "context ctx1", + ok: true, + ctx: "ctx1", + }, + "happy-alias": { + cmd: "ctx ctx1", + ok: true, + ctx: "ctx1", + }, + "toast": { + cmd: "ctxto ctx1", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsContextCmd()) + if u.ok { + ctx, ok := p.ContextArg() + assert.Equal(t, u.ok, ok) + assert.Equal(t, u.ctx, ctx) + } + }) + } +} + +func TestHelpCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + }{ + "empty": {}, + "plain": { + cmd: "help", + ok: true, + }, + "toast": { + cmd: "helpme", + }, + "toast1": { + cmd: "hozer", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsHelpCmd()) + }) + } +} + +func TestBailCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + }{ + "empty": {}, + "plain": { + cmd: "quit", + ok: true, + }, + "q": { + cmd: "q", + ok: true, + }, + "q!": { + cmd: "q!", + ok: true, + }, + "toast": { + cmd: "zorg", + }, + "toast1": { + cmd: "quitter", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsBailCmd()) + }) + } +} + +func TestAliasCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + }{ + "empty": {}, + "plain": { + cmd: "alias", + ok: true, + }, + "a": { + cmd: "a", + ok: true, + }, + "toast": { + cmd: "abba", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsAliasCmd()) + }) + } +} + +func TestCowCmd(t *testing.T) { + uu := map[string]struct { + cmd string + ok bool + }{ + "empty": {}, + "plain": { + cmd: "cow", + ok: true, + }, + "msg": { + cmd: "cow bumblebeetuna", + ok: true, + }, + "toast": { + cmd: "cowdy", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + p := cmd.NewInterpreter(u.cmd) + assert.Equal(t, u.ok, p.IsCowCmd()) + }) + } +} diff --git a/internal/view/cmd/types.go b/internal/view/cmd/types.go new file mode 100644 index 0000000000..122a3ab42c --- /dev/null +++ b/internal/view/cmd/types.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package cmd + +import "regexp" + +const ( + cowCmd = "cow" + canCmd = "can" + nsFlag = "-n" + filterFlag = "/" + labelFlag = "=" + fuzzyFlag = "-f" + contextFlag = "@" +) + +var ( + rbacRX = regexp.MustCompile(`^can\s+([u|g|s]):\s*([\w-:]+)\s*$`) + + contextCmd = map[string]struct{}{ + "ctx": {}, + "context": {}, + "contexts": {}, + } + dirCmd = map[string]struct{}{ + "dir": {}, + "d": {}, + "ls": {}, + } + bailCmd = map[string]struct{}{ + "q": {}, + "q!": {}, + "qa": {}, + "Q": {}, + "quit": {}, + "exit": {}, + } + helpCmd = map[string]struct{}{ + "?": {}, + "h": {}, + "help": {}, + } + aliasCmd = map[string]struct{}{ + "a": {}, + "alias": {}, + } + xrayCmd = map[string]struct{}{ + "x": {}, + "xr": {}, + "xray": {}, + } +) diff --git a/internal/view/command.go b/internal/view/command.go index 2baae3ac80..0fdb7f7a8f 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -14,19 +14,18 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/view/cmd" "github.com/rs/zerolog/log" ) var ( customViewers MetaViewers - - canRX = regexp.MustCompile(`\Acan\s([u|g|s]):([\w-:]+)\b`) + contextRX = regexp.MustCompile(`\s+@([\w-]+)`) ) // Command represents a user command. type Command struct { - app *App - + app *App alias *dao.Alias mx sync.Mutex } @@ -39,9 +38,9 @@ func NewCommand(app *App) *Command { } // Init initializes the command. -func (c *Command) Init() error { +func (c *Command) Init(path string) error { c.alias = dao.NewAlias(c.app.factory) - if _, err := c.alias.Ensure(); err != nil { + if _, err := c.alias.Ensure(path); err != nil { log.Error().Err(err).Msgf("command init failed!") return err } @@ -51,14 +50,14 @@ func (c *Command) Init() error { } // Reset resets Command and reload aliases. -func (c *Command) Reset(clear bool) error { +func (c *Command) Reset(path string, clear bool) error { c.mx.Lock() defer c.mx.Unlock() if clear { c.alias.Clear() } - if _, err := c.alias.Ensure(); err != nil { + if _, err := c.alias.Ensure(path); err != nil { return err } @@ -74,172 +73,203 @@ func allowedXRay(gvr client.GVR) bool { "apps/v1/statefulsets": {}, "apps/v1/replicasets": {}, } - _, ok := gg[gvr.String()] + return ok } -func (c *Command) xrayCmd(cmd string) error { - tokens := strings.Split(cmd, " ") - if len(tokens) < 2 { - return errors.New("you must specify a resource") +func (c *Command) contextCmd(p *cmd.Interpreter) error { + ctx, ok := p.ContextArg() + if !ok { + return fmt.Errorf("invalid command use `context xxx`") + } + + if ctx != "" { + return useContext(c.app, ctx) + } + + gvr, v, err := c.viewMetaFor(p) + if err != nil { + return err + } + + return c.exec(p, gvr, c.componentFor(gvr, ctx, v), true) +} + +func (c *Command) xrayCmd(p *cmd.Interpreter) error { + arg, cns, ok := p.XrayArgs() + if !ok { + return errors.New("invalid command. use `xray xxx`") } - gvr, ok := c.alias.AsGVR(tokens[1]) + gvr, _, ok := c.alias.AsGVR(arg) if !ok { - return fmt.Errorf("`%s` command not found", cmd) + return fmt.Errorf("invalid resource name: %q", arg) } if !allowedXRay(gvr) { - return fmt.Errorf("`%s` command not found", cmd) + return fmt.Errorf("unsupported resource %q", arg) } - - x := NewXray(gvr) ns := c.app.Config.ActiveNamespace() - if len(tokens) == 3 { - ns = tokens[2] + if cns != "" { + ns = cns } if err := c.app.Config.SetActiveNamespace(client.CleanseNamespace(ns)); err != nil { return err } + if err := c.app.switchNS(ns); err != nil { + return err + } + if err := c.app.Config.Save(); err != nil { return err } - return c.exec(cmd, "xrays", x, true) + return c.exec(p, client.NewGVR("xrays"), NewXray(gvr), true) } // Run execs the command by showing associated display. -func (c *Command) run(cmd, path string, clearStack bool) error { - if c.specialCmd(cmd, path) { +func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { + if c.specialCmd(p) { return nil } - cmds := strings.Split(cmd, " ") - command := strings.ToLower(cmds[0]) - gvr, v, err := c.viewMetaFor(command) + // if _, ok := c.alias.Check(p.Cmd()); !ok { + // return fmt.Errorf("command not found %q", p.Cmd()) + // } + + gvr, v, err := c.viewMetaFor(p) if err != nil { return err } - var cns string - tt := strings.Split(gvr, " ") - if len(tt) == 2 { - gvr, cns = tt[0], tt[1] + + ns := c.app.Config.ActiveNamespace() + if cns, ok := p.NSArg(); ok { + ns = cns + } + if err := c.app.switchNS(ns); err != nil { + return err } - switch command { - case "ctx", "context", "contexts": - if len(cmds) == 2 { - return useContext(c.app, cmds[1]) - } - return c.exec(cmd, gvr, c.componentFor(gvr, path, v), clearStack) - case "dir": - if len(cmds) != 2 { - return errors.New("you must specify a directory") - } - return c.app.dirCmd(cmds[1]) - default: - // checks if Command includes a namespace - ns := c.app.Config.ActiveNamespace() - if len(cmds) == 2 { - ns = cmds[1] + if context, ok := p.HasContext(); ok { + res, err := dao.AccessorFor(c.app.factory, client.NewGVR("contexts")) + if err != nil { + return err } - if cns != "" { - ns = cns + switcher, ok := res.(dao.Switchable) + if !ok { + return errors.New("expecting a switchable resource") } - if err := c.app.switchNS(ns); err != nil { + if err := switcher.Switch(context); err != nil { + log.Error().Err(err).Msgf("Context switch failed") return err } - if !c.alias.Check(command) { - return fmt.Errorf("`%s` Command not found", cmd) + + if err := c.app.switchContext(p); err != nil { + return err } - return c.exec(cmd, gvr, c.componentFor(gvr, path, v), clearStack) } + + co := c.componentFor(gvr, fqn, v) + co.SetFilter("") + co.SetLabelFilter(nil) + if f, ok := p.FilterArg(); ok { + co.SetFilter(f) + } + if ll, ok := p.LabelsArg(); ok { + co.SetLabelFilter(ll) + } + + return c.exec(p, gvr, co, clearStack) } func (c *Command) defaultCmd() error { if c.app.Conn() == nil || !c.app.Conn().ConnectionOK() { - return c.run("context", "", true) + return c.run(cmd.NewInterpreter("context"), "", true) } - view := c.app.Config.ActiveView() - if view == "" { - return c.run("pod", "", true) - } - tokens := strings.Split(view, " ") - cmd := view - if len(tokens) == 1 { - if !isContextCmd(tokens[0]) { - cmd = tokens[0] + " " + c.app.Config.ActiveNamespace() - } + + p := cmd.NewInterpreter(c.app.Config.ActiveView()) + if p.IsBlank() { + return c.run(p.Reset("pod"), "", true) } - if err := c.run(cmd, "", true); err != nil { - log.Error().Err(err).Msgf("Default run command failed %q", cmd) - return c.run("pod", "", true) + if err := c.run(p, "", true); err != nil { + log.Error().Err(err).Msgf("Default run command failed %q", p.GetLine()) + return c.run(p.Reset("pod"), "", true) } - return nil -} -func isContextCmd(c string) bool { - return c == "ctx" || c == "context" + return nil } -func (c *Command) specialCmd(cmd, path string) bool { - cmds := strings.Split(cmd, " ") - switch cmds[0] { - case "cow": - c.app.cowCmd(path) - return true - case "q", "q!", "qa", "Q", "quit": +func (c *Command) specialCmd(p *cmd.Interpreter) bool { + switch { + case p.IsCowCmd(): + if msg, ok := p.CowArg(); !ok { + c.app.Flash().Errf("Invalid command. Use `cow xxx`") + } else { + c.app.cowCmd(msg) + } + case p.IsBailCmd(): c.app.BailOut() - return true - case "?", "h", "help": - c.app.helpCmd(nil) - return true - case "a", "alias": - c.app.aliasCmd(nil) - return true - case "x", "xray": - if err := c.xrayCmd(cmd); err != nil { + case p.IsHelpCmd(): + _ = c.app.helpCmd(nil) + case p.IsAliasCmd(): + _ = c.app.aliasCmd(nil) + case p.IsXrayCmd(): + if err := c.xrayCmd(p); err != nil { c.app.Flash().Err(err) } - return true - default: - if !canRX.MatchString(cmd) { - return false + case p.IsRBACCmd(): + if cat, sub, ok := p.RBACArgs(); !ok { + c.app.Flash().Errf("Invalid command. Use `can [u|g|s]:xxx`") + } else if err := c.app.inject(NewPolicy(c.app, cat, sub), true); err != nil { + c.app.Flash().Err(err) } - tokens := canRX.FindAllStringSubmatch(cmd, -1) - if len(tokens) == 1 && len(tokens[0]) == 3 { - if err := c.app.inject(NewPolicy(c.app, tokens[0][1], tokens[0][2]), false); err != nil { - log.Error().Err(err).Msgf("policy view load failed") - return false - } - return true + case p.IsContextCmd(): + if err := c.contextCmd(p); err != nil { + c.app.Flash().Err(err) } + case p.IsDirCmd(): + if a, ok := p.DirArg(); !ok { + c.app.Flash().Errf("Invalid command. Use `dir xxx`") + } else if err := c.app.dirCmd(a); err != nil { + c.app.Flash().Err(err) + } + default: + return false } - return false + + return true } -func (c *Command) viewMetaFor(cmd string) (string, *MetaViewer, error) { - gvr, ok := c.alias.AsGVR(cmd) +func (c *Command) viewMetaFor(p *cmd.Interpreter) (client.GVR, *MetaViewer, error) { + agvr, exp, ok := c.alias.AsGVR(p.Cmd()) if !ok { - return "", nil, fmt.Errorf("`%s` command not found", cmd) + return client.NoGVR, nil, fmt.Errorf("`%s` command not found", p.Cmd()) + } + gvr := agvr + if exp != "" { + ff := strings.Fields(exp) + ff[0] = agvr.String() + ap := cmd.NewInterpreter(strings.Join(ff, " ")) + gvr = client.NewGVR(ap.Cmd()) + p.Amend(ap) } - v, ok := customViewers[gvr] - if !ok { - return gvr.String(), &MetaViewer{viewerFn: NewBrowser}, nil + v := MetaViewer{viewerFn: NewBrowser} + if mv, ok := customViewers[gvr]; ok { + v = mv } - return gvr.String(), &v, nil + return gvr, &v, nil } -func (c *Command) componentFor(gvr, path string, v *MetaViewer) ResourceViewer { +func (c *Command) componentFor(gvr client.GVR, fqn string, v *MetaViewer) ResourceViewer { var view ResourceViewer if v.viewerFn != nil { - view = v.viewerFn(client.NewGVR(gvr)) + view = v.viewerFn(gvr) } else { - view = NewBrowser(client.NewGVR(gvr)) + view = NewBrowser(gvr) } - view.SetInstance(path) + view.SetInstance(fqn) if v.enterFn != nil { view.GetTable().SetEnterFn(v.enterFn) } @@ -247,7 +277,7 @@ func (c *Command) componentFor(gvr, path string, v *MetaViewer) ResourceViewer { return view } -func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) (err error) { +func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component, clearStack bool) (err error) { defer func() { if e := recover(); e != nil { log.Error().Msgf("Something bad happened! %#v", e) @@ -255,33 +285,30 @@ func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) ( log.Debug().Msgf("History %v", c.app.cmdHistory.List()) log.Error().Msg(string(debug.Stack())) - hh := c.app.cmdHistory.List() - if len(hh) == 0 { - _ = c.run("pod", "", true) - } else { - _ = c.run(hh[0], "", true) + p := cmd.NewInterpreter("pod") + if cmd := c.app.cmdHistory.Pop(); cmd != "" { + p = p.Reset(cmd) } - err = fmt.Errorf("invalid command %q", cmd) + err = c.run(p, "", true) } }() if comp == nil { return fmt.Errorf("no component found for %s", gvr) } - c.app.Flash().Infof("Viewing %s...", client.NewGVR(gvr).R()) - command := cmd - if tokens := strings.Split(cmd, " "); len(tokens) >= 2 { - command = tokens[0] - } - c.app.Config.SetActiveView(command) - if err := c.app.Config.Save(); err != nil { - log.Error().Err(err).Msg("Config save failed!") + c.app.Flash().Infof("Viewing %s...", gvr.R()) + if clearStack { + cmd := contextRX.ReplaceAllString(p.GetLine(), "") + c.app.Config.SetActiveView(cmd) + if err := c.app.Config.Save(); err != nil { + log.Error().Err(err).Msg("Config save failed!") + } } if err := c.app.inject(comp, clearStack); err != nil { return err } - c.app.cmdHistory.Push(cmd) + c.app.cmdHistory.Push(p.GetLine()) return } diff --git a/internal/view/container.go b/internal/view/container.go index 6094fd2ed0..7a2238a3fa 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -110,7 +110,7 @@ func (c *Container) logOptions(prev bool) (*dao.LogOptions, error) { return &opts, nil } -func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) { +func (c *Container) viewLogs(app *App, model ui.Tabular, gvr client.GVR, path string) { c.ResourceViewer.(*LogsExtender).showLogs(c.GetTable().Path, false) } @@ -136,7 +136,10 @@ func (c *Container) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { } func (c *Container) portForwardContext(ctx context.Context) context.Context { - ctx = context.WithValue(ctx, internal.KeyBenchCfg, c.App().BenchFile) + if bc := c.App().BenchFile; bc != "" { + ctx = context.WithValue(ctx, internal.KeyBenchCfg, c.App().BenchFile) + } + return context.WithValue(ctx, internal.KeyPath, c.GetTable().Path) } diff --git a/internal/view/context.go b/internal/view/context.go index 97a5c91b5d..66d4c45e36 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" @@ -101,7 +102,7 @@ func (c *Context) makeStyledForm() *tview.Form { return f } -func (c *Context) useCtx(app *App, model ui.Tabular, gvr, path string) { +func (c *Context) useCtx(app *App, model ui.Tabular, gvr client.GVR, path string) { log.Debug().Msgf("SWITCH CTX %q--%q", gvr, path) if err := useContext(app, path); err != nil { app.Flash().Err(err) @@ -128,5 +129,5 @@ func useContext(app *App, name string) error { return err } - return app.switchContext(name) + return app.switchContext(cmd.NewInterpreter("ctx " + name)) } diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index 940904111a..90943a812d 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -42,9 +42,9 @@ func NewCronJob(gvr client.GVR) ResourceViewer { return &c } -func (c *CronJob) showJobs(app *App, model ui.Tabular, gvr, path string) { +func (c *CronJob) showJobs(app *App, model ui.Tabular, gvr client.GVR, path string) { log.Debug().Msgf("Showing Jobs %q:%q -- %q", model.GetNamespace(), gvr, path) - o, err := app.factory.Get(gvr, path, true, labels.Everything()) + o, err := app.factory.Get(gvr.String(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return diff --git a/internal/view/details.go b/internal/view/details.go index 86ca3a700c..04cbf6577b 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -58,6 +58,9 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) * return &d } +func (d *Details) SetFilter(string) {} +func (d *Details) SetLabelFilter(map[string]string) {} + // Init initializes the viewer. func (d *Details) Init(_ context.Context) error { if d.title != "" { @@ -294,7 +297,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(d.app.Config.K9s.GetScreenDumpDir(), d.app.Config.K9s.CurrentContextDir(), d.title, d.text.GetText(true)); err != nil { + if path, err := saveYAML(d.app.Config.K9s.GetScreenDumpDir(), d.app.Config.K9s.ActiveContextDir(), d.title, d.text.GetText(true)); err != nil { d.app.Flash().Err(err) } else { d.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/dir.go b/internal/view/dir.go index 5d30276cd1..a55220a0eb 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -12,7 +12,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" @@ -163,7 +163,7 @@ func isKustomized(sel string) bool { } kk := []string{kustomizeNoExt, kustomizeYAML, kustomizeYML} for _, f := range ff { - if config.InList(kk, f.Name()) { + if data.InList(kk, f.Name()) { return true } } diff --git a/internal/view/dp.go b/internal/view/dp.go index 9af1bd93bb..11decf20cb 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -91,7 +91,7 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) { return &opts, nil } -func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, fqn string) { +func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string) { var ddp dao.Deployment ddp.Init(d.App().factory, d.GVR()) diff --git a/internal/view/ds.go b/internal/view/ds.go index ab92227364..4bb9dd6c1b 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -43,7 +43,7 @@ func (d *DaemonSet) bindKeys(aa ui.KeyActions) { }) } -func (d *DaemonSet) showPods(app *App, model ui.Tabular, _, path string) { +func (d *DaemonSet) showPods(app *App, model ui.Tabular, _ client.GVR, path string) { var res dao.DaemonSet res.Init(app.factory, d.GVR()) diff --git a/internal/view/exec.go b/internal/view/exec.go index 7bdcef004d..f4f45bf4e7 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -64,7 +64,7 @@ func runK(a *App, opts shellOpts) error { if isInsecure := a.Conn().Config().Flags().Insecure; isInsecure != nil && *isInsecure { args = append(args, "--insecure-skip-tls-verify") } - args = append(args, "--context", a.Config.K9s.CurrentContext) + args = append(args, "--context", a.Config.K9s.ActiveContextName()) if cfg := a.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { args = append(args, "--kubeconfig", *cfg) } @@ -198,7 +198,7 @@ func runKu(a *App, opts shellOpts) (string, error) { if g, err := a.Conn().Config().ImpersonateGroups(); err == nil { args = append(args, "--as-group", g) } - args = append(args, "--context", a.Config.K9s.CurrentContext) + args = append(args, "--context", a.Config.K9s.ActiveContextName()) if cfg := a.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { args = append(args, "--kubeconfig", *cfg) } @@ -284,8 +284,11 @@ func sshIn(a *App, fqn, co string) error { } func nukeK9sShell(a *App) error { - clName := a.Config.K9s.CurrentCluster - if !a.Config.K9s.Clusters[clName].FeatureGates.NodeShell { + ct, err := a.Config.K9s.ActiveContext() + if err != nil { + return err + } + if !ct.FeatureGates.NodeShell { return nil } diff --git a/internal/view/helm_chart.go b/internal/view/helm_chart.go index d4624e7a3d..afa58e5056 100644 --- a/internal/view/helm_chart.go +++ b/internal/view/helm_chart.go @@ -45,7 +45,7 @@ func (c *HelmChart) bindKeys(aa ui.KeyActions) { }) } -func (c *HelmChart) viewReleases(app *App, model ui.Tabular, _, path string) { +func (c *HelmChart) viewReleases(app *App, model ui.Tabular, _ client.GVR, path string) { v := NewHistory(client.NewGVR("helm-history")) v.SetContextFn(c.helmContext) if err := app.inject(v, false); err != nil { @@ -58,7 +58,7 @@ func (c *HelmChart) historyCmd(evt *tcell.EventKey) *tcell.EventKey { if path == "" { return evt } - c.viewReleases(c.App(), c.GetTable().GetModel(), c.GVR().String(), path) + c.viewReleases(c.App(), c.GetTable().GetModel(), c.GVR(), path) return nil } diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go index 185acaeb4d..ce746ff682 100644 --- a/internal/view/helm_history.go +++ b/internal/view/helm_history.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render/helm" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" @@ -19,6 +20,8 @@ import ( // History represents a helm History view. type History struct { ResourceViewer + + Values *model.RevValues } // NewHistory returns a new helm-history view. @@ -31,6 +34,7 @@ func NewHistory(gvr client.GVR) ResourceViewer { h.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) h.AddBindKeysFn(h.bindKeys) h.SetContextFn(h.HistoryContext) + h.GetTable().SetEnterFn(h.getValsCmd) return &h } @@ -62,6 +66,21 @@ func (h *History) bindKeys(aa ui.KeyActions) { }) } +func (h *History) getValsCmd(app *App, _ ui.Tabular, _ client.GVR, path string) { + ns, n := client.Namespaced(path) + tt := strings.Split(n, ":") + if len(tt) < 2 { + app.Flash().Err(fmt.Errorf("unable to parse version in %q", path)) + return + } + name, rev := tt[0], tt[1] + h.Values = model.NewRevValues(h.GVR(), client.FQN(ns, name), rev) + v := NewLiveView(h.App(), "Values", h.Values) + if err := v.app.inject(v, false); err != nil { + v.app.Flash().Err(err) + } +} + func (h *History) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ ui.KeyR: ui.NewKeyAction("RollBackTo...", h.rollbackCmd, true), diff --git a/internal/view/help.go b/internal/view/help.go index d5ff15cb4d..fd9da2ab13 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -43,6 +43,9 @@ func NewHelp(app *App) *Help { } } +func (h *Help) SetFilter(string) {} +func (h *Help) SetLabelFilter(map[string]string) {} + // Init initializes the component. func (h *Help) Init(ctx context.Context) error { if err := h.Table.Init(ctx); err != nil { diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 6805e40519..5f7f4365d8 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -99,27 +99,28 @@ func defaultEnv(c *client.Config, path string, header render.Header, row render. return env } -func describeResource(app *App, m ui.Tabular, gvr, path string) { - v := NewLiveView(app, "Describe", model.NewDescribe(client.NewGVR(gvr), path)) +func describeResource(app *App, m ui.Tabular, gvr client.GVR, path string) { + v := NewLiveView(app, "Describe", model.NewDescribe(gvr, path)) if err := app.inject(v, false); err != nil { app.Flash().Err(err) } } -func showPodsWithLabels(app *App, path string, sel map[string]string) { - labels := make([]string, 0, len(sel)) - for k, v := range sel { - labels = append(labels, fmt.Sprintf("%s=%s", k, v)) +func toLabelsStr(labels map[string]string) string { + ll := make([]string, 0, len(labels)) + for k, v := range labels { + ll = append(ll, fmt.Sprintf("%s=%s", k, v)) } - showPods(app, path, strings.Join(labels, ","), "") + + return strings.Join(ll, ",") } func showPods(app *App, path, labelSel, fieldSel string) { - if err := app.switchNS(client.AllNamespaces); err != nil { - app.Flash().Err(err) - return - } - + // !!BOZO!! needed?? + // if err := app.switchNS(client.BlankNamespace); err != nil { + // app.Flash().Err(err) + // return + // } v := NewPod(client.NewGVR("v1/pods")) v.SetContextFn(podCtx(app, path, labelSel, fieldSel)) @@ -137,14 +138,6 @@ func podCtx(app *App, path, labelSel, fieldSel string) ContextFunc { ctx = context.WithValue(ctx, internal.KeyPath, path) ctx = context.WithValue(ctx, internal.KeyLabels, labelSel) - ns, _ := client.Namespaced(path) - mx := client.NewMetricsServer(app.factory.Client()) - nmx, err := mx.FetchPodsMetrics(ctx, ns) - if err != nil { - log.Debug().Err(err).Msgf("No pods metrics") - } - ctx = context.WithValue(ctx, internal.KeyMetrics, nmx) - return context.WithValue(ctx, internal.KeyFields, fieldSel) } } @@ -254,8 +247,12 @@ func linesWithRegions(lines []string, matches fuzzy.Matches) []string { for i, m := range matches { for _, loc := range dao.ContinuousRanges(m.MatchedIndexes) { start, end := loc[0]+offsetForLine[m.Index], loc[1]+offsetForLine[m.Index] - regionStr := matchTag(i, ll[m.Index][start:end]) - ll[m.Index] = ll[m.Index][:start] + regionStr + ll[m.Index][end:] + line := ll[m.Index] + if end > len(line) { + end = len(line) + } + regionStr := matchTag(i, line[start:end]) + ll[m.Index] = line[:start] + regionStr + line[end:] offsetForLine[m.Index] += len(regionStr) - (end - start) } } diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index d1d144f054..0278afaa02 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/rs/zerolog" @@ -59,7 +60,7 @@ func TestParsePFAnn(t *testing.T) { } func TestExtractApp(t *testing.T) { - app := NewApp(config.NewConfig(nil)) + app := NewApp(mock.NewMockConfig()) uu := map[string]struct { app *App diff --git a/internal/view/img_scan.go b/internal/view/img_scan.go index 811e1af0e8..edc6501129 100644 --- a/internal/view/img_scan.go +++ b/internal/view/img_scan.go @@ -51,7 +51,7 @@ func (c *ImageScan) bindKeys(aa ui.KeyActions) { }) } -func (s *ImageScan) viewCVE(app *App, model ui.Tabular, gvr, path string) { +func (s *ImageScan) viewCVE(app *App, _ ui.Tabular, _ client.GVR, path string) { bin := browseLinux if runtime.GOOS == "darwin" { bin = browseOSX diff --git a/internal/view/job.go b/internal/view/job.go index 24df7d38d1..09ff8d6854 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -26,8 +26,8 @@ func NewJob(gvr client.GVR) ResourceViewer { return &j } -func (*Job) showPods(app *App, model ui.Tabular, gvr, path string) { - o, err := app.factory.Get(gvr, path, true, labels.Everything()) +func (*Job) showPods(app *App, model ui.Tabular, gvr client.GVR, path string) { + o, err := app.factory.Get(gvr.String(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return diff --git a/internal/view/live_view.go b/internal/view/live_view.go index 245a1ec419..be5ec6d393 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -60,6 +60,9 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView { return &v } +func (v *LiveView) SetFilter(string) {} +func (v *LiveView) SetLabelFilter(map[string]string) {} + // Init initializes the viewer. func (v *LiveView) Init(_ context.Context) error { if v.title != "" { @@ -354,7 +357,7 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { name := fmt.Sprintf("%s--%s", strings.Replace(v.model.GetPath(), "/", "-", 1), strings.ToLower(v.title)) - if _, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentContextDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { + if _, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.ActiveContextDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { v.app.Flash().Err(err) } else { v.app.Flash().Infof("File %q saved successfully!", name) diff --git a/internal/view/live_view_test.go b/internal/view/live_view_test.go index 257e8114bb..923ed3a84d 100644 --- a/internal/view/live_view_test.go +++ b/internal/view/live_view_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/stretchr/testify/assert" ) @@ -18,7 +19,7 @@ apiVersion: v1 the secret name you want to quote to use tls.","title":"secretName","type":"string"}},"required":["http","class","classInSpec"],"type":"object"} ` - v := NewLiveView(NewApp(config.NewConfig(nil)), "fred", nil) + v := NewLiveView(NewApp(mock.NewMockConfig()), "fred", nil) assert.NoError(t, v.Init(context.Background())) v.text.SetText(colorizeYAML(config.Yaml{}, s)) diff --git a/internal/view/log.go b/internal/view/log.go index 2b6732465f..5627a2c2af 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -61,6 +61,9 @@ func NewLog(gvr client.GVR, opts *dao.LogOptions) *Log { return &l } +func (l *Log) SetFilter(string) {} +func (l *Log) SetLabelFilter(map[string]string) {} + // Init initializes the viewer. func (l *Log) Init(ctx context.Context) (err error) { if l.app, err = extractApp(ctx); err != nil { @@ -403,7 +406,7 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey { // SaveCmd dumps the logs to file. func (l *Log) SaveCmd(*tcell.EventKey) *tcell.EventKey { - path, err := saveData(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.CurrentContextDir(), l.model.GetPath(), l.logs.GetText(true)) + path, err := saveData(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.ActiveContextDir(), l.model.GetPath(), l.logs.GetText(true)) if err != nil { l.app.Flash().Err(err) return nil diff --git a/internal/view/log_indicator_test.go b/internal/view/log_indicator_test.go index 0c793f594d..2316354d97 100644 --- a/internal/view/log_indicator_test.go +++ b/internal/view/log_indicator_test.go @@ -18,10 +18,10 @@ func TestLogIndicatorRefresh(t *testing.T) { e string }{ "all-containers": { - view.NewLogIndicator(config.NewConfig(nil), defaults, true), "[::b]AllContainers:[gray::d]Off[-::] [::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[gray::d]Off[-::] [::b]Timestamps:[gray::d]Off[-::] [::b]Wrap:[gray::d]Off[-::]\n", + view.NewLogIndicator(config.NewConfig(nil), defaults, true), "[::b]AllContainers:[steelblue::d]Off[-::] [::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[steelblue::d]Off[-::] [::b]Timestamps:[steelblue::d]Off[-::] [::b]Wrap:[steelblue::d]Off[-::]\n", }, "plain": { - view.NewLogIndicator(config.NewConfig(nil), defaults, false), "[::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[gray::d]Off[-::] [::b]Timestamps:[gray::d]Off[-::] [::b]Wrap:[gray::d]Off[-::]\n", + view.NewLogIndicator(config.NewConfig(nil), defaults, false), "[::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[steelblue::d]Off[-::] [::b]Timestamps:[steelblue::d]Off[-::] [::b]Wrap:[steelblue::d]Off[-::]\n", }, } diff --git a/internal/view/log_test.go b/internal/view/log_test.go index f5ae47a35a..9bde19d19e 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view" @@ -109,10 +110,15 @@ func TestLogViewSave(t *testing.T) { ii.Lines(0, false, ll) v.Flush(ll) - dir := filepath.Join(app.Config.K9s.GetScreenDumpDir(), app.Config.K9s.CurrentCluster) - c1, _ := os.ReadDir(dir) + dd := "/tmp/test-dumps/na" + assert.NoError(t, ensureDumpDir(dd)) + app.Config.K9s.ScreenDumpDir = "/tmp/test-dumps" + dir := filepath.Join(app.Config.K9s.GetScreenDumpDir(), app.Config.K9s.ActiveContextDir()) + c1, err := os.ReadDir(dir) + assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir)) v.SaveCmd(nil) - c2, _ := os.ReadDir(dir) + c2, err := os.ReadDir(dir) + assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir)) assert.Equal(t, len(c2), len(c1)+1) } @@ -144,5 +150,16 @@ func TestAllContainerKeyBinding(t *testing.T) { // Helpers... func makeApp() *view.App { - return view.NewApp(config.NewConfig(ks{})) + return view.NewApp(mock.NewMockConfig()) +} + +func ensureDumpDir(n string) error { + config.AppDumpsDir = n + if _, err := os.Stat(n); os.IsNotExist(err) { + return os.MkdirAll(n, 0700) + } + if err := os.RemoveAll(n); err != nil { + return err + } + return os.MkdirAll(n, 0700) } diff --git a/internal/view/logger.go b/internal/view/logger.go index a3da224bd7..19c079bfe3 100644 --- a/internal/view/logger.go +++ b/internal/view/logger.go @@ -154,7 +154,7 @@ func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.CurrentContextDir(), l.title, l.GetText(true)); err != nil { + if path, err := saveYAML(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.ActiveContextDir(), l.title, l.GetText(true)); err != nil { l.app.Flash().Err(err) } else { l.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/node.go b/internal/view/node.go index c1f2274f9c..6e3b17cbe4 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -45,8 +45,12 @@ func (n *Node) bindDangerousKeys(aa ui.KeyActions) { ui.KeyU: ui.NewKeyAction("Uncordon", n.toggleCordonCmd(false), true), ui.KeyR: ui.NewKeyAction("Drain", n.drainCmd, true), }) - cl := n.App().Config.K9s.CurrentCluster - if n.App().Config.K9s.Clusters[cl].FeatureGates.NodeShell { + ct, err := n.App().Config.K9s.ActiveContext() + if err != nil { + log.Error().Err(err).Msgf("No active context located") + return + } + if ct.FeatureGates.NodeShell { aa.Add(ui.KeyActions{ ui.KeyS: ui.NewKeyAction("Shell", n.sshCmd, true), }) @@ -66,8 +70,8 @@ func (n *Node) bindKeys(aa ui.KeyActions) { }) } -func (n *Node) showPods(a *App, _ ui.Tabular, _, path string) { - showPods(a, n.GetTable().GetSelectedItem(), client.AllNamespaces, "spec.nodeName="+path) +func (n *Node) showPods(a *App, _ ui.Tabular, _ client.GVR, path string) { + showPods(a, n.GetTable().GetSelectedItem(), client.BlankNamespace, "spec.nodeName="+path) } func (n *Node) drainCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/ns.go b/internal/view/ns.go index 263018d5ad..ada763e93a 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -5,7 +5,7 @@ package view import ( "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" @@ -41,7 +41,7 @@ func (n *Namespace) bindKeys(aa ui.KeyActions) { }) } -func (n *Namespace) switchNs(app *App, model ui.Tabular, gvr, path string) { +func (n *Namespace) switchNs(app *App, _ ui.Tabular, _ client.GVR, path string) { n.useNamespace(path) app.gotoResource("pods", "", false) } @@ -73,14 +73,14 @@ func (n *Namespace) useNamespace(fqn string) { } } -func (n *Namespace) decorate(data *render.TableData) { - if n.App().Conn() == nil || len(data.RowEvents) == 0 { +func (n *Namespace) decorate(td *render.TableData) { + if n.App().Conn() == nil || len(td.RowEvents) == 0 { return } // checks if all ns is in the list if not add it. - if _, ok := data.RowEvents.FindIndex(client.NamespaceAll); !ok { - data.RowEvents = append(data.RowEvents, + if _, ok := td.RowEvents.FindIndex(client.NamespaceAll); !ok { + td.RowEvents = append(td.RowEvents, render.RowEvent{ Kind: render.EventUnchanged, Row: render.Row{ @@ -91,8 +91,8 @@ func (n *Namespace) decorate(data *render.TableData) { ) } - for _, re := range data.RowEvents { - if config.InList(n.App().Config.FavNamespaces(), re.Row.ID) { + for _, re := range td.RowEvents { + if data.InList(n.App().Config.FavNamespaces(), re.Row.ID) { re.Row.Fields[0] += favNSIndicator re.Kind = render.EventUnchanged } diff --git a/internal/view/ofaas.go b/internal/view/ofaas.go deleted file mode 100644 index d2bc27f286..0000000000 --- a/internal/view/ofaas.go +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package view - -// BOZO!! revamp with latest... -// import ( -// "strings" - -// "github.com/derailed/k9s/internal/client" -// "github.com/derailed/k9s/internal/render" -// "github.com/derailed/k9s/internal/ui" -// ) - -// // OpenFaas represents an OpenFaaS viewer. -// type OpenFaas struct { -// ResourceViewer -// } - -// // NewOpenFaas returns a new viewer. -// func NewOpenFaas(gvr client.GVR) ResourceViewer { -// o := OpenFaas{ResourceViewer: NewBrowser(gvr)} -// o.AddBindKeysFn(o.bindKeys) -// o.GetTable().SetEnterFn(o.showPods) - -// return &o -// } - -// func (o *OpenFaas) bindKeys(aa ui.KeyActions) { -// aa.Add(ui.KeyActions{ -// ui.KeyShiftS: ui.NewKeyAction("Sort Status", o.GetTable().SortColCmd(statusCol, true), false), -// ui.KeyShiftI: ui.NewKeyAction("Sort Invocations", o.GetTable().SortColCmd("INVOCATIONS", false), false), -// ui.KeyShiftR: ui.NewKeyAction("Sort Replicas", o.GetTable().SortColCmd("REPLICAS", false), false), -// ui.KeyShiftL: ui.NewKeyAction("Sort Available", o.GetTable().SortColCmd(availCol, false), false), -// }) -// } - -// func (o *OpenFaas) showPods(a *App, _ ui.Tabular, _, path string) { -// labels := o.GetTable().GetSelectedCell(o.GetTable().NameColIndex() + 3) -// sels := make(map[string]string) - -// tokens := strings.Split(labels, ",") -// for _, t := range tokens { -// s := strings.Split(t, "=") -// sels[s[0]] = s[1] -// } - -// showPodsWithLabels(a, path, sels) -// } diff --git a/internal/view/pf.go b/internal/view/pf.go index bef01c0683..40ded9d65a 100644 --- a/internal/view/pf.go +++ b/internal/view/pf.go @@ -44,13 +44,17 @@ func NewPortForward(gvr client.GVR) ResourceViewer { } func (p *PortForward) portForwardContext(ctx context.Context) context.Context { - return context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) + if bc := p.App().BenchFile; bc != "" { + return context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) + } + + return ctx } func (p *PortForward) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("View Benchmarks", p.showBenchCmd, true), - tcell.KeyCtrlL: ui.NewKeyAction("Benchmark Run/Stop", p.toggleBenchCmd, true), + ui.KeyB: ui.NewKeyAction("Benchmark Run/Stop", p.toggleBenchCmd, true), tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true), ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.GetTable().SortColCmd("PORTS", true), false), ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.GetTable().SortColCmd("URL", true), false), @@ -108,16 +112,25 @@ func (p *PortForward) toggleBenchCmd(evt *tcell.EventKey) *tcell.EventKey { } p.App().Status(model.FlashWarn, "Benchmark in progress...") - go p.runBenchmark() + go func() { + if err := p.runBenchmark(); err != nil { + log.Error().Err(err).Msgf("Benchmark run failed") + } + }() return nil } -func (p *PortForward) runBenchmark() { +func (p *PortForward) runBenchmark() error { log.Debug().Msg("Bench starting...") - p.bench.Run(p.App().Config.K9s.CurrentCluster, func() { - log.Debug().Msg("Bench Completed!") + ct, err := p.App().Config.K9s.ActiveContext() + if err != nil { + return err + } + name := p.App().Config.K9s.ActiveContextName() + p.bench.Run(ct.ClusterName, name, func() { + log.Debug().Msgf("Benchmark %q Completed!", name) p.App().QueueUpdate(func() { if p.bench.Canceled() { p.App().Status(model.FlashInfo, "Benchmark canceled") @@ -132,6 +145,8 @@ func (p *PortForward) runBenchmark() { }() }) }) + + return nil } func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index a80e8d8ad9..8a79167ff0 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -33,7 +33,12 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe SetFieldTextColor(styles.FieldFgColor.Color()). SetFieldBackgroundColor(styles.BgColor.Color()) - address := v.App().Config.CurrentCluster().PortForwardAddress + ct, err := v.App().Config.K9s.ActiveContext() + if err != nil { + log.Error().Err(err).Msgf("No active context detected") + return + } + address := ct.PortForwardAddress pf, err := aa.PreferredPorts(ports) if err != nil { diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 37040a8a3b..0f5710b74a 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -125,7 +125,7 @@ func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error { } log.Debug().Msgf(">>> Starting port forward %q -- %#v", pf.ID(), pt) go runForward(v, pf, fwd) - tt = append(tt, pt.ContainerPort) + tt = append(tt, pt.LocalPort) } if len(tt) == 1 { v.App().Flash().Infof("PortForward activated %s", tt[0]) @@ -137,6 +137,10 @@ func startFwdCB(v ResourceViewer, path string, pts port.PortTunnels) error { } func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { + ct, err := v.App().Config.CurrentContext() + if err != nil { + return err + } mm, anns, err := fetchPodPorts(v.App().factory, path) if err != nil { return err @@ -156,7 +160,7 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardCB) error { return err } - pts, err := pfs.ToTunnels(v.App().Config.CurrentCluster().PortForwardAddress, ports, port.IsPortFree) + pts, err := pfs.ToTunnels(ct.PortForwardAddress, ports, port.IsPortFree) if err != nil { return err } diff --git a/internal/view/picker.go b/internal/view/picker.go index bca0386746..b16042be95 100644 --- a/internal/view/picker.go +++ b/internal/view/picker.go @@ -27,6 +27,9 @@ func NewPicker() *Picker { } } +func (p *Picker) SetFilter(string) {} +func (p *Picker) SetLabelFilter(map[string]string) {} + // Init initializes the view. func (p *Picker) Init(ctx context.Context) error { app, err := extractApp(ctx) diff --git a/internal/view/pod.go b/internal/view/pod.go index c317304ab9..1ef29bab80 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -128,7 +128,7 @@ func (p *Pod) logOptions(prev bool) (*dao.LogOptions, error) { return &opts, nil } -func (p *Pod) showContainers(app *App, model ui.Tabular, gvr, path string) { +func (p *Pod) showContainers(app *App, _ ui.Tabular, _ client.GVR, _ string) { co := NewContainer(client.NewGVR("containers")) co.SetContextFn(p.coContext) if err := app.inject(co, false); err != nil { @@ -186,7 +186,10 @@ func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { } func (p *Pod) portForwardContext(ctx context.Context) context.Context { - ctx = context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) + if bc := p.App().BenchFile; bc != "" { + ctx = context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) + } + return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem()) } @@ -455,7 +458,7 @@ func buildShellArgs(cmd, path, co string, kcfg *string) []string { args := make([]string, 0, 15) args = append(args, cmd, "-it") ns, po := client.Namespaced(path) - if ns != client.AllNamespaces { + if ns != client.BlankNamespace { args = append(args, "-n", ns) } args = append(args, po) diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index 101bd5da6d..bbbf0378b9 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -9,7 +9,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/view" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,6 @@ func TestPodNew(t *testing.T) { // Helpers... func makeCtx() context.Context { - cfg := config.NewConfig(ks{}) + cfg := mock.NewMockConfig() return context.WithValue(context.Background(), internal.KeyApp, view.NewApp(cfg)) } diff --git a/internal/view/policy.go b/internal/view/policy.go index 35081e99de..28fee83e64 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -33,7 +33,7 @@ func NewPolicy(app *App, subject, name string) *Policy { subjectName: name, } p.AddBindKeysFn(p.bindKeys) - p.GetTable().SetSortCol(nameCol, false) + p.GetTable().SetSortCol("API-GROUP", false) p.SetContextFn(p.subjectCtx) p.GetTable().SetEnterFn(blankEnterFn) @@ -50,7 +50,7 @@ func (p *Policy) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(nameCol, true), false), - ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.GetTable().SortColCmd("GROUP", true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort Api-Group", p.GetTable().SortColCmd("API-GROUP", true), false), ui.KeyShiftB: ui.NewKeyAction("Sort Binding", p.GetTable().SortColCmd("BINDING", true), false), }) } diff --git a/internal/view/priorityclass.go b/internal/view/priorityclass.go index 78a0686681..fc89ef5010 100644 --- a/internal/view/priorityclass.go +++ b/internal/view/priorityclass.go @@ -5,6 +5,7 @@ package view import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" ) @@ -31,5 +32,5 @@ func (s *PriorityClass) bindKeys(aa ui.KeyActions) { } func (s *PriorityClass) refCmd(evt *tcell.EventKey) *tcell.EventKey { - return scanRefs(evt, s.App(), s.GetTable(), "scheduling.k8s.io/v1/priorityclasses") + return scanRefs(evt, s.App(), s.GetTable(), dao.PcGVR) } diff --git a/internal/view/pulse.go b/internal/view/pulse.go index d8acc0d0c1..c323f64d01 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -77,6 +77,9 @@ func NewPulse(gvr client.GVR) ResourceViewer { } } +func (p *Pulse) SetFilter(string) {} +func (p *Pulse) SetLabelFilter(map[string]string) {} + // Init initializes the view. func (p *Pulse) Init(ctx context.Context) error { p.SetBorder(true) diff --git a/internal/view/pvc.go b/internal/view/pvc.go index 075ff4efca..486fb46383 100644 --- a/internal/view/pvc.go +++ b/internal/view/pvc.go @@ -5,6 +5,7 @@ package view import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" ) @@ -35,5 +36,5 @@ func (p *PersistentVolumeClaim) bindKeys(aa ui.KeyActions) { } func (p *PersistentVolumeClaim) refCmd(evt *tcell.EventKey) *tcell.EventKey { - return scanRefs(evt, p.App(), p.GetTable(), "v1/persistentvolumeclaims") + return scanRefs(evt, p.App(), p.GetTable(), dao.PvcGVR) } diff --git a/internal/view/rbac.go b/internal/view/rbac.go index 0205ee2295..2ea21464e1 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -23,7 +23,7 @@ func NewRbac(gvr client.GVR) ResourceViewer { ResourceViewer: NewBrowser(gvr), } r.AddBindKeysFn(r.bindKeys) - r.GetTable().SetSortCol("APIGROUP", true) + r.GetTable().SetSortCol("API-GROUP", true) r.GetTable().SetEnterFn(blankEnterFn) return &r @@ -32,11 +32,11 @@ func NewRbac(gvr client.GVR) ResourceViewer { func (r *Rbac) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ - ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.GetTable().SortColCmd("APIGROUP", true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort API-Group", r.GetTable().SortColCmd("API-GROUP", true), false), }) } -func showRules(app *App, _ ui.Tabular, gvr, path string) { +func showRules(app *App, _ ui.Tabular, gvr client.GVR, path string) { v := NewRbac(client.NewGVR("rbac")) v.SetContextFn(rbacCtx(gvr, path)) @@ -45,11 +45,11 @@ func showRules(app *App, _ ui.Tabular, gvr, path string) { } } -func rbacCtx(gvr, path string) ContextFunc { +func rbacCtx(gvr client.GVR, path string) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) return context.WithValue(ctx, internal.KeyGVR, gvr) } } -func blankEnterFn(_ *App, _ ui.Tabular, _, _ string) {} +func blankEnterFn(_ *App, _ ui.Tabular, _ client.GVR, _ string) {} diff --git a/internal/view/reference.go b/internal/view/reference.go index d9a76d803a..5519eae907 100644 --- a/internal/view/reference.go +++ b/internal/view/reference.go @@ -33,7 +33,7 @@ func (r *Reference) Init(ctx context.Context) error { if err := r.ResourceViewer.Init(ctx); err != nil { return err } - r.GetTable().GetModel().SetNamespace(client.AllNamespaces) + r.GetTable().GetModel().SetNamespace(client.BlankNamespace) return nil } diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 52532c3633..cde177b9c1 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -61,6 +61,9 @@ func coreViewers(vv MetaViewers) { } func miscViewers(vv MetaViewers) { + vv[client.NewGVR("workloads")] = MetaViewer{ + viewerFn: NewWorkload, + } vv[client.NewGVR("contexts")] = MetaViewer{ viewerFn: NewContext, } @@ -156,7 +159,7 @@ func extViewers(vv MetaViewers) { } } -func showCRD(app *App, _ ui.Tabular, _, path string) { +func showCRD(app *App, _ ui.Tabular, _ client.GVR, path string) { _, crd := client.Namespaced(path) app.gotoResource(crd, "", false) } diff --git a/internal/view/rs.go b/internal/view/rs.go index 739ce6a9d7..3650b8ff15 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -38,7 +38,7 @@ func (r *ReplicaSet) bindKeys(aa ui.KeyActions) { }) } -func (r *ReplicaSet) showPods(app *App, model ui.Tabular, gvr, path string) { +func (r *ReplicaSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string) { var drs dao.ReplicaSet rs, err := drs.Load(app.factory, path) if err != nil { diff --git a/internal/view/sa.go b/internal/view/sa.go index d4a94a088e..c1433c2cce 100644 --- a/internal/view/sa.go +++ b/internal/view/sa.go @@ -41,7 +41,7 @@ func (s *ServiceAccount) subjectCtx(ctx context.Context) context.Context { } func (s *ServiceAccount) refCmd(evt *tcell.EventKey) *tcell.EventKey { - return scanSARefs(evt, s.App(), s.GetTable(), "v1/serviceaccounts") + return scanSARefs(evt, s.App(), s.GetTable(), dao.SaGVR) } func (s *ServiceAccount) policyCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -56,7 +56,7 @@ func (s *ServiceAccount) policyCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func scanSARefs(evt *tcell.EventKey, a *App, t *Table, gvr string) *tcell.EventKey { +func scanSARefs(evt *tcell.EventKey, a *App, t *Table, gvr client.GVR) *tcell.EventKey { path := t.GetSelectedItem() if path == "" { return evt diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index 2b71d7d46c..bae27e7fa0 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/xray" "github.com/derailed/tcell/v2" @@ -24,7 +25,7 @@ import ( var _ ResourceViewer = (*Sanitizer)(nil) -// Sanitizer represents an sanitizer tree view. +// Sanitizer represents a sanitizer tree view. type Sanitizer struct { *ui.Tree @@ -46,6 +47,9 @@ func NewSanitizer(gvr client.GVR) ResourceViewer { } } +func (s *Sanitizer) SetFilter(string) {} +func (s *Sanitizer) SetLabelFilter(map[string]string) {} + // Init initializes the view. func (s *Sanitizer) Init(ctx context.Context) error { s.envFn = s.k9sEnv @@ -96,7 +100,7 @@ func (*Sanitizer) InCmdMode() bool { // ExtraHints returns additional hints. func (s *Sanitizer) ExtraHints() map[string]string { - if s.app.Config.K9s.NoIcons { + if s.app.Config.K9s.UI.NoIcons { return nil } return xray.EmojiInfo() @@ -266,7 +270,7 @@ func (s *Sanitizer) TreeLoadFailed(err error) { } func (s *Sanitizer) update(node *xray.TreeNode) { - root := makeTreeNode(node, s.ExpandNodes(), s.app.Config.K9s.NoIcons, s.app.Styles) + root := makeTreeNode(node, s.ExpandNodes(), s.app.Config.K9s.UI.NoIcons, s.app.Styles) if node == nil { s.app.QueueUpdateDraw(func() { s.SetRoot(root) @@ -313,7 +317,7 @@ func (s *Sanitizer) TreeChanged(node *xray.TreeNode) { } func (s *Sanitizer) hydrate(parent *tview.TreeNode, n *xray.TreeNode) { - node := makeTreeNode(n, s.ExpandNodes(), s.app.Config.K9s.NoIcons, s.app.Styles) + node := makeTreeNode(n, s.ExpandNodes(), s.app.Config.K9s.UI.NoIcons, s.app.Styles) for _, c := range n.Children { s.hydrate(node, c) } @@ -414,9 +418,9 @@ func (s *Sanitizer) styleTitle() string { var title string if ns == client.ClusterScope { - title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, s.Count), s.app.Styles.Frame()) + title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame()) } else { - title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, s.Count), s.app.Styles.Frame()) + title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame()) } buff := s.CmdBuff().GetText() diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 9f12c30029..9183498c8b 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -9,7 +9,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" @@ -28,7 +28,7 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { s.GetTable().SetBorderFocusColor(tcell.ColorSteelBlue) s.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorRoyalBlue).Attributes(tcell.AttrNone)) s.GetTable().SetSortCol(ageCol, true) - s.GetTable().SelectRow(1, true) + s.GetTable().SelectRow(1, 0, true) s.GetTable().SetEnterFn(s.edit) s.SetContextFn(s.dirContext) @@ -36,8 +36,8 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { } func (s *ScreenDump) dirContext(ctx context.Context) context.Context { - dir := filepath.Join(s.App().Config.K9s.GetScreenDumpDir(), s.App().Config.K9s.CurrentContextDir()) - if err := config.EnsureFullPath(dir, config.DefaultDirMod); err != nil { + dir := filepath.Join(s.App().Config.K9s.GetScreenDumpDir(), s.App().Config.K9s.ActiveContextDir()) + if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil { s.App().Flash().Err(err) return ctx } @@ -45,7 +45,7 @@ func (s *ScreenDump) dirContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyDir, dir) } -func (s *ScreenDump) edit(app *App, model ui.Tabular, gvr, path string) { +func (s *ScreenDump) edit(app *App, _ ui.Tabular, _ client.GVR, path string) { log.Debug().Msgf("ScreenDump selection is %q", path) s.Stop() diff --git a/internal/view/secret.go b/internal/view/secret.go index 3cc78234c9..3237c24550 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -5,6 +5,7 @@ package view import ( "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" v1 "k8s.io/api/core/v1" @@ -37,7 +38,7 @@ func (s *Secret) bindKeys(aa ui.KeyActions) { } func (s *Secret) refCmd(evt *tcell.EventKey) *tcell.EventKey { - return scanRefs(evt, s.App(), s.GetTable(), "v1/secrets") + return scanRefs(evt, s.App(), s.GetTable(), dao.SecGVR) } func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/sts.go b/internal/view/sts.go index cbb77a7cd1..e9e04fcae1 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -86,7 +86,7 @@ func (s *StatefulSet) bindKeys(aa ui.KeyActions) { }) } -func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _, path string) { +func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string) { i, err := s.getInstance(path) if err != nil { app.Flash().Err(err) diff --git a/internal/view/svc.go b/internal/view/svc.go index b47bd01295..4abf37d772 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -48,12 +48,12 @@ func NewService(gvr client.GVR) ResourceViewer { func (s *Service) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - tcell.KeyCtrlL: ui.NewKeyAction("Bench Run/Stop", s.toggleBenchCmd, true), - ui.KeyShiftT: ui.NewKeyAction("Sort Type", s.GetTable().SortColCmd("TYPE", true), false), + ui.KeyB: ui.NewKeyAction("Bench Run/Stop", s.toggleBenchCmd, true), + ui.KeyShiftT: ui.NewKeyAction("Sort Type", s.GetTable().SortColCmd("TYPE", true), false), }) } -func (s *Service) showPods(a *App, _ ui.Tabular, gvr, path string) { +func (s *Service) showPods(a *App, _ ui.Tabular, _ client.GVR, path string) { var res dao.Service res.Init(a.factory, s.GVR()) @@ -71,7 +71,7 @@ func (s *Service) showPods(a *App, _ ui.Tabular, gvr, path string) { return } - showPodsWithLabels(a, path, svc.Spec.Selector) + showPods(a, path, toLabelsStr(svc.Spec.Selector), "") } func (s *Service) checkSvc(svc *v1.Service) error { @@ -153,14 +153,30 @@ func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error { } var err error - base := "http://" + cfg.HTTP.Host + ":" + port + cfg.HTTP.Path + base := cfg.HTTP.Host + if !strings.Contains(base, ":") { + base += ":" + port + cfg.HTTP.Path + } else { + base += cfg.HTTP.Path + } + if strings.Index(base, "http") != 0 { + base = "http://" + base + } + if s.bench, err = perf.NewBenchmark(base, s.App().version, cfg); err != nil { return err } s.App().Status(model.FlashWarn, "Benchmark in progress...") - log.Debug().Msg("Bench starting...") - go s.bench.Run(s.App().Config.K9s.CurrentCluster, s.benchDone) + log.Debug().Msg("Benchmark starting...") + + ct, err := s.App().Config.K9s.ActiveContext() + if err != nil { + return err + } + name := s.App().Config.K9s.ActiveContextName() + + go s.bench.Run(ct.ClusterName, name, s.benchDone) return nil } diff --git a/internal/view/table.go b/internal/view/table.go index 4db21ff17f..a2ce9aabf7 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -170,7 +170,7 @@ func (t *Table) BufferActive(state bool, k model.BufferKind) { } func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveTable(t.app.Config.K9s.GetScreenDumpDir(), t.app.Config.K9s.CurrentContextDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { + if path, err := saveTable(t.app.Config.K9s.GetScreenDumpDir(), t.app.Config.K9s.ActiveContextDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { t.app.Flash().Err(err) } else { t.app.Flash().Infof("File %s saved successfully!", path) diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index bb20ce490c..32e21b2dac 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -13,13 +13,13 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -29,7 +29,8 @@ func TestTableSave(t *testing.T) { assert.NoError(t, v.Init(makeContext())) v.SetTitle("k9s-test") - dir := filepath.Join(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.CurrentCluster) + assert.NoError(t, ensureDumpDir("/tmp/test-dumps")) + dir := filepath.Join(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.ActiveContextDir()) c1, _ := os.ReadDir(dir) v.saveCmd(nil) @@ -128,6 +129,7 @@ var _ ui.Tabular = (*mockTableModel)(nil) func (t *mockTableModel) SetInstance(string) {} func (t *mockTableModel) SetLabelFilter(string) {} +func (t *mockTableModel) GetLabelFilter() string { return "" } func (t *mockTableModel) Empty() bool { return false } func (t *mockTableModel) Count() int { return 1 } func (t *mockTableModel) HasMetrics() bool { return true } @@ -195,29 +197,40 @@ func makeTableData() *render.TableData { } func makeContext() context.Context { - a := NewApp(config.NewConfig(ks{})) + a := NewApp(mock.NewMockConfig()) ctx := context.WithValue(context.Background(), internal.KeyApp, a) return context.WithValue(ctx, internal.KeyStyles, a.Styles) } -type ks struct{} +// type ks struct{} -func (k ks) CurrentContextName() (string, error) { - return "test", nil -} +// func (k ks) CurrentContextName() (string, error) { +// return "test", nil +// } -func (k ks) CurrentClusterName() (string, error) { - return "test", nil -} +// func (k ks) CurrentClusterName() (string, error) { +// return "test", nil +// } -func (k ks) CurrentNamespaceName() (string, error) { - return "test", nil -} +// func (k ks) CurrentNamespaceName() (string, error) { +// return "test", nil +// } -func (k ks) ClusterNames() (map[string]struct{}, error) { - return map[string]struct{}{"test": {}}, nil -} +// func (k ks) ContextNames() (map[string]struct{}, error) { +// return map[string]struct{}{"test": {}}, nil +// } -func (k ks) NamespaceNames(nn []v1.Namespace) []string { - return []string{"test"} +// func (k ks) NamespaceNames(nn []v1.Namespace) []string { +// return []string{"test"} +// } + +func ensureDumpDir(n string) error { + config.AppDumpsDir = n + if _, err := os.Stat(n); os.IsNotExist(err) { + return os.Mkdir(n, 0700) + } + if err := os.RemoveAll(n); err != nil { + return err + } + return os.Mkdir(n, 0700) } diff --git a/internal/view/types.go b/internal/view/types.go index 6a1316f8c2..070db98c79 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -31,7 +31,7 @@ type ( BoostActionsFunc func(ui.KeyActions) // EnterFunc represents an enter key action. - EnterFunc func(app *App, model ui.Tabular, gvr, path string) + EnterFunc func(app *App, model ui.Tabular, gvr client.GVR, path string) // LogOptionsFunc returns the active log options. LogOptionsFunc func(bool) (*dao.LogOptions, error) diff --git a/internal/view/value_extender.go b/internal/view/value_extender.go index a7cf7f8c61..795da9f740 100644 --- a/internal/view/value_extender.go +++ b/internal/view/value_extender.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( @@ -20,7 +23,7 @@ type ValueExtender struct { func NewValueExtender(r ResourceViewer) ResourceViewer { p := ValueExtender{ResourceViewer: r} p.AddBindKeysFn(p.bindKeys) - p.GetTable().SetEnterFn(func(app *App, model ui.Tabular, gvr, path string) { + p.GetTable().SetEnterFn(func(app *App, model ui.Tabular, gvr client.GVR, path string) { p.valuesCmd(nil) }) diff --git a/internal/view/vul_extender.go b/internal/view/vul_extender.go index 2bd59b93a3..5c1774d62c 100644 --- a/internal/view/vul_extender.go +++ b/internal/view/vul_extender.go @@ -26,7 +26,7 @@ func NewVulnerabilityExtender(r ResourceViewer) ResourceViewer { } func (v *VulnerabilityExtender) bindKeys(aa ui.KeyActions) { - if v.App().Config.K9s.EnableImageScan { + if v.App().Config.K9s.ImageScans.Enable { aa.Add(ui.KeyActions{ ui.KeyV: ui.NewKeyAction("Show Vulnerabilities", v.showVulCmd, true), ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerabilities", v.GetTable().SortColCmd("VS", true), false), diff --git a/internal/view/workload.go b/internal/view/workload.go new file mode 100644 index 0000000000..dd4d43b66d --- /dev/null +++ b/internal/view/workload.go @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "context" + "fmt" + "strings" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/ui/dialog" + "github.com/derailed/tcell/v2" + "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Workload presents a workload viewer. +type Workload struct { + ResourceViewer +} + +// NewWorkload returns a new viewer. +func NewWorkload(gvr client.GVR) ResourceViewer { + w := Workload{ + ResourceViewer: NewBrowser(gvr), + } + w.GetTable().SetEnterFn(w.showRes) + w.AddBindKeysFn(w.bindKeys) + w.GetTable().SetSortCol("KIND", true) + + return &w +} + +func (w *Workload) bindDangerousKeys(aa ui.KeyActions) { + aa.Add(ui.KeyActions{ + ui.KeyE: ui.NewKeyAction("Edit", w.editCmd, true), + tcell.KeyCtrlD: ui.NewKeyAction("Delete", w.deleteCmd, true), + }) +} + +func (w *Workload) bindKeys(aa ui.KeyActions) { + if !w.App().Config.K9s.IsReadOnly() { + w.bindDangerousKeys(aa) + } + + aa.Add(ui.KeyActions{ + ui.KeyShiftK: ui.NewKeyAction("Sort Kind", w.GetTable().SortColCmd("KIND", true), false), + ui.KeyShiftS: ui.NewKeyAction("Sort Status", w.GetTable().SortColCmd(statusCol, true), false), + ui.KeyShiftR: ui.NewKeyAction("Sort Ready", w.GetTable().SortColCmd("READY", true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort Age", w.GetTable().SortColCmd(ageCol, true), false), + ui.KeyY: ui.NewKeyAction(yamlAction, w.yamlCmd, true), + ui.KeyD: ui.NewKeyAction("Describe", w.describeCmd, true), + }) +} + +func parsePath(path string) (client.GVR, string, bool) { + tt := strings.Split(path, "|") + if len(tt) != 3 { + log.Error().Msgf("unable to parse path: %q", path) + return client.NewGVR(""), client.FQN("", ""), false + } + + return client.NewGVR(tt[0]), client.FQN(tt[1], tt[2]), true +} + +func (w *Workload) showRes(app *App, _ ui.Tabular, _ client.GVR, path string) { + gvr, fqn, ok := parsePath(path) + if !ok { + app.Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + return + } + app.gotoResource(gvr.R(), fqn, false) +} + +func (w *Workload) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { + selections := w.GetTable().GetSelectedItems() + if len(selections) == 0 { + return evt + } + + w.Stop() + defer w.Start() + { + msg := fmt.Sprintf("Delete %s %s?", w.GVR().R(), selections[0]) + if len(selections) > 1 { + msg = fmt.Sprintf("Delete %d marked %s?", len(selections), w.GVR()) + } + w.resourceDelete(selections, msg) + } + + return nil +} + +func (w *Workload) defaultContext(gvr client.GVR, fqn string) context.Context { + ctx := context.WithValue(context.Background(), internal.KeyFactory, w.App().factory) + ctx = context.WithValue(ctx, internal.KeyGVR, gvr) + if fqn != "" { + ctx = context.WithValue(ctx, internal.KeyPath, fqn) + } + if ui.IsLabelSelector(w.GetTable().CmdBuff().GetText()) { + ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(w.GetTable().CmdBuff().GetText())) + } + ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(w.App().Config.ActiveNamespace())) + ctx = context.WithValue(ctx, internal.KeyWithMetrics, w.App().factory.Client().HasMetrics()) + + return ctx +} + +func (w *Workload) resourceDelete(selections []string, msg string) { + okFn := func(propagation *metav1.DeletionPropagation, force bool) { + w.GetTable().ShowDeleted() + if len(selections) > 1 { + w.App().Flash().Infof("Delete %d marked %s", len(selections), w.GVR()) + } else { + w.App().Flash().Infof("Delete resource %s %s", w.GVR(), selections[0]) + } + for _, sel := range selections { + gvr, fqn, ok := parsePath(sel) + if !ok { + w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", sel)) + return + } + + grace := dao.DefaultGrace + if force { + grace = dao.ForceGrace + } + if err := w.GetTable().GetModel().Delete(w.defaultContext(gvr, fqn), fqn, propagation, grace); err != nil { + w.App().Flash().Errf("Delete failed with `%s", err) + } else { + w.App().factory.DeleteForwarder(sel) + } + w.GetTable().DeleteMark(sel) + } + w.GetTable().Start() + } + dialog.ShowDelete(w.App().Styles.Dialog(), w.App().Content.Pages, msg, okFn, func() {}) +} + +func (w *Workload) describeCmd(evt *tcell.EventKey) *tcell.EventKey { + path := w.GetTable().GetSelectedItem() + if path == "" { + return evt + } + gvr, fqn, ok := parsePath(path) + if !ok { + w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + return evt + } + + describeResource(w.App(), nil, gvr, fqn) + + return nil +} + +func (w *Workload) editCmd(evt *tcell.EventKey) *tcell.EventKey { + path := w.GetTable().GetSelectedItem() + if path == "" { + return evt + } + gvr, fqn, ok := parsePath(path) + if !ok { + w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + return evt + } + + w.Stop() + defer w.Start() + if err := editRes(w.App(), gvr, fqn); err != nil { + w.App().Flash().Err(err) + } + + return nil +} + +func (w *Workload) yamlCmd(evt *tcell.EventKey) *tcell.EventKey { + path := w.GetTable().GetSelectedItem() + if path == "" { + return evt + } + gvr, fqn, ok := parsePath(path) + if !ok { + w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + return evt + } + + v := NewLiveView(w.App(), yamlAction, model.NewYAML(gvr, fqn)) + if err := v.app.inject(v, false); err != nil { + v.app.Flash().Err(err) + } + + return nil +} + +// func (w *Workload) editCmd(evt *tcell.EventKey) *tcell.EventKey { +// path := w.GetTable().GetSelectedItem() +// if path == "" { +// return evt +// } +// gvr, fqn, ok := parsePath(path) +// if !ok { +// w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) +// return evt +// } + +// w.Stop() +// defer w.Start() +// { +// ns, n := client.Namespaced(fqn) +// args := make([]string, 0, 10) +// args = append(args, "edit") +// args = append(args, gvr.R()) +// args = append(args, "-n", ns) +// args = append(args, "--context", w.App().Config.K9s.CurrentContext) +// if cfg := w.App().Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { +// args = append(args, "--kubeconfig", *cfg) +// } +// if err := runK(w.App(), shellOpts{args: append(args, n)}); err != nil { +// w.App().Flash().Errf("Edit exec failed: %s", err) +// } +// } + +// return evt +// } diff --git a/internal/view/xray.go b/internal/view/xray.go index f2dfb0a8b9..26c9caff77 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -15,6 +15,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/xray" @@ -52,6 +53,9 @@ func NewXray(gvr client.GVR) ResourceViewer { } } +func (x *Xray) SetFilter(string) {} +func (x *Xray) SetLabelFilter(map[string]string) {} + // Init initializes the view. func (x *Xray) Init(ctx context.Context) error { x.envFn = x.k9sEnv @@ -103,7 +107,7 @@ func (*Xray) InCmdMode() bool { // ExtraHints returns additional hints. func (x *Xray) ExtraHints() map[string]string { - if x.app.Config.K9s.NoIcons { + if x.app.Config.K9s.UI.NoIcons { return nil } return xray.EmojiInfo() @@ -411,7 +415,7 @@ func (x *Xray) editCmd(evt *tcell.EventKey) *tcell.EventKey { args = append(args, "edit") args = append(args, client.NewGVR(spec.GVR()).R()) args = append(args, "-n", ns) - args = append(args, "--context", x.app.Config.K9s.CurrentContext) + args = append(args, "--context", x.app.Config.K9s.ActiveContextName()) if cfg := x.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { args = append(args, "--kubeconfig", *cfg) } @@ -501,7 +505,7 @@ func (x *Xray) TreeLoadFailed(err error) { } func (x *Xray) update(node *xray.TreeNode) { - root := makeTreeNode(node, x.ExpandNodes(), x.app.Config.K9s.NoIcons, x.app.Styles) + root := makeTreeNode(node, x.ExpandNodes(), x.app.Config.K9s.UI.NoIcons, x.app.Styles) if node == nil { x.app.QueueUpdateDraw(func() { x.SetRoot(root) @@ -548,7 +552,7 @@ func (x *Xray) TreeChanged(node *xray.TreeNode) { } func (x *Xray) hydrate(parent *tview.TreeNode, n *xray.TreeNode) { - node := makeTreeNode(n, x.ExpandNodes(), x.app.Config.K9s.NoIcons, x.app.Styles) + node := makeTreeNode(n, x.ExpandNodes(), x.app.Config.K9s.UI.NoIcons, x.app.Styles) for _, c := range n.Children { x.hydrate(node, c) } @@ -644,9 +648,9 @@ func (x *Xray) styleTitle() string { var title string if ns == client.ClusterScope { - title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, x.Count), x.app.Styles.Frame()) + title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame()) } else { - title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, x.Count), x.app.Styles.Frame()) + title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame()) } buff := x.CmdBuff().GetText() diff --git a/internal/view/yaml.go b/internal/view/yaml.go index 0fda208726..dba886733d 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -68,7 +68,7 @@ func saveYAML(screenDumpDir, context, name, data string) (string, error) { return "", err } - fName := fmt.Sprintf("%s--%d.yml", config.SanitizeFilename(name), time.Now().Unix()) + fName := fmt.Sprintf("%s--%d.yaml", config.SanitizeFilename(name), time.Now().Unix()) path := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(path, mod, 0600) diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index 003809dc5b..64cbfc5794 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/derailed/k9s/internal/config" "github.com/rs/zerolog/log" "github.com/anchore/clio" @@ -26,6 +27,7 @@ import ( "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" "github.com/anchore/grype/grype/vex" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ImgScanner *imageScanner @@ -38,15 +40,21 @@ type imageScanner struct { scans Scans mx sync.RWMutex initialized bool + config *config.ImageScans } // NewImageScanner returns a new instance. -func NewImageScanner() *imageScanner { +func NewImageScanner(cfg *config.ImageScans) *imageScanner { return &imageScanner{ - scans: make(Scans), + scans: make(Scans), + config: cfg, } } +func (s *imageScanner) ShouldExcludes(m metav1.ObjectMeta) bool { + return s.config.ShouldExclude(m.Namespace, m.Labels) +} + // GetScan fetch scan for a given image. Returns ok=false when not found. func (s *imageScanner) GetScan(img string) (*Scan, bool) { s.mx.RLock() @@ -56,7 +64,7 @@ func (s *imageScanner) GetScan(img string) (*Scan, bool) { return scan, ok } -func (s *imageScanner) SetScan(img string, sc *Scan) { +func (s *imageScanner) setScan(img string, sc *Scan) { s.mx.Lock() defer s.mx.Unlock() s.scans[img] = sc @@ -127,7 +135,7 @@ func (s *imageScanner) Enqueue(images ...string) { return } sc := newScan(img) - s.SetScan(img, sc) + s.setScan(img, sc) if err := s.scan(img, sc); err != nil { log.Warn().Err(err).Msgf("Scan failed for img %s --", img) } diff --git a/internal/watch/factory.go b/internal/watch/factory.go index fcd67300fe..d21726f121 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -11,6 +11,8 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" di "k8s.io/client-go/dynamic/dynamicinformer" @@ -75,7 +77,7 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run return nil, err } if client.IsAllNamespace(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } var oo []runtime.Object @@ -131,7 +133,7 @@ func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime. func (f *Factory) waitForCacheSync(ns string) { if client.IsClusterWide(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } f.mx.RLock() @@ -182,7 +184,7 @@ func (f *Factory) SetActiveNS(ns string) error { func (f *Factory) isClusterWide() bool { f.mx.RLock() defer f.mx.RUnlock() - _, ok := f.factories[client.AllNamespaces] + _, ok := f.factories[client.BlankNamespace] return ok } @@ -221,7 +223,7 @@ func (f *Factory) ForResource(ns, gvr string) (informers.GenericInformer, error) func (f *Factory) ensureFactory(ns string) (di.DynamicSharedInformerFactory, error) { if client.IsClusterWide(ns) { - ns = client.AllNamespaces + ns = client.BlankNamespace } f.mx.Lock() defer f.mx.Unlock() @@ -275,8 +277,8 @@ func (f *Factory) ForwarderFor(path string) (Forwarder, bool) { return fwd, ok } -// BOZO!! Review!!! // ValidatePortForwards check if pods are still around for portforwards. +// BOZO!! Review!!! func (f *Factory) ValidatePortForwards() { for k, fwd := range f.forwarders { tokens := strings.Split(k, ":") @@ -288,10 +290,19 @@ func (f *Factory) ValidatePortForwards() { if len(paths) < 1 { log.Error().Msgf("Invalid path %q", tokens[0]) } - _, err := f.Get("v1/pods", paths[0], false, labels.Everything()) + o, err := f.Get("v1/pods", paths[0], false, labels.Everything()) if err != nil { fwd.Stop() delete(f.forwarders, k) + continue + } + var pod v1.Pod + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod); err != nil { + continue + } + if pod.GetCreationTimestamp().Time.Unix() > fwd.Age().Unix() { + fwd.Stop() + delete(f.forwarders, k) } } } diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index 94abc1622c..8eab5c0657 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -5,6 +5,7 @@ package watch import ( "strings" + "time" "github.com/derailed/k9s/internal/port" "github.com/rs/zerolog/log" @@ -38,7 +39,7 @@ type Forwarder interface { SetActive(bool) // Age returns forwarder age. - Age() string + Age() time.Time // HasPortMapping returns true if port mapping exists. HasPortMapping(string) bool diff --git a/internal/watch/forwarders_test.go b/internal/watch/forwarders_test.go index f6a22b5b83..19e847853b 100644 --- a/internal/watch/forwarders_test.go +++ b/internal/watch/forwarders_test.go @@ -5,6 +5,7 @@ package watch_test import ( "testing" + "time" "github.com/derailed/k9s/internal/port" "github.com/derailed/k9s/internal/watch" @@ -182,5 +183,5 @@ func (m noOpForwarder) Port() string { return "" } func (m noOpForwarder) FQN() string { return "" } func (m noOpForwarder) Active() bool { return false } func (m noOpForwarder) SetActive(bool) {} -func (m noOpForwarder) Age() string { return "" } +func (m noOpForwarder) Age() time.Time { return time.Now() } func (m noOpForwarder) HasPortMapping(string) bool { return false } diff --git a/internal/xray/generic.go b/internal/xray/generic.go index 7684385095..8d30badc1d 100644 --- a/internal/xray/generic.go +++ b/internal/xray/generic.go @@ -8,22 +8,22 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Generic renders a generic resource to screen. type Generic struct { - table *metav1beta1.Table + table *metav1.Table } // SetTable sets the tabular resource. -func (g *Generic) SetTable(_ string, t *metav1beta1.Table) { +func (g *Generic) SetTable(_ string, t *metav1.Table) { g.table = t } // Render renders a K8s resource to screen. func (g *Generic) Render(ctx context.Context, ns string, o interface{}) error { - row, ok := o.(metav1beta1.TableRow) + row, ok := o.(metav1.TableRow) if !ok { return fmt.Errorf("expecting a TableRow but got %T", o) } diff --git a/plugins/carvel.yml b/plugins/carvel.yaml similarity index 100% rename from plugins/carvel.yml rename to plugins/carvel.yaml diff --git a/plugins/crossplane.yml b/plugins/crossplane.yaml similarity index 100% rename from plugins/crossplane.yml rename to plugins/crossplane.yaml diff --git a/plugins/debug-container.yml b/plugins/debug-container.yaml similarity index 100% rename from plugins/debug-container.yml rename to plugins/debug-container.yaml diff --git a/plugins/dive.yml b/plugins/dive.yaml similarity index 100% rename from plugins/dive.yml rename to plugins/dive.yaml diff --git a/plugins/flux.yml b/plugins/flux.yaml similarity index 100% rename from plugins/flux.yml rename to plugins/flux.yaml diff --git a/plugins/get-all.yml b/plugins/get-all.yaml similarity index 100% rename from plugins/get-all.yml rename to plugins/get-all.yaml diff --git a/plugins/helm-default-values.yml b/plugins/helm-default-values.yaml similarity index 100% rename from plugins/helm-default-values.yml rename to plugins/helm-default-values.yaml diff --git a/plugins/helm-purge.yml b/plugins/helm-purge.yaml similarity index 100% rename from plugins/helm-purge.yml rename to plugins/helm-purge.yaml diff --git a/plugins/helm_values.yml b/plugins/helm_values.yaml similarity index 100% rename from plugins/helm_values.yml rename to plugins/helm_values.yaml diff --git a/plugins/job_suspend.yml b/plugins/job_suspend.yaml similarity index 100% rename from plugins/job_suspend.yml rename to plugins/job_suspend.yaml diff --git a/plugins/k3d_root_shell.yml b/plugins/k3d_root_shell.yaml similarity index 100% rename from plugins/k3d_root_shell.yml rename to plugins/k3d_root_shell.yaml diff --git a/plugins/log_full.yml b/plugins/log_full.yaml similarity index 100% rename from plugins/log_full.yml rename to plugins/log_full.yaml diff --git a/plugins/log_jq.yml b/plugins/log_jq.yaml similarity index 100% rename from plugins/log_jq.yml rename to plugins/log_jq.yaml diff --git a/plugins/log_stern.yml b/plugins/log_stern.yaml similarity index 100% rename from plugins/log_stern.yml rename to plugins/log_stern.yaml diff --git a/plugins/rm-ns.yml b/plugins/rm-ns.yaml similarity index 100% rename from plugins/rm-ns.yml rename to plugins/rm-ns.yaml diff --git a/plugins/watch_events.yml b/plugins/watch_events.yaml similarity index 100% rename from plugins/watch_events.yml rename to plugins/watch_events.yaml diff --git a/skins/axual.yml b/skins/axual.yaml similarity index 100% rename from skins/axual.yml rename to skins/axual.yaml diff --git a/skins/black_and_wtf.yml b/skins/black_and_wtf.yaml similarity index 100% rename from skins/black_and_wtf.yml rename to skins/black_and_wtf.yaml diff --git a/skins/dracula.yml b/skins/dracula.yaml similarity index 100% rename from skins/dracula.yml rename to skins/dracula.yaml diff --git a/skins/gruvbox-dark.yml b/skins/gruvbox-dark.yaml similarity index 100% rename from skins/gruvbox-dark.yml rename to skins/gruvbox-dark.yaml diff --git a/skins/gruvbox-light.yml b/skins/gruvbox-light.yaml similarity index 100% rename from skins/gruvbox-light.yml rename to skins/gruvbox-light.yaml diff --git a/skins/in_the_navy.yml b/skins/in_the_navy.yaml similarity index 100% rename from skins/in_the_navy.yml rename to skins/in_the_navy.yaml diff --git a/skins/kiss.yml b/skins/kiss.yaml similarity index 100% rename from skins/kiss.yml rename to skins/kiss.yaml diff --git a/skins/monokai.yml b/skins/monokai.yaml similarity index 100% rename from skins/monokai.yml rename to skins/monokai.yaml diff --git a/skins/narsingh.yml b/skins/narsingh.yaml similarity index 100% rename from skins/narsingh.yml rename to skins/narsingh.yaml diff --git a/skins/nightfox.yml b/skins/nightfox.yaml similarity index 100% rename from skins/nightfox.yml rename to skins/nightfox.yaml diff --git a/skins/nord.yml b/skins/nord.yaml similarity index 100% rename from skins/nord.yml rename to skins/nord.yaml diff --git a/skins/one_dark.yml b/skins/one_dark.yaml similarity index 100% rename from skins/one_dark.yml rename to skins/one_dark.yaml diff --git a/skins/red.yml b/skins/red.yaml similarity index 100% rename from skins/red.yml rename to skins/red.yaml diff --git a/skins/rose_pine.yml b/skins/rose_pine.yaml similarity index 100% rename from skins/rose_pine.yml rename to skins/rose_pine.yaml diff --git a/skins/snazzy.yml b/skins/snazzy.yaml similarity index 100% rename from skins/snazzy.yml rename to skins/snazzy.yaml diff --git a/skins/solarized-16.yml b/skins/solarized-16.yaml similarity index 100% rename from skins/solarized-16.yml rename to skins/solarized-16.yaml diff --git a/skins/solarized_dark.yml b/skins/solarized_dark.yaml similarity index 100% rename from skins/solarized_dark.yml rename to skins/solarized_dark.yaml diff --git a/skins/solarized_light.yml b/skins/solarized_light.yaml similarity index 100% rename from skins/solarized_light.yml rename to skins/solarized_light.yaml diff --git a/skins/stock.yml b/skins/stock.yaml similarity index 100% rename from skins/stock.yml rename to skins/stock.yaml diff --git a/skins/transparent.yml b/skins/transparent.yaml similarity index 100% rename from skins/transparent.yml rename to skins/transparent.yaml From f8ad4aa8c7240ed83a627228a3cc4b306c6e842d Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Mon, 25 Dec 2023 02:18:47 +0800 Subject: [PATCH 037/169] adding cancelable launch prompts to NodeShell (#2360) --- internal/ui/dialog/prompt.go | 57 ++++++++++++++++++++++++ internal/ui/dialog/prompt_test.go | 46 ++++++++++++++++++++ internal/view/exec.go | 72 +++++++++++++++++++++++-------- internal/view/node.go | 5 +-- 4 files changed, 157 insertions(+), 23 deletions(-) create mode 100644 internal/ui/dialog/prompt.go create mode 100644 internal/ui/dialog/prompt_test.go diff --git a/internal/ui/dialog/prompt.go b/internal/ui/dialog/prompt.go new file mode 100644 index 0000000000..622ae4a991 --- /dev/null +++ b/internal/ui/dialog/prompt.go @@ -0,0 +1,57 @@ +package dialog + +import ( + "context" + + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tview" +) + +type promptAction func(ctx context.Context) + +// ShowPrompt pops a prompt dialog. +func ShowPrompt(styles config.Dialog, pages *ui.Pages, title, msg string, action promptAction, cancel cancelFunc) { + f := tview.NewForm() + f.SetItemPadding(0) + f.SetButtonsAlign(tview.AlignCenter). + SetButtonBackgroundColor(styles.ButtonBgColor.Color()). + SetButtonTextColor(styles.ButtonFgColor.Color()). + SetLabelColor(styles.LabelFgColor.Color()). + SetFieldTextColor(styles.FieldFgColor.Color()) + + ctx, cancelCtx := context.WithCancel(context.Background()) + + f.AddButton("Cancel", func() { + dismiss(pages) + cancelCtx() + cancel() + }) + + for i := 0; i < f.GetButtonCount(); i++ { + b := f.GetButton(i) + if b == nil { + continue + } + b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()) + b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color()) + } + + f.SetFocus(0) + modal := tview.NewModalForm("<"+title+">", f) + modal.SetText(msg) + modal.SetTextColor(styles.FgColor.Color()) + modal.SetDoneFunc(func(int, string) { + dismiss(pages) + cancelCtx() + cancel() + }) + + pages.AddPage(dialogKey, modal, false, false) + pages.ShowPage(dialogKey) + + go func() { + action(ctx) + dismiss(pages) + }() +} diff --git a/internal/ui/dialog/prompt_test.go b/internal/ui/dialog/prompt_test.go new file mode 100644 index 0000000000..7d4ade05fa --- /dev/null +++ b/internal/ui/dialog/prompt_test.go @@ -0,0 +1,46 @@ +package dialog + +import ( + "context" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tcell/v2" + "github.com/derailed/tview" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestShowPrompt(t *testing.T) { + t.Run("waiting done", func(t *testing.T) { + a := tview.NewApplication() + p := ui.NewPages() + a.SetRoot(p, false) + + ShowPrompt(config.Dialog{}, p, "Running", "Pod", func(context.Context) { + time.Sleep(time.Millisecond) + }, func() { + t.Errorf("unexpected cancellations") + }) + }) + + t.Run("canceled", func(t *testing.T) { + a := tview.NewApplication() + p := ui.NewPages() + a.SetRoot(p, false) + + go ShowPrompt(config.Dialog{}, p, "Running", "Pod", func(ctx context.Context) { + select { + case <-time.After(time.Second): + t.Errorf("expected cancellations") + case <-ctx.Done(): + } + }, func() {}) + + time.Sleep(time.Second / 2) + d := p.GetPrimitive(dialogKey).(*tview.ModalForm) + if assert.NotNil(t, d) { + d.InputHandler()(tcell.NewEventKey(tcell.KeyEnter, '\n', 0), func(tview.Primitive) {}) + } + }) +} diff --git a/internal/view/exec.go b/internal/view/exec.go index f4f45bf4e7..ed54d98888 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -18,6 +18,8 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/ui/dialog" "github.com/fatih/color" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -233,25 +235,50 @@ func clearScreen() { const ( k9sShell = "k9s-shell" - k9sShellRetryCount = 10 - k9sShellRetryDelay = 10 * time.Second + k9sShellRetryCount = 50 + k9sShellRetryDelay = 2 * time.Second ) -func ssh(a *App, node string) error { +func launchNodeShell(v model.Igniter, a *App, node string) { if err := nukeK9sShell(a); err != nil { - return err + a.Flash().Errf("Cleaning node shell failed: %s", err) + return } + + msg := fmt.Sprintf("Launching node shell on %s...", node) + dialog.ShowPrompt(a.Styles.Dialog(), a.Content.Pages, "Launching", msg, func(ctx context.Context) { + err := launchShellPod(ctx, a, node) + if err != nil { + if !errors.Is(err, context.Canceled) { + a.Flash().Errf("Launching node shell failed: %s", err) + } + return + } + + go launchPodShell(v, a) + }, func() { + if err := nukeK9sShell(a); err != nil { + a.Flash().Errf("Cleaning node shell failed: %s", err) + return + } + }) +} + +func launchPodShell(v model.Igniter, a *App) { defer func() { if err := nukeK9sShell(a); err != nil { - log.Error().Err(err).Msgf("nuking k9s shell pod") + a.Flash().Errf("Launching node shell failed: %s", err) + return } }() - if err := launchShellPod(a, node); err != nil { - return err - } - ns := a.Config.K9s.ShellPod.Namespace - return sshIn(a, client.FQN(ns, k9sShellPodName()), k9sShell) + v.Stop() + defer v.Start() + + ns := a.Config.K9s.ShellPod.Namespace + if err := sshIn(a, client.FQN(ns, k9sShellPodName()), k9sShell); err != nil { + a.Flash().Errf("Launching node shell failed: %s", err) + } } func sshIn(a *App, fqn, co string) error { @@ -309,31 +336,33 @@ func nukeK9sShell(a *App) error { return err } -func launchShellPod(a *App, node string) error { - a.Flash().Infof("Launching node shell on %s...", node) - +func launchShellPod(ctx context.Context, a *App, node string) error { var ( spo = a.Config.K9s.ShellPod spec = k9sShellPod(node, spo) ) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() dial, err := a.Conn().Dial() if err != nil { return err } + conn := dial.CoreV1().Pods(spo.Namespace) - if _, err := conn.Create(ctx, spec, metav1.CreateOptions{}); err != nil { + if _, err = conn.Create(ctx, spec, metav1.CreateOptions{}); err != nil { return err } for i := 0; i < k9sShellRetryCount; i++ { o, err := a.factory.Get("v1/pods", client.FQN(spo.Namespace, k9sShellPodName()), true, labels.Everything()) if err != nil { - time.Sleep(k9sShellRetryDelay) - continue + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(k9sShellRetryDelay): + continue + } } + var pod v1.Pod if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod); err != nil { return err @@ -342,7 +371,12 @@ func launchShellPod(a *App, node string) error { if pod.Status.Phase == v1.PodRunning { return nil } - time.Sleep(k9sShellRetryDelay) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(k9sShellRetryDelay): + } } return fmt.Errorf("unable to launch shell pod on node %s", node) diff --git a/internal/view/node.go b/internal/view/node.go index 6e3b17cbe4..636031953d 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -14,7 +14,6 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -161,9 +160,7 @@ func (n *Node) sshCmd(evt *tcell.EventKey) *tcell.EventKey { n.Stop() defer n.Start() _, node := client.Namespaced(path) - if err := ssh(n.App(), node); err != nil { - log.Error().Err(err).Msgf("Node Shell Failed") - } + launchNodeShell(n, n.App(), node) return nil } From f0d0e62b700f08b04318d5c53ed0012ef2b0c8d9 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sun, 24 Dec 2023 11:29:22 -0700 Subject: [PATCH 038/169] K9s/release v0.30.1 (#2369) * [Bug] Fix #2368 * [Bug] Fix #2363 * [Bug] Fix #2364 * [Bug] Fix #2366 * [Bug] Fix #2367 * Release docs + rebase fixes --- Makefile | 2 +- change_logs/release_v0.30.1.md | 57 ++++++++++++ internal/config/styles.go | 1 - internal/config/templates/stock-skin.yaml | 103 +++++++++++++--------- internal/dao/container.go | 2 +- internal/dao/node.go | 2 +- internal/dao/pod.go | 4 +- internal/render/pod.go | 4 +- internal/ui/config.go | 3 +- internal/ui/dialog/prompt.go | 3 + internal/ui/dialog/prompt_test.go | 8 +- internal/ui/table.go | 6 +- internal/ui/table_helper.go | 8 ++ internal/ui/table_helper_test.go | 23 +++++ internal/view/cmd/args.go | 2 +- internal/view/cmd/helpers.go | 2 +- internal/view/cmd/helpers_test.go | 2 +- internal/view/cmd/interpreter.go | 2 +- internal/view/helpers.go | 13 +-- internal/view/log_indicator.go | 3 - internal/view/log_indicator_test.go | 4 +- internal/view/node.go | 1 + plugins/carvel.yaml | 2 +- plugins/crossplane.yaml | 10 +-- plugins/debug-container.yaml | 2 +- plugins/dive.yaml | 2 +- plugins/flux.yaml | 68 +++++++------- plugins/get-all.yaml | 2 +- plugins/helm-default-values.yaml | 2 +- plugins/helm-purge.yaml | 2 +- plugins/helm_values.yaml | 2 +- plugins/job_suspend.yaml | 2 +- plugins/k3d_root_shell.yaml | 2 +- plugins/log_full.yaml | 2 +- plugins/log_jq.yaml | 2 +- plugins/log_stern.yaml | 2 +- plugins/rm-ns.yaml | 2 +- plugins/watch_events.yaml | 2 +- snap/snapcraft.yaml | 2 +- 39 files changed, 235 insertions(+), 128 deletions(-) create mode 100644 change_logs/release_v0.30.1.md diff --git a/Makefile b/Makefile index 06dfb4c1cd..29f751e9e8 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.0 +VERSION ?= v0.30.1 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.1.md b/change_logs/release_v0.30.1.md new file mode 100644 index 0000000000..a983a8d424 --- /dev/null +++ b/change_logs/release_v0.30.1.md @@ -0,0 +1,57 @@ + + +# Release v0.30.1 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +🎵 `On The twentyfouth day of Christmas my true love gave to me... Bugs!!` 🎵 + +Got to love the aftermath... Thank you all for pitch'in in and help flesh out bugs!! The gift that keeps on... giving? + +🎅 Merry Christmas to all and Best wishes for the new year!!🧑‍🎄 + +--- + +### Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2368](https://github.com/derailed/k9s/issues/2368) Pod CPU and MEM columns are empty in 0.30.0 +* [#2367](https://github.com/derailed/k9s/issues/2367) k9s 0.30.0 issue loading plugins +* [#2366](https://github.com/derailed/k9s/issues/2366) List pods of deployment is now impossible +* [#2264](https://github.com/derailed/k9s/issues/2264) k9s 0.30.0 fields and values missed in action in the "namespace view" +* [#2263](https://github.com/derailed/k9s/issues/2263) Default 0.30.0 default skin on macOS is no good + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2360](https://github.com/derailed/k9s/pull/2360) adding cancelable launch prompts to NodeShell + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/styles.go b/internal/config/styles.go index 2ad1956ca3..5dd3c09950 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -541,7 +541,6 @@ func (s *Styles) Load(path string) error { if err != nil { return err } - if err := yaml.Unmarshal(f, s); err != nil { return err } diff --git a/internal/config/templates/stock-skin.yaml b/internal/config/templates/stock-skin.yaml index 02e68ab280..3d677bf89f 100644 --- a/internal/config/templates/stock-skin.yaml +++ b/internal/config/templates/stock-skin.yaml @@ -17,24 +17,24 @@ k9s: bgColor: black suggestColor: dodgerblue border: - default: seagreen command: aqua - info: - fgColor: orange - sectionColor: white - dialog: - fgColor: dodgerblue + default: seagreen + help: + fgColor: cadetblue bgColor: black - buttonFgColor: black - buttonBgColor: dodgerblue - buttonFocusFgColor: white - buttonFocusBgColor: fuchsia - labelFgColor: fuchsia - fieldFgColor: dodgerblue + sectionColor: green + keyColor: dodgerblue + numKeyColor: fuchsia frame: + title: + fgColor: aqua + bgColor: black + highlightColor: fuchsia + counterColor: papayawhip + filterColor: seagreen border: fgColor: dodgerblue - focusColor: aqua + focusColor: lightskyblue menu: fgColor: white keyColor: dodgerblue @@ -46,52 +46,71 @@ k9s: status: newColor: lightskyblue modifyColor: greenyellow - addColor: white - errorColor: orangered + addColor: dodgerblue pendingColor: darkorange + errorColor: orangered highlightColor: aqua killColor: mediumpurple - completedColor: gray - title: - fgColor: aqua - highlightColor: fuchsia - counterColor: papayawhip - filterColor: steelblue + completedColor: lightslategray + info: + sectionColor: white + fgColor: orange views: - # Charts skins... - charts: - bgColor: black - defaultDialColors: - - linegreen - - orangered - defaultChartColors: - - linegreen - - orangered table: fgColor: aqua bgColor: black - cursorFgColor: white - cursorBgColor: black - markColor: darkgoldenrod + cursorFgColor: black + cursorBgColor: aqua + markColor: palegreen header: - fgColor: lightGray + fgColor: white bgColor: black - sorterColor: orange + sorterColor: aqua xray: - fgColor: blue + fgColor: aqua + bgColor: black + cursorColor: dodgerblue + cursorTextColor: black + graphicColor: cadetblue + charts: bgColor: black - cursorColor: aqua - graphicColor: darkgoldenrod - showIcons: false + dialBgColor: black + chartBgColor: black + defaultDialColors: + - palegreen + - orangered + defaultChartColors: + - palegreen + - orangered + resourceColors: + cpu: + - dodgerblue + - darkslateblue + mem: + - yellow + - goldenrod yaml: keyColor: steelblue - colonColor: white valueColor: papayawhip + colonColor: white + picker: + mainColor: white + focusColor: aqua + shortcutColor: aqua logs: - fgColor: white + fgColor: lightskyblue bgColor: black indicator: fgColor: dodgerblue bgColor: black toggleOnColor: limegreen - toggleOffColor: steelblue \ No newline at end of file + toggleOffColor: gray + dialog: + fgColor: cadetblue + bgColor: black + buttonFgColor: black + buttonBgColor: darkslateblue + buttonFocusFgColor: black + buttonFocusBgColor: dodgerblue + labelFgColor: white + fieldFgColor: white \ No newline at end of file diff --git a/internal/dao/container.go b/internal/dao/container.go index df8cd76627..f90d74c908 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -38,7 +38,7 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error cmx client.ContainersMetrics err error ) - if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok { + if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx { cmx, _ = client.DialMetrics(c.Client()).FetchContainersMetrics(ctx, fqn) } diff --git a/internal/dao/node.go b/internal/dao/node.go index 4f8058f4d5..db0c8fffef 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -139,7 +139,7 @@ func (n *Node) Get(ctx context.Context, path string) (runtime.Object, error) { } var nmx *mv1beta1.NodeMetrics - if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok { + if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx { nmx, _ = client.DialMetrics(n.Client()).FetchNodeMetrics(ctx, path) } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index d4404f5f0b..4c11879f40 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -70,7 +70,7 @@ func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) { } var pmx *mv1beta1.PodMetrics - if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); withMx || !ok { + if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx { pmx, _ = client.DialMetrics(p.Client()).FetchPodMetrics(ctx, path) } @@ -95,7 +95,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { } var pmx client.PodsMetricsMap - if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); false && ok && withMx { + if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx { pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetricsMap(ctx, ns) } sel, _ := ctx.Value(internal.KeyLabels).(string) diff --git a/internal/render/pod.go b/internal/render/pod.go index fb7ab88d57..4433ce1954 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -101,8 +101,8 @@ func (Pod) Header(ns string) Header { HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "IP", Wide: true}, - HeaderColumn{Name: "NODE", Wide: true}, + HeaderColumn{Name: "IP"}, + HeaderColumn{Name: "NODE"}, HeaderColumn{Name: "NOMINATED NODE", Wide: true}, HeaderColumn{Name: "READINESS GATES", Wide: true}, HeaderColumn{Name: "QOS", Wide: true}, diff --git a/internal/ui/config.go b/internal/ui/config.go index d6f3bc7698..6ca8a4951a 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -148,8 +148,7 @@ func (c *Configurator) RefreshStyles(context string) { var skin string if c.Config != nil { skin = c.Config.K9s.UI.Skin - ct, err := c.Config.K9s.ActiveContext() - if err != nil { + if ct, err := c.Config.K9s.ActiveContext(); err != nil { log.Warn().Msgf("No active context found. Using default skin") } else if ct.Skin != "" { skin = ct.Skin diff --git a/internal/ui/dialog/prompt.go b/internal/ui/dialog/prompt.go index 622ae4a991..8232d2d215 100644 --- a/internal/ui/dialog/prompt.go +++ b/internal/ui/dialog/prompt.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/dialog/prompt_test.go b/internal/ui/dialog/prompt_test.go index 7d4ade05fa..a5a7c0229a 100644 --- a/internal/ui/dialog/prompt_test.go +++ b/internal/ui/dialog/prompt_test.go @@ -1,14 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( "context" + "testing" + "time" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/stretchr/testify/assert" - "testing" - "time" ) func TestShowPrompt(t *testing.T) { diff --git a/internal/ui/table.go b/internal/ui/table.go index d2f2f7adb0..acf4d866ba 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -22,6 +22,8 @@ import ( "golang.org/x/text/language" ) +const maxTruncate = 50 + type ( // ColorerFunc represents a row colorer. ColorerFunc func(ns string, evt render.RowEvent) tcell.Color @@ -469,9 +471,9 @@ func (t *Table) styleTitle() string { buff := t.cmdBuff.GetText() if IsLabelSelector(buff) { - buff = TrimLabelSelector(buff) + buff = truncate(TrimLabelSelector(buff), maxTruncate) } else if l := t.GetModel().GetLabelFilter(); l != "" { - buff = l + buff = truncate(l, maxTruncate) } if buff == "" { diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 85a6e6896b..2f7a4f0e10 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -104,6 +104,14 @@ func TrimLabelSelector(s string) string { return s } +func truncate(s string, max int) string { + if len(s) < max { + return s + } + + return s[:max] + "..." +} + // SkinTitle decorates a title. func SkinTitle(fmat string, style config.Frame) string { bgColor := style.Title.BgColor diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 072e72970d..01fc7afe58 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -9,6 +9,29 @@ import ( "github.com/stretchr/testify/assert" ) +func TestTruncate(t *testing.T) { + uu := map[string]struct { + s, e string + }{ + "empty": {}, + "max": { + s: "/app.kubernetes.io/instance=prom,app.kubernetes.io/name=prometheus,app.kubernetes.io/component=server", + e: "/app.kubernetes.io/instance=prom,app.kubernetes.io...", + }, + "less": { + s: "app=fred,env=blee", + e: "app=fred,env=blee", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, truncate(u.s, 50)) + }) + } +} + func TestIsLabelSelector(t *testing.T) { uu := map[string]struct { s string diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go index 73afbffbb2..aaef7d61e7 100644 --- a/internal/view/cmd/args.go +++ b/internal/view/cmd/args.go @@ -38,7 +38,7 @@ func newArgs(p *Interpreter, aa []string) args { args[filterKey] = a[1:] case strings.Contains(a, labelFlag): - if ll := toLabels(a); len(ll) != 0 { + if ll := ToLabels(a); len(ll) != 0 { args[labelKey] = a } diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go index 5d53d63398..bc4978f3e5 100644 --- a/internal/view/cmd/helpers.go +++ b/internal/view/cmd/helpers.go @@ -11,7 +11,7 @@ import ( "github.com/rs/zerolog/log" ) -func toLabels(s string) map[string]string { +func ToLabels(s string) map[string]string { ll := strings.Split(s, ",") lbls := make(map[string]string, len(ll)) for _, l := range ll { diff --git a/internal/view/cmd/helpers_test.go b/internal/view/cmd/helpers_test.go index 9b9f5dc406..da4f821d00 100644 --- a/internal/view/cmd/helpers_test.go +++ b/internal/view/cmd/helpers_test.go @@ -53,7 +53,7 @@ func Test_toLabels(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.ll, toLabels(u.s)) + assert.Equal(t, u.ll, ToLabels(u.s)) }) } } diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go index fce8980ebd..4b142e513f 100644 --- a/internal/view/cmd/interpreter.go +++ b/internal/view/cmd/interpreter.go @@ -196,5 +196,5 @@ func (c *Interpreter) LabelsArg() (map[string]string, bool) { return nil, false } - return toLabels(ll), true + return ToLabels(ll), true } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 5f7f4365d8..6648b1daa0 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -18,6 +18,7 @@ import ( "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "github.com/rs/zerolog/log" @@ -116,13 +117,9 @@ func toLabelsStr(labels map[string]string) string { } func showPods(app *App, path, labelSel, fieldSel string) { - // !!BOZO!! needed?? - // if err := app.switchNS(client.BlankNamespace); err != nil { - // app.Flash().Err(err) - // return - // } v := NewPod(client.NewGVR("v1/pods")) - v.SetContextFn(podCtx(app, path, labelSel, fieldSel)) + v.SetContextFn(podCtx(app, path, fieldSel)) + v.SetLabelFilter(cmd.ToLabels(labelSel)) ns, _ := client.Namespaced(path) if err := app.Config.SetActiveNamespace(ns); err != nil { @@ -133,11 +130,9 @@ func showPods(app *App, path, labelSel, fieldSel string) { } } -func podCtx(app *App, path, labelSel, fieldSel string) ContextFunc { +func podCtx(app *App, path, fieldSel string) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) - ctx = context.WithValue(ctx, internal.KeyLabels, labelSel) - return context.WithValue(ctx, internal.KeyFields, fieldSel) } } diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index ee95f97fc3..35962b92e0 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -9,7 +9,6 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/tview" - "github.com/rs/zerolog/log" ) const spacer = " " @@ -155,7 +154,5 @@ func (l *LogIndicator) Refresh() { l.indicator = append(l.indicator, fmt.Sprintf(toggleOffFmt, "Wrap", "")...) } - log.Debug().Msgf("INDICATOR: %q", l.indicator) - _, _ = l.Write(l.indicator) } diff --git a/internal/view/log_indicator_test.go b/internal/view/log_indicator_test.go index 2316354d97..0c793f594d 100644 --- a/internal/view/log_indicator_test.go +++ b/internal/view/log_indicator_test.go @@ -18,10 +18,10 @@ func TestLogIndicatorRefresh(t *testing.T) { e string }{ "all-containers": { - view.NewLogIndicator(config.NewConfig(nil), defaults, true), "[::b]AllContainers:[steelblue::d]Off[-::] [::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[steelblue::d]Off[-::] [::b]Timestamps:[steelblue::d]Off[-::] [::b]Wrap:[steelblue::d]Off[-::]\n", + view.NewLogIndicator(config.NewConfig(nil), defaults, true), "[::b]AllContainers:[gray::d]Off[-::] [::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[gray::d]Off[-::] [::b]Timestamps:[gray::d]Off[-::] [::b]Wrap:[gray::d]Off[-::]\n", }, "plain": { - view.NewLogIndicator(config.NewConfig(nil), defaults, false), "[::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[steelblue::d]Off[-::] [::b]Timestamps:[steelblue::d]Off[-::] [::b]Wrap:[steelblue::d]Off[-::]\n", + view.NewLogIndicator(config.NewConfig(nil), defaults, false), "[::b]Autoscroll:[limegreen::b]On[-::] [::b]FullScreen:[gray::d]Off[-::] [::b]Timestamps:[gray::d]Off[-::] [::b]Wrap:[gray::d]Off[-::]\n", }, } diff --git a/internal/view/node.go b/internal/view/node.go index 636031953d..c5a14b932c 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -14,6 +14,7 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/plugins/carvel.yaml b/plugins/carvel.yaml index eea45ca714..d3bfeb7ab0 100644 --- a/plugins/carvel.yaml +++ b/plugins/carvel.yaml @@ -1,5 +1,5 @@ # $HOME/.k9s/plugin.yml -plugin: +plugins: kapp-inspect: shortCut: Shift-Z confirm: false diff --git a/plugins/crossplane.yaml b/plugins/crossplane.yaml index fb7015e123..14d7915e3f 100644 --- a/plugins/crossplane.yaml +++ b/plugins/crossplane.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # List all the resources managed by a Composite Resource kube-lineage: shortCut: Ctrl-X @@ -11,11 +11,11 @@ plugin: args: - -c - >- - kubectl lineage + kubectl lineage -d 6 --exclude-types Event,ProviderConfigUsage.aws.upbound.io,ProviderConfigUsage.kubernetes.crossplane.io --show-group - --context $CONTEXT - $RESOURCE_NAME - $NAME + --context $CONTEXT + $RESOURCE_NAME + $NAME | less -K diff --git a/plugins/debug-container.yaml b/plugins/debug-container.yaml index 2040cefcf3..0d1e387a5f 100644 --- a/plugins/debug-container.yaml +++ b/plugins/debug-container.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: #--- Create debug container for selected pod in current namespace # See https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/#ephemeral-container debug: diff --git a/plugins/dive.yaml b/plugins/dive.yaml index c92205ac32..090bc2c680 100644 --- a/plugins/dive.yaml +++ b/plugins/dive.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: dive: shortCut: d confirm: false diff --git a/plugins/flux.yaml b/plugins/flux.yaml index ca5c68e665..cbe887d3bb 100644 --- a/plugins/flux.yaml +++ b/plugins/flux.yaml @@ -2,7 +2,7 @@ # move selected line to chosen resource in K9s, then: # Shift-T (with confirmation) to toggle helm releases or kustomizations suspend and resume # Shift-R (no confirmation) to reconcile a git source or a helm release or a kustomization -plugin: +plugins: toggle-helmrelease: shortCut: Shift-T confirm: true @@ -19,7 +19,7 @@ plugin: flux $verb helmrelease --context $CONTEXT - -n $NAMESPACE $NAME + -n $NAMESPACE $NAME | less -K toggle-kustomization: shortCut: Shift-T @@ -34,10 +34,10 @@ plugin: - >- suspended=$(kubectl --context $CONTEXT get kustomizations -n $NAMESPACE $NAME -o=custom-columns=TYPE:.spec.suspend | tail -1); verb=$([ $suspended = "true" ] && echo "resume" || echo "suspend"); - flux - $verb kustomization - --context $CONTEXT - -n $NAMESPACE $NAME + flux + $verb kustomization + --context $CONTEXT + -n $NAMESPACE $NAME | less -K reconcile-git: shortCut: Shift-R @@ -53,7 +53,7 @@ plugin: flux reconcile source git --context $CONTEXT - -n $NAMESPACE $NAME + -n $NAMESPACE $NAME | less -K reconcile-hr: shortCut: Shift-R @@ -66,10 +66,10 @@ plugin: args: - -c - >- - flux - reconcile helmrelease - --context $CONTEXT - -n $NAMESPACE $NAME + flux + reconcile helmrelease + --context $CONTEXT + -n $NAMESPACE $NAME | less -K reconcile-helm-repo: shortCut: Shift-Z @@ -82,10 +82,10 @@ plugin: args: - -c - >- - flux - reconcile source helm - --context $CONTEXT - -n $NAMESPACE $NAME + flux + reconcile source helm + --context $CONTEXT + -n $NAMESPACE $NAME | less -K reconcile-oci-repo: shortCut: Shift-Z @@ -98,10 +98,10 @@ plugin: args: - -c - >- - flux - reconcile source oci - --context $CONTEXT - -n $NAMESPACE $NAME + flux + reconcile source oci + --context $CONTEXT + -n $NAMESPACE $NAME | less -K reconcile-ks: shortCut: Shift-R @@ -133,7 +133,7 @@ plugin: flux reconcile image repository --context $CONTEXT - -n $NAMESPACE $NAME + -n $NAMESPACE $NAME | less -K reconcile-iua: shortCut: Shift-R @@ -146,10 +146,10 @@ plugin: args: - -c - >- - flux - reconcile image update + flux + reconcile image update --context $CONTEXT - -n $NAMESPACE $NAME + -n $NAMESPACE $NAME | less -K trace: shortCut: Shift-A @@ -163,12 +163,12 @@ plugin: - -c - >- resource=$(echo $RESOURCE_NAME | sed -E 's/ies$/y/' | sed -E 's/ses$/se/' | sed -E 's/(s|es)$//g') - flux - trace - --context $CONTEXT - --kind $resource - --api-version $RESOURCE_GROUP/$RESOURCE_VERSION - --namespace $NAMESPACE $NAME + flux + trace + --context $CONTEXT + --kind $resource + --api-version $RESOURCE_GROUP/$RESOURCE_VERSION + --namespace $NAMESPACE $NAME | less -K # credits: https://github.com/fluxcd/flux2/discussions/2494 get-suspended-helmreleases: @@ -182,10 +182,10 @@ plugin: args: - -c - >- - kubectl get - --all-namespaces - helmreleases.helm.toolkit.fluxcd.io -o json - | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.namespace,.metadata.name,.spec.suspend] | @tsv' + kubectl get + --all-namespaces + helmreleases.helm.toolkit.fluxcd.io -o json + | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.namespace,.metadata.name,.spec.suspend] | @tsv' | less get-suspended-kustomizations: shortCut: Shift-S @@ -198,7 +198,7 @@ plugin: args: - -c - >- - kubectl get + kubectl get --all-namespaces kustomizations.kustomize.toolkit.fluxcd.io -o json | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.name,.spec.suspend] | @tsv' diff --git a/plugins/get-all.yaml b/plugins/get-all.yaml index d872dd0de2..58f65d71c3 100644 --- a/plugins/get-all.yaml +++ b/plugins/get-all.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: #get all resources in a namespace using the krew get-all plugin get-all-namespace: shortCut: g diff --git a/plugins/helm-default-values.yaml b/plugins/helm-default-values.yaml index ce587347b7..253d03d689 100644 --- a/plugins/helm-default-values.yaml +++ b/plugins/helm-default-values.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: helm-default-values: shortCut: Shift-V confirm: false diff --git a/plugins/helm-purge.yaml b/plugins/helm-purge.yaml index eb106c07ef..300053a7fa 100644 --- a/plugins/helm-purge.yaml +++ b/plugins/helm-purge.yaml @@ -1,5 +1,5 @@ # $HOME/.k9s/plugin.yml -plugin: +plugins: # Issues a helm delete --purge for the resource associated with the selected pod helm-purge: shortCut: Ctrl-P diff --git a/plugins/helm_values.yaml b/plugins/helm_values.yaml index 8efc0d0fba..97e70cf850 100644 --- a/plugins/helm_values.yaml +++ b/plugins/helm_values.yaml @@ -1,6 +1,6 @@ # View user-supplied values when the helm chart was created -plugin: +plugins: helm-values: shortCut: v confirm: false diff --git a/plugins/job_suspend.yaml b/plugins/job_suspend.yaml index d674dd2d2c..abee83bc52 100644 --- a/plugins/job_suspend.yaml +++ b/plugins/job_suspend.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # Suspends/Resumes a cronjob toggleCronjob: shortCut: Ctrl-S diff --git a/plugins/k3d_root_shell.yaml b/plugins/k3d_root_shell.yaml index 295c680833..79707b4b5a 100644 --- a/plugins/k3d_root_shell.yaml +++ b/plugins/k3d_root_shell.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # Opens a shell to k3d container as root k3d-root-shell: shortCut: Shift-S diff --git a/plugins/log_full.yaml b/plugins/log_full.yaml index 304a86d384..452c1dbc91 100644 --- a/plugins/log_full.yaml +++ b/plugins/log_full.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # See https://k9scli.io/topics/plugins/ raw-logs-follow: shortCut: Ctrl-L diff --git a/plugins/log_jq.yaml b/plugins/log_jq.yaml index f4c40e02f3..331427b92f 100644 --- a/plugins/log_jq.yaml +++ b/plugins/log_jq.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # Sends logs over to jq for processing. This leverages kubectl plugin kubectl-jq. jqlogs: shortCut: Ctrl-J diff --git a/plugins/log_stern.yaml b/plugins/log_stern.yaml index 9f7b9ed079..1e850b385d 100644 --- a/plugins/log_stern.yaml +++ b/plugins/log_stern.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # Leverage stern (https://github.com/stern/stern) to output logs. stern: shortCut: Ctrl-L diff --git a/plugins/rm-ns.yaml b/plugins/rm-ns.yaml index 14a2a251d8..88509dc98c 100644 --- a/plugins/rm-ns.yaml +++ b/plugins/rm-ns.yaml @@ -1,4 +1,4 @@ -plugin: +plugins: # remove finalizers from a stuck namespace rm-ns: shortCut: n diff --git a/plugins/watch_events.yaml b/plugins/watch_events.yaml index ffd8b2fc59..db96c6eb3f 100644 --- a/plugins/watch_events.yaml +++ b/plugins/watch_events.yaml @@ -2,7 +2,7 @@ # requires linux "watch" command # change '-n' to adjust refresh time in seconds -plugin: +plugins: watch-events: shortCut: Shift-E confirm: false diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f6da346138..b56bd21968 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -31,7 +31,7 @@ apps: parts: build: - plugin: go + plugins: go source: https://github.com/derailed/k9s.git source-tag: $SNAPCRAFT_PROJECT_VERSION override-build: | From defad7a1741dcdc61dbc463253667de8432e8fe0 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Mon, 25 Dec 2023 13:15:27 +0800 Subject: [PATCH 039/169] fix cmdline flags not working (#2373) --- cmd/root.go | 2 +- internal/config/k9s.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7a3bfc17da..703ddf289f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -112,8 +112,8 @@ func loadConfiguration() *config.Config { k9sCfg := config.NewConfig(k8sCfg) if err := k9sCfg.Load(config.AppConfigFile); err != nil { log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") - k9sCfg.K9s.Generate(k9sFlags) } + k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("refine failed") } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 2fae52b9d1..59ce473f9c 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -86,7 +86,7 @@ func (k *K9s) Refine(k1 *K9s) { k.Thresholds = k1.Thresholds } -func (k *K9s) Generate(k9sFlags *Flags) { +func (k *K9s) Override(k9sFlags *Flags) { if *k9sFlags.RefreshRate != DefaultRefreshRate { k.OverrideRefreshRate(*k9sFlags.RefreshRate) } From e113f4b7c5af6155893f0bb07c45de18297a95fd Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Mon, 25 Dec 2023 13:48:59 +0800 Subject: [PATCH 040/169] get node filtering params from matching context values (#2375) --- internal/dao/pod.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 4c11879f40..f426f4ef2b 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -98,7 +98,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { if withMx, ok := ctx.Value(internal.KeyWithMetrics).(bool); ok && withMx { pmx, _ = client.DialMetrics(p.Client()).FetchPodsMetricsMap(ctx, ns) } - sel, _ := ctx.Value(internal.KeyLabels).(string) + sel, _ := ctx.Value(internal.KeyFields).(string) fsel, err := labels.ConvertSelectorToLabelsMap(sel) if err != nil { return nil, err From 1efadc81a62c451a399b092b829f5792c586d759 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sun, 24 Dec 2023 22:57:48 -0700 Subject: [PATCH 041/169] K9s/release v0.30.2 (#2376) * fix #2370 * Fix #2362 * K9s V0.30.2 release docs --- Makefile | 2 +- README.md | 2 +- change_logs/release_v0.30.0.md | 10 ++-- change_logs/release_v0.30.1.md | 6 +-- change_logs/release_v0.30.2.md | 96 ++++++++++++++++++++++++++++++++++ internal/config/config_test.go | 4 +- internal/config/scans.go | 18 +++---- 7 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 change_logs/release_v0.30.2.md diff --git a/Makefile b/Makefile index 29f751e9e8..e62a64a2e7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.1 +VERSION ?= v0.30.2 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index a94cb862ae..1b6b920df4 100644 --- a/README.md +++ b/README.md @@ -898,7 +898,7 @@ k9s: memory: 100Mi imageScans: enable: false - blackList: + exclusions: namespaces: [] labels: {} logger: diff --git a/change_logs/release_v0.30.0.md b/change_logs/release_v0.30.0.md index 9f3be44dc5..54d31df2c8 100644 --- a/change_logs/release_v0.30.0.md +++ b/change_logs/release_v0.30.0.md @@ -116,8 +116,8 @@ k9s: # ImageScan config changed from v0.29.0! imageScans: enable: false - # Now figures exclusions ie blacklist namespaces or specific workload labels - blackList: + # Now figures exclusions ie excludes certain namespaces or specific workload labels + exclusions: # Exclude the following namespaces for image vulscans! namespaces: - kube-system @@ -236,13 +236,13 @@ This is a feature reported by many of you and its (finally!) here. As of this dr --- -# The Black List... +# Vulnerability Scan Exclusions... As it seems customary with all k9s new features, folks want to turn them off ;( The `Vulscan` feature did not get out unscaped ;( As it was rightfully so pointed out, you may want to opted out scans for images that you do not control. Tho I think it might be a good idea to run wide open once in a while to see if your cluster has any holes?? -For this reason, we've opted to intro a blacklist section under the image scan configuration to exclude certain images from the scans. +For this reason, we've opted to intro an exclusion section under the image scan configuration to exclude certain images from the scans. Here is a sample configuration: @@ -258,7 +258,7 @@ k9s: noIcons: false imageScans: enable: true - blackList: + exclusions: # Skip scans on these namespaces namespaces: - ns-1 diff --git a/change_logs/release_v0.30.1.md b/change_logs/release_v0.30.1.md index a983a8d424..362929be30 100644 --- a/change_logs/release_v0.30.1.md +++ b/change_logs/release_v0.30.1.md @@ -19,7 +19,7 @@ On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_inv ## 🎄 Maintenance Release! 🎄 -🎵 `On The twentyfouth day of Christmas my true love gave to me... Bugs!!` 🎵 +🎵 `On The eleventh day of Christmas my true love gave to me... Bugs!!` 🎵 Got to love the aftermath... Thank you all for pitch'in in and help flesh out bugs!! The gift that keeps on... giving? @@ -41,8 +41,8 @@ Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgj * [#2368](https://github.com/derailed/k9s/issues/2368) Pod CPU and MEM columns are empty in 0.30.0 * [#2367](https://github.com/derailed/k9s/issues/2367) k9s 0.30.0 issue loading plugins * [#2366](https://github.com/derailed/k9s/issues/2366) List pods of deployment is now impossible -* [#2264](https://github.com/derailed/k9s/issues/2264) k9s 0.30.0 fields and values missed in action in the "namespace view" -* [#2263](https://github.com/derailed/k9s/issues/2263) Default 0.30.0 default skin on macOS is no good +* [#2364](https://github.com/derailed/k9s/issues/2364) k9s 0.30.0 fields and values missed in action in the "namespace view" +* [#2363](https://github.com/derailed/k9s/issues/2363) Default 0.30.0 default skin on macOS is no good --- diff --git a/change_logs/release_v0.30.2.md b/change_logs/release_v0.30.2.md new file mode 100644 index 0000000000..1ab582e73a --- /dev/null +++ b/change_logs/release_v0.30.2.md @@ -0,0 +1,96 @@ + + +# Release v0.30.2 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +🎵 `On The eleventh day of Christmas my true love gave to me... More Bugs!!` 🎵 + +Thank you all for pitching in and help flesh out bugs!! + +--- + +## [!!FEATURE NAME CHANGED!!] Vulnerability Scan Exclusions... + +As it seems customary with all k9s new features, folks want to turn them off ;( +The `Vulscan` feature did not get out unscaped ;( +As it was rightfully so pointed out, you may want to opted out scans for images that you do not control. +Tho I think it might be a good idea to run wide open once in a while to see if your cluster has any holes?? +For this reason, we've opted to intro an exclusion section under the image scan configuration to exclude certain images from the scans. + +Here is a sample configuration: + +```yaml +k9s: + liveViewAutoRefresh: false + refreshRate: 2 + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + imageScans: + enable: true + # MOTE!! Field Name changed!! + exclusions: + # Skip scans on these namespaces + namespaces: + - ns-1 + - ns-2 + # Skip scans for pods matching these labels + labels: + - app: + - fred + - blee + - duh + - env: + - dev +``` + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2374](https://github.com/derailed/k9s/issues/2374) The headless parameter does not function properly (v0.30.1) +* [#2372](https://github.com/derailed/k9s/issues/2372) Unable to set default resource to load (v0.30.1) +* [#2371](https://github.com/derailed/k9s/issues/2371) --write cli option does not work (0.30.X) +* [#2370](https://github.com/derailed/k9s/issues/2370) Wrong list of pods on node (0.30.X) +* [#2362](https://github.com/derailed/k9s/issues/2362) blackList: Use inclusive language alternatives + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2375](https://github.com/derailed/k9s/pull/2375) get node filtering params from matching context values +* [#2373](https://github.com/derailed/k9s/pull/2373) fix command line flags not working + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e29d9f7add..280fac4a0a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -170,7 +170,7 @@ var expectedConfig = `k9s: memory: 100Mi imageScans: enable: false - blackList: + exclusions: namespaces: [] labels: {} logger: @@ -212,7 +212,7 @@ var resetConfig = `k9s: memory: 100Mi imageScans: enable: false - blackList: + exclusions: namespaces: [] labels: {} logger: diff --git a/internal/config/scans.go b/internal/config/scans.go index 19e970d93f..5731c58eca 100644 --- a/internal/config/scans.go +++ b/internal/config/scans.go @@ -21,19 +21,19 @@ func (l Labels) exclude(k, val string) bool { return false } -// Blacklist tracks vul scan exclusions. -type BlackList struct { +// ScanExcludes tracks vul scan exclusions. +type ScanExcludes struct { Namespaces []string `yaml:"namespaces"` Labels Labels `yaml:"labels"` } -func newBlackList() BlackList { - return BlackList{ +func newScanExcludes() ScanExcludes { + return ScanExcludes{ Labels: make(Labels), } } -func (b BlackList) exclude(ns string, ll map[string]string) bool { +func (b ScanExcludes) exclude(ns string, ll map[string]string) bool { for _, nss := range b.Namespaces { if nss == ns { return true @@ -50,14 +50,14 @@ func (b BlackList) exclude(ns string, ll map[string]string) bool { // ImageScans tracks vul scans options. type ImageScans struct { - Enable bool `yaml:"enable"` - BlackList BlackList `yaml:"blackList"` + Enable bool `yaml:"enable"` + Exclusions ScanExcludes `yaml:"exclusions"` } // NewImageScans returns a new instance. func NewImageScans() *ImageScans { return &ImageScans{ - BlackList: newBlackList(), + Exclusions: newScanExcludes(), } } @@ -67,5 +67,5 @@ func (i *ImageScans) ShouldExclude(ns string, ll map[string]string) bool { return false } - return i.BlackList.exclude(ns, ll) + return i.Exclusions.exclude(ns, ll) } From 463be69a9c6ec78d30a8daaee92313c5070de0a4 Mon Sep 17 00:00:00 2001 From: Emanuele Ciurleo Date: Mon, 25 Dec 2023 16:03:29 -0300 Subject: [PATCH 042/169] Update build v0.30.2 (#2380) --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b56bd21968..3be205ee8e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.29.1' +version: 'v0.30.2' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 26d1585699fb1faed828161c13265200f83486d8 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Mon, 25 Dec 2023 12:06:23 -0700 Subject: [PATCH 043/169] K9s/release v0.30.3 (#2381) * fix #2377 * fix #2379 * cleaning up * Release docs --- Makefile | 2 +- change_logs/release_v0.30.3.md | 45 +++++ internal/config/data/dir.go | 11 +- internal/config/data/dir_test.go | 6 + internal/config/data/helpers.go | 13 ++ .../config/data/testdata/configs/aws_ct.yaml | 12 ++ internal/config/files.go | 40 ++-- internal/config/helpers.go | 5 - internal/config/k9s.go | 19 +- internal/config/mock/test_helpers.go | 4 + internal/render/pod.go | 28 +-- internal/render/pod_int_test.go | 177 ++++++++++++++++++ internal/render/pod_test.go | 120 ------------ internal/ui/table_helper.go | 10 +- internal/ui/table_helper_test.go | 12 +- internal/view/benchmark.go | 5 +- internal/view/cmd/helpers.go | 3 - internal/view/details.go | 2 +- internal/view/live_view.go | 2 +- internal/view/log.go | 5 +- internal/view/log_test.go | 3 +- internal/view/logger.go | 2 +- internal/view/screen_dump.go | 3 +- internal/view/table.go | 2 +- internal/view/table_helper.go | 12 +- internal/view/table_int_test.go | 3 +- internal/view/yaml.go | 16 +- snap/snapcraft.yaml | 2 +- 28 files changed, 346 insertions(+), 218 deletions(-) create mode 100644 change_logs/release_v0.30.3.md create mode 100644 internal/config/data/testdata/configs/aws_ct.yaml create mode 100644 internal/render/pod_int_test.go diff --git a/Makefile b/Makefile index e62a64a2e7..298dfa8dfd 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.2 +VERSION ?= v0.30.3 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.3.md b/change_logs/release_v0.30.3.md new file mode 100644 index 0000000000..f40bcd95b6 --- /dev/null +++ b/change_logs/release_v0.30.3.md @@ -0,0 +1,45 @@ + + +# Release v0.30.3 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +🎵 `On The twelfth day of Christmas my true love gave to me... More Bugs!!` 🎵 + +Thank you all for pitching in and help flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2379](https://github.com/derailed/k9s/issues/2379) Filtering with equal sign (=) does not work in 0.30.X +* [#2378](https://github.com/derailed/k9s/issues/2378) Logs directory not created in the k9s config/home dir 0.30.1 +* [#2377](https://github.com/derailed/k9s/issues/2377) Opening AWS EKS contexts create two directories per cluster 0.30.1 + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index 3a577eceda..6c95775911 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -34,9 +34,14 @@ func (d Dir) Load(n string, ct *api.Context) (*Config, error) { } var ( - path = filepath.Join(d.root, ct.Cluster, n, MainConfigFile) - cfg *Config - err error + path = filepath.Join( + d.root, + SanitizeFileName(ct.Cluster), + SanitizeFileName(n), + MainConfigFile, + ) + cfg *Config + err error ) if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 { log.Debug().Msgf("Context config not found! Generating... %q", path) diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go index fc4c3d3fde..56d0bc67ce 100644 --- a/internal/config/data/dir_test.go +++ b/internal/config/data/dir_test.go @@ -50,6 +50,12 @@ func TestDirLoad(t *testing.T) { flags: makeFlags("cl-test", "ct-test-1"), cfg: mustLoadConfig("testdata/configs/def_ct.yaml"), }, + + "non-sanitized-path": { + dir: "/tmp/data/k9s", + flags: makeFlags("arn:aws:eks:eu-central-1:xxx:cluster/fred-blee", "fred-blee"), + cfg: mustLoadConfig("testdata/configs/aws_ct.yaml"), + }, } for k := range uu { diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go index 98887bf6dd..59f043870c 100644 --- a/internal/config/data/helpers.go +++ b/internal/config/data/helpers.go @@ -6,8 +6,21 @@ package data import ( "os" "path/filepath" + "regexp" ) +var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) + +// SanitizeContextSubpath ensure cluster/context produces a valid path. +func SanitizeContextSubpath(cluster, context string) string { + return filepath.Join(SanitizeFileName(cluster), SanitizeFileName(context)) +} + +// SanitizeFileName ensure file spec is valid. +func SanitizeFileName(name string) string { + return invalidPathCharsRX.ReplaceAllString(name, "-") +} + // InList check if string is in a collection of strings. func InList(ll []string, n string) bool { for _, l := range ll { diff --git a/internal/config/data/testdata/configs/aws_ct.yaml b/internal/config/data/testdata/configs/aws_ct.yaml new file mode 100644 index 0000000000..0f81b93b7f --- /dev/null +++ b/internal/config/data/testdata/configs/aws_ct.yaml @@ -0,0 +1,12 @@ +k9s: + cluster: arn:aws:eks:eu-central-1:xxx:cluster/fred-blee + namespace: + active: default + lockFavorites: false + favorites: + - default + view: + active: po + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/files.go b/internal/config/files.go index 05c40e2796..0d8cee113d 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -8,7 +8,6 @@ import ( "os" "os/user" "path/filepath" - "regexp" "github.com/derailed/k9s/internal/config/data" @@ -81,19 +80,13 @@ var ( // InitLogsLoc initializes K9s logs location. func InitLogLoc() error { - if hasK9sConfigEnv() { - tmpDir, err := userTmpDir() - if err != nil { - return err - } - AppLogFile = filepath.Join(tmpDir, K9sLogsFile) - return nil + tmpDir, err := userTmpDir() + if err != nil { + return err } + AppLogFile = filepath.Join(tmpDir, K9sLogsFile) - var err error - AppLogFile, err = xdg.StateFile(filepath.Join(AppName, K9sLogsFile)) - - return err + return nil } // InitLocs initializes k9s artifacts locations. @@ -182,31 +175,24 @@ func initXDGLocs() error { return nil } -var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) - -// SanitizeFileName ensure file spec is valid. -func SanitizeFileName(name string) string { - return invalidPathCharsRX.ReplaceAllString(name, "-") -} - // AppContextDir generates a valid context config dir. func AppContextDir(cluster, context string) string { - return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context)) + return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context)) } // AppContextAliasesFile generates a valid context specific aliases file path. func AppContextAliasesFile(cluster, context string) string { - return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "aliases.yaml") + return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "aliases.yaml") } // AppContextPluginsFile generates a valid context specific plugins file path. func AppContextPluginsFile(cluster, context string) string { - return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "plugins.yaml") + return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "plugins.yaml") } // AppContextHotkeysFile generates a valid context specific hotkeys file path. func AppContextHotkeysFile(cluster, context string) string { - return filepath.Join(AppContextsDir, sanContextSubpath(cluster, context), "hotkeys.yaml") + return filepath.Join(AppContextsDir, data.SanitizeContextSubpath(cluster, context), "hotkeys.yaml") } // AppContextConfig generates a valid context config file path. @@ -216,14 +202,14 @@ func AppContextConfig(cluster, context string) string { // DumpsDir generates a valid context dump directory. func DumpsDir(cluster, context string) (string, error) { - dir := filepath.Join(AppDumpsDir, sanContextSubpath(cluster, context)) + dir := filepath.Join(AppDumpsDir, data.SanitizeContextSubpath(cluster, context)) return dir, data.EnsureDirPath(dir, data.DefaultDirMod) } // EnsureBenchmarksDir generates a valid benchmark results directory. func EnsureBenchmarksDir(cluster, context string) (string, error) { - dir := filepath.Join(AppBenchmarksDir, sanContextSubpath(cluster, context)) + dir := filepath.Join(AppBenchmarksDir, data.SanitizeContextSubpath(cluster, context)) return dir, data.EnsureDirPath(dir, data.DefaultDirMod) } @@ -274,10 +260,6 @@ func SkinFileFromName(n string) string { // Helpers... -func sanContextSubpath(cluster, context string) string { - return filepath.Join(SanitizeFileName(cluster), SanitizeFileName(context)) -} - func hasK9sConfigEnv() bool { return os.Getenv(K9sConfigDir) != "" } diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 4d2d239784..0e8d0c2d1d 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -11,11 +11,6 @@ import ( v1 "k8s.io/api/core/v1" ) -// SanitizeFilename sanitizes the dump filename. -func SanitizeFilename(name string) string { - return invalidPathCharsRX.ReplaceAllString(name, "-") -} - // InNSList check if ns is in an ns collection. func InNSList(nn []interface{}, ns string) bool { ss := make([]string, len(nn)) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 59ce473f9c..18e1b2daa0 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -56,6 +56,7 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { } } +// Save saves the k9s config to dis. func (k *K9s) Save() error { if k.activeConfig != nil { path := filepath.Join( @@ -70,6 +71,7 @@ func (k *K9s) Save() error { return nil } +// Refine merges k9s configs. func (k *K9s) Refine(k1 *K9s) { k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh k.ScreenDumpDir = k1.ScreenDumpDir @@ -86,6 +88,7 @@ func (k *K9s) Refine(k1 *K9s) { k.Thresholds = k1.Thresholds } +// Override overrides k9s config from cli args. func (k *K9s) Override(k9sFlags *Flags) { if *k9sFlags.RefreshRate != DefaultRefreshRate { k.OverrideRefreshRate(*k9sFlags.RefreshRate) @@ -105,6 +108,7 @@ func (k *K9s) OverrideScreenDumpDir(dir string) { k.manualScreenDumpDir = &dir } +// GetScreenDumpDir fetch screen dumps dir. func (k *K9s) GetScreenDumpDir() string { screenDumpDir := k.ScreenDumpDir if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" { @@ -117,21 +121,29 @@ func (k *K9s) GetScreenDumpDir() string { return screenDumpDir } +// Reset resets configuration and context. func (k *K9s) Reset() { k.activeConfig, k.activeContextName = nil, "" } +// ActiveScreenDumpsDir fetch context specific screen dumps dir. +func (k *K9s) ActiveScreenDumpsDir() string { + return filepath.Join(k.GetScreenDumpDir(), k.ActiveContextDir()) +} + +// ActiveContextDir fetch current cluster/context path. func (k *K9s) ActiveContextDir() string { if k.activeConfig == nil { return "na" } - return filepath.Join( - SanitizeFileName(k.activeConfig.Context.ClusterName), - SanitizeFileName(k.ActiveContextName()), + return data.SanitizeContextSubpath( + k.activeConfig.Context.ClusterName, + k.ActiveContextName(), ) } +// ActiveContextNamespace fetch the context active ns. func (k *K9s) ActiveContextNamespace() (string, error) { if k.activeConfig != nil { return k.activeConfig.Context.Namespace.Active, nil @@ -140,6 +152,7 @@ func (k *K9s) ActiveContextNamespace() (string, error) { return "", errors.New("context config is not set") } +// ActiveContextName returns the active context name. func (k *K9s) ActiveContextName() string { return k.activeContextName } diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index b5378bf2d5..2fa94be082 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -69,6 +69,10 @@ func NewMockKubeSettings(f *genericclioptions.ConfigFlags) mockKubeSettings { Cluster: *f.ClusterName, Namespace: client.DefaultNamespace, }, + "fred-blee": { + Cluster: "arn:aws:eks:eu-central-1:xxx:cluster/fred-blee", + Namespace: client.DefaultNamespace, + }, }, } } diff --git a/internal/render/pod.go b/internal/render/pod.go index 4433ce1954..bf99be0123 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -142,7 +142,11 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { cs := po.Status.ContainerStatuses cr, _, rc := p.Statuses(cs) - c, r := p.gatherPodMX(&po, pwm.MX) + var ccmx []mv1beta1.ContainerMetrics + if pwm.MX != nil { + ccmx = pwm.MX.Containers + } + c, r := gatherCoMX(po.Spec.Containers, ccmx) phase := p.Phase(&po) row.ID = client.MetaFQN(po.ObjectMeta) row.Fields = Fields{ @@ -232,22 +236,20 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object { return p } -func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, r metric) { - rcpu, rmem := podRequests(pod.Spec.Containers) +func gatherCoMX(cc []v1.Container, ccmx []mv1beta1.ContainerMetrics) (c, r metric) { + rcpu, rmem := cosRequests(cc) r.cpu, r.mem = rcpu.MilliValue(), rmem.Value() - lcpu, lmem := podLimits(pod.Spec.Containers) + lcpu, lmem := cosLimits(cc) r.lcpu, r.lmem = lcpu.MilliValue(), lmem.Value() - if mx != nil { - ccpu, cmem := currentRes(mx) - c.cpu, c.mem = ccpu.MilliValue(), cmem.Value() - } + ccpu, cmem := currentRes(ccmx) + c.cpu, c.mem = ccpu.MilliValue(), cmem.Value() return } -func podLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { +func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) for _, c := range cc { limits := c.Resources.Limits @@ -264,7 +266,7 @@ func podLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { return *cpu, *mem } -func podRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) { +func cosRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) for _, c := range cc { co := c @@ -280,12 +282,12 @@ func podRequests(cc []v1.Container) (resource.Quantity, resource.Quantity) { return *cpu, *mem } -func currentRes(mx *mv1beta1.PodMetrics) (resource.Quantity, resource.Quantity) { +func currentRes(ccmx []mv1beta1.ContainerMetrics) (resource.Quantity, resource.Quantity) { cpu, mem := new(resource.Quantity), new(resource.Quantity) - if mx == nil { + if ccmx == nil { return *cpu, *mem } - for _, co := range mx.Containers { + for _, co := range ccmx { c, m := co.Usage.Cpu(), co.Usage.Memory() cpu.Add(*c) mem.Add(*m) diff --git a/internal/render/pod_int_test.go b/internal/render/pod_int_test.go new file mode 100644 index 0000000000..11b07cd587 --- /dev/null +++ b/internal/render/pod_int_test.go @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "testing" + + "github.com/derailed/k9s/internal/client" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + res "k8s.io/apimachinery/pkg/api/resource" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +) + +func Test_gatherPodMx(t *testing.T) { + uu := map[string]struct { + cc []v1.Container + mx []mv1beta1.ContainerMetrics + c, r metric + perc string + }{ + "single": { + cc: []v1.Container{ + makeContainer("c1", false, "10m", "1Mi", "20m", "2Mi"), + }, + mx: []mv1beta1.ContainerMetrics{ + makeCoMX("c1", "1m", "22Mi"), + }, + c: metric{ + cpu: 1, + mem: 22 * client.MegaByte, + }, + r: metric{ + cpu: 10, + mem: 1 * client.MegaByte, + lcpu: 20, + lmem: 2 * client.MegaByte, + }, + perc: "10", + }, + "multi": { + cc: []v1.Container{ + makeContainer("c1", false, "11m", "22Mi", "111m", "44Mi"), + makeContainer("c2", false, "93m", "1402Mi", "0m", "2804Mi"), + makeContainer("c3", false, "11m", "34Mi", "0m", "69Mi"), + }, + r: metric{ + cpu: 11 + 93 + 11, + mem: (22 + 1402 + 34) * client.MegaByte, + lcpu: 111 + 0 + 0, + lmem: (44 + 2804 + 69) * client.MegaByte, + }, + mx: []mv1beta1.ContainerMetrics{ + makeCoMX("c1", "1m", "22Mi"), + makeCoMX("c2", "51m", "1275Mi"), + makeCoMX("c3", "1m", "27Mi"), + }, + c: metric{ + cpu: 1 + 51 + 1, + mem: (22 + 1275 + 27) * client.MegaByte, + }, + perc: "46", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c, r := gatherCoMX(u.cc, u.mx) + assert.Equal(t, u.c.cpu, c.cpu) + assert.Equal(t, u.c.mem, c.mem) + assert.Equal(t, u.c.lcpu, c.lcpu) + assert.Equal(t, u.c.lmem, c.lmem) + + assert.Equal(t, u.r.cpu, r.cpu) + assert.Equal(t, u.r.mem, r.mem) + assert.Equal(t, u.r.lcpu, r.lcpu) + assert.Equal(t, u.r.lmem, r.lmem) + + assert.Equal(t, u.perc, client.ToPercentageStr(c.cpu, r.cpu)) + }) + } +} + +func Test_podLimits(t *testing.T) { + uu := map[string]struct { + cc []v1.Container + l v1.ResourceList + }{ + "plain": { + cc: []v1.Container{ + makeContainer("c1", false, "10m", "1Mi", "20m", "2Mi"), + }, + l: makeRes("20m", "2Mi"), + }, + "multi-co": { + cc: []v1.Container{ + makeContainer("c1", false, "10m", "1Mi", "20m", "2Mi"), + makeContainer("c2", false, "10m", "1Mi", "40m", "4Mi"), + }, + l: makeRes("60m", "6Mi"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c, m := cosLimits(u.cc) + assert.True(t, c.Equal(*u.l.Cpu())) + assert.True(t, m.Equal(*u.l.Memory())) + }) + } +} + +func Test_podRequests(t *testing.T) { + uu := map[string]struct { + cc []v1.Container + l v1.ResourceList + }{ + "plain": { + cc: []v1.Container{ + makeContainer("c1", false, "10m", "1Mi", "20m", "2Mi"), + }, + l: makeRes("10m", "1Mi"), + }, + "multi-co": { + cc: []v1.Container{ + makeContainer("c1", false, "10m", "1Mi", "20m", "2Mi"), + makeContainer("c2", false, "10m", "1Mi", "40m", "4Mi"), + }, + l: makeRes("20m", "2Mi"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c, m := cosRequests(u.cc) + assert.True(t, c.Equal(*u.l.Cpu())) + assert.True(t, m.Equal(*u.l.Memory())) + }) + } +} + +// Helpers... + +func makeContainer(n string, init bool, rc, rm, lc, lm string) v1.Container { + var res v1.ResourceRequirements + if init { + res = v1.ResourceRequirements{} + } else { + res = v1.ResourceRequirements{ + Requests: makeRes(rc, rm), + Limits: makeRes(lc, lm), + } + } + + return v1.Container{Name: n, Resources: res} +} + +func makeRes(c, m string) v1.ResourceList { + cpu, _ := res.ParseQuantity(c) + mem, _ := res.ParseQuantity(m) + + return v1.ResourceList{ + v1.ResourceCPU: cpu, + v1.ResourceMemory: mem, + } +} + +func makeCoMX(n string, c, m string) mv1beta1.ContainerMetrics { + return mv1beta1.ContainerMetrics{ + Name: n, + Usage: makeRes(c, m), + } +} diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index 32a89b1b8a..f274f20621 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -281,123 +281,3 @@ func makeRes(c, m string) v1.ResourceList { v1.ResourceMemory: mem, } } - -// apiVersion: v1 -// kind: Pod -// metadata: -// creationTimestamp: "2023-11-11T17:01:40Z" -// finalizers: -// - batch.kubernetes.io/job-tracking -// generateName: hello-28328646- -// labels: -// batch.kubernetes.io/controller-uid: 35cf5552-7180-48c1-b7b2-8b6e630a7860 -// batch.kubernetes.io/job-name: hello-28328646 -// controller-uid: 35cf5552-7180-48c1-b7b2-8b6e630a7860 -// job-name: hello-28328646 -// name: hello-28328646-h9fnh -// namespace: fred -// ownerReferences: -// - apiVersion: batch/v1 -// blockOwnerDeletion: true -// controller: true -// kind: Job -// name: hello-28328646 -// uid: 35cf5552-7180-48c1-b7b2-8b6e630a7860 -// resourceVersion: "381637" -// uid: ea77c360-6375-459b-8b30-2ac0c59404cd -// spec: -// containers: -// - args: -// - /bin/bash -// - -c -// - for i in {1..5}; do echo "hello";sleep 1; done -// image: blang/busybox-bash -// imagePullPolicy: Always -// name: c1 -// resources: {} -// terminationMessagePath: /dev/termination-log -// terminationMessagePolicy: File -// volumeMounts: -// - mountPath: /var/run/secrets/kubernetes.io/serviceaccount -// name: kube-api-access-7sztm -// readOnly: true -// dnsPolicy: ClusterFirst -// enableServiceLinks: true -// nodeName: kind-worker -// preemptionPolicy: PreemptLowerPriority -// priority: 0 -// restartPolicy: OnFailure -// schedulerName: default-scheduler -// securityContext: {} -// serviceAccount: default -// serviceAccountName: default -// terminationGracePeriodSeconds: 30 -// tolerations: -// - effect: NoExecute -// key: node.kubernetes.io/not-ready -// operator: Exists -// tolerationSeconds: 300 -// - effect: NoExecute -// key: node.kubernetes.io/unreachable -// operator: Exists -// tolerationSeconds: 300 -// volumes: -// - name: kube-api-access-7sztm -// projected: -// defaultMode: 420 -// sources: -// - serviceAccountToken: -// expirationSeconds: 3607 -// path: token -// - configMap: -// items: -// - key: ca.crt -// path: ca.crt -// name: kube-root-ca.crt -// - downwardAPI: -// items: -// - fieldRef: -// apiVersion: v1 -// fieldPath: metadata.namespace -// path: namespace -// status: -// conditions: -// - lastProbeTime: null -// lastTransitionTime: "2023-11-11T17:01:40Z" -// status: "True" -// type: Initialized -// - lastProbeTime: null -// lastTransitionTime: "2023-11-11T17:01:40Z" -// message: 'containers with unready status: [c1[]' -// reason: ContainersNotReady -// status: "False" -// type: Ready -// - lastProbeTime: null -// lastTransitionTime: "2023-11-11T17:01:40Z" -// message: 'containers with unready status: [c1[]' -// reason: ContainersNotReady -// status: "False" -// type: ContainersReady -// - lastProbeTime: null -// lastTransitionTime: "2023-11-11T17:01:40Z" -// status: "True" -// type: PodScheduled -// containerStatuses: -// - image: blang/busybox-bash -// imageID: "" -// lastState: {} -// name: c1 -// ready: false -// restartCount: 0 -// started: false -// state: -// waiting: -// message: Back-off pulling image "blang/busybox-bash" -// reason: ImagePullBackOff -// hostIP: 172.18.0.3 -// phase: Pending -// podIP: 10.244.1.59 -// podIPs: -// - ip: 10.244.1.59 -// qosClass: BestEffort -// startTime: "2023-11-11T17:01:40Z" diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 2f7a4f0e10..68650b7c64 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/view/cmd" "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" ) @@ -41,11 +42,9 @@ const ( var ( // LabelRx identifies a label query. - LabelRx = regexp.MustCompile(`\A\-l`) - + LabelRx = regexp.MustCompile(`\A\-l`) inverseRx = regexp.MustCompile(`\A\!`) - - fuzzyRx = regexp.MustCompile(`\A\-f`) + fuzzyRx = regexp.MustCompile(`\A\-f`) ) func mustExtractStyles(ctx context.Context) *config.Styles { @@ -71,12 +70,11 @@ func IsLabelSelector(s string) bool { if s == "" { return false } - if LabelRx.MatchString(s) { return true } - return !strings.Contains(s, " ") && strings.Contains(s, "=") + return !strings.Contains(s, " ") && cmd.ToLabels(s) != nil } // IsFuzzySelector checks if query is fuzzy. diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 01fc7afe58..dc86cd396c 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -37,11 +37,13 @@ func TestIsLabelSelector(t *testing.T) { s string ok bool }{ - "empty": {s: ""}, - "cool": {s: "-l app=fred,env=blee", ok: true}, - "no-flag": {s: "app=fred,env=blee", ok: true}, - "no-space": {s: "-lapp=fred,env=blee", ok: true}, - "wrong-flag": {s: "-f app=fred,env=blee"}, + "empty": {s: ""}, + "cool": {s: "-l app=fred,env=blee", ok: true}, + "no-flag": {s: "app=fred,env=blee", ok: true}, + "no-space": {s: "-lapp=fred,env=blee", ok: true}, + "wrong-flag": {s: "-f app=fred,env=blee"}, + "missing-key": {s: "=fred"}, + "missing-val": {s: "fred="}, } for k := range uu { diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 8a5232eca5..535b39f864 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" @@ -74,8 +75,8 @@ func benchDir(cfg *config.Config) string { } return filepath.Join( config.AppBenchmarksDir, - config.SanitizeFileName(ct.ClusterName), - config.SanitizeFilename(cfg.K9s.ActiveContextName()), + data.SanitizeFileName(ct.ClusterName), + data.SanitizeFileName(cfg.K9s.ActiveContextName()), ) } diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go index bc4978f3e5..58b8335f68 100644 --- a/internal/view/cmd/helpers.go +++ b/internal/view/cmd/helpers.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" ) func ToLabels(s string) map[string]string { @@ -74,7 +73,6 @@ func SuggestSubCommand(command string, namespaces client.NamespaceNames, context if n, ok := p.HasContext(); ok { suggests = completeCtx(n, contexts) } - log.Debug().Msgf("!!SUGG CTX!! %#v", suggests) if len(suggests) > 0 { break } @@ -84,7 +82,6 @@ func SuggestSubCommand(command string, namespaces client.NamespaceNames, context return nil } suggests = completeNS(ns, namespaces) - log.Debug().Msgf("!!SUGG NS!! %#v", suggests) default: if n, ok := p.HasContext(); ok { diff --git a/internal/view/details.go b/internal/view/details.go index 04cbf6577b..53c9ee8db1 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -297,7 +297,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(d.app.Config.K9s.GetScreenDumpDir(), d.app.Config.K9s.ActiveContextDir(), d.title, d.text.GetText(true)); err != nil { + if path, err := saveYAML(d.app.Config.K9s.ActiveScreenDumpsDir(), d.title, d.text.GetText(true)); err != nil { d.app.Flash().Err(err) } else { d.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/live_view.go b/internal/view/live_view.go index be5ec6d393..d11a092f14 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -357,7 +357,7 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { name := fmt.Sprintf("%s--%s", strings.Replace(v.model.GetPath(), "/", "-", 1), strings.ToLower(v.title)) - if _, err := saveYAML(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.ActiveContextDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { + if _, err := saveYAML(v.app.Config.K9s.ActiveScreenDumpsDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { v.app.Flash().Err(err) } else { v.app.Flash().Infof("File %q saved successfully!", name) diff --git a/internal/view/log.go b/internal/view/log.go index 5627a2c2af..b6120bc6b2 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -406,7 +406,7 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey { // SaveCmd dumps the logs to file. func (l *Log) SaveCmd(*tcell.EventKey) *tcell.EventKey { - path, err := saveData(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.ActiveContextDir(), l.model.GetPath(), l.logs.GetText(true)) + path, err := saveData(l.app.Config.K9s.ActiveScreenDumpsDir(), l.model.GetPath(), l.logs.GetText(true)) if err != nil { l.app.Flash().Err(err) return nil @@ -420,8 +420,7 @@ func ensureDir(dir string) error { return os.MkdirAll(dir, 0744) } -func saveData(screenDumpDir, context, fqn, data string) (string, error) { - dir := filepath.Join(screenDumpDir, context) +func saveData(dir, fqn, data string) (string, error) { if err := ensureDir(dir); err != nil { return "", err } diff --git a/internal/view/log_test.go b/internal/view/log_test.go index 9bde19d19e..a55c50d9c7 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -7,7 +7,6 @@ import ( "bytes" "fmt" "os" - "path/filepath" "testing" "github.com/derailed/k9s/internal/client" @@ -113,7 +112,7 @@ func TestLogViewSave(t *testing.T) { dd := "/tmp/test-dumps/na" assert.NoError(t, ensureDumpDir(dd)) app.Config.K9s.ScreenDumpDir = "/tmp/test-dumps" - dir := filepath.Join(app.Config.K9s.GetScreenDumpDir(), app.Config.K9s.ActiveContextDir()) + dir := app.Config.K9s.ActiveScreenDumpsDir() c1, err := os.ReadDir(dir) assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir)) v.SaveCmd(nil) diff --git a/internal/view/logger.go b/internal/view/logger.go index 19c079bfe3..00446b0b17 100644 --- a/internal/view/logger.go +++ b/internal/view/logger.go @@ -154,7 +154,7 @@ func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(l.app.Config.K9s.GetScreenDumpDir(), l.app.Config.K9s.ActiveContextDir(), l.title, l.GetText(true)); err != nil { + if path, err := saveYAML(l.app.Config.K9s.ActiveScreenDumpsDir(), l.title, l.GetText(true)); err != nil { l.app.Flash().Err(err) } else { l.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 9183498c8b..4e4371c13e 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -5,7 +5,6 @@ package view import ( "context" - "path/filepath" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -36,7 +35,7 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { } func (s *ScreenDump) dirContext(ctx context.Context) context.Context { - dir := filepath.Join(s.App().Config.K9s.GetScreenDumpDir(), s.App().Config.K9s.ActiveContextDir()) + dir := s.App().Config.K9s.ActiveScreenDumpsDir() if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil { s.App().Flash().Err(err) return ctx diff --git a/internal/view/table.go b/internal/view/table.go index a2ce9aabf7..7e24dcf816 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -170,7 +170,7 @@ func (t *Table) BufferActive(state bool, k model.BufferKind) { } func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveTable(t.app.Config.K9s.GetScreenDumpDir(), t.app.Config.K9s.ActiveContextDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { + if path, err := saveTable(t.app.Config.K9s.ActiveScreenDumpsDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { t.app.Flash().Err(err) } else { t.app.Flash().Infof("File %s saved successfully!", path) diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 3833655954..820f8754e1 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -12,21 +12,21 @@ import ( "time" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/rs/zerolog/log" ) -func computeFilename(screenDumpDir, context, ns, title, path string) (string, error) { +func computeFilename(dumpPath, ns, title, path string) (string, error) { now := time.Now().UnixNano() - dir := filepath.Join(screenDumpDir, context) + dir := filepath.Join(dumpPath) if err := ensureDir(dir); err != nil { return "", err } - name := title + "-" + config.SanitizeFilename(path) + name := title + "-" + data.SanitizeFileName(path) if path == "" { name = title } @@ -41,13 +41,13 @@ func computeFilename(screenDumpDir, context, ns, title, path string) (string, er return strings.ToLower(filepath.Join(dir, fName)), nil } -func saveTable(screenDumpDir, context, title, path string, data *render.TableData) (string, error) { +func saveTable(dir, title, path string, data *render.TableData) (string, error) { ns := data.Namespace if client.IsClusterWide(ns) { ns = client.NamespaceAll } - fPath, err := computeFilename(screenDumpDir, context, ns, title, path) + fPath, err := computeFilename(dir, ns, title, path) if err != nil { return "", err } diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 32e21b2dac..13b564fc79 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -6,7 +6,6 @@ package view import ( "context" "os" - "path/filepath" "testing" "time" @@ -30,7 +29,7 @@ func TestTableSave(t *testing.T) { v.SetTitle("k9s-test") assert.NoError(t, ensureDumpDir("/tmp/test-dumps")) - dir := filepath.Join(v.app.Config.K9s.GetScreenDumpDir(), v.app.Config.K9s.ActiveContextDir()) + dir := v.app.Config.K9s.ActiveScreenDumpsDir() c1, _ := os.ReadDir(dir) v.saveCmd(nil) diff --git a/internal/view/yaml.go b/internal/view/yaml.go index dba886733d..828e96e3f0 100644 --- a/internal/view/yaml.go +++ b/internal/view/yaml.go @@ -12,6 +12,7 @@ import ( "time" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/tview" "github.com/rs/zerolog/log" ) @@ -62,18 +63,17 @@ func enableRegion(str string) string { return strings.ReplaceAll(strings.ReplaceAll(str, "<<<", "["), ">>>", "]") } -func saveYAML(screenDumpDir, context, name, data string) (string, error) { - dir := filepath.Join(screenDumpDir, config.SanitizeFilename(context)) +func saveYAML(dir, name, raw string) (string, error) { if err := ensureDir(dir); err != nil { return "", err } - fName := fmt.Sprintf("%s--%d.yaml", config.SanitizeFilename(name), time.Now().Unix()) - path := filepath.Join(dir, fName) + fName := fmt.Sprintf("%s--%d.yaml", data.SanitizeFileName(name), time.Now().Unix()) + fpath := filepath.Join(dir, fName) mod := os.O_CREATE | os.O_WRONLY - file, err := os.OpenFile(path, mod, 0600) + file, err := os.OpenFile(fpath, mod, 0600) if err != nil { - log.Error().Err(err).Msgf("YAML create %s", path) + log.Error().Err(err).Msgf("YAML create %s", fpath) return "", nil } defer func() { @@ -81,9 +81,9 @@ func saveYAML(screenDumpDir, context, name, data string) (string, error) { log.Error().Err(err).Msg("Closing yaml file") } }() - if _, err := file.Write([]byte(data)); err != nil { + if _, err := file.Write([]byte(raw)); err != nil { return "", err } - return path, nil + return fpath, nil } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3be205ee8e..b612e4d7f1 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.2' +version: 'v0.30.3' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From d8982eae9fb8a6184a53761dd3e6c0acec540099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:22:53 -0700 Subject: [PATCH 044/169] Bump github.com/anchore/grype from 0.73.4 to 0.73.5 (#2385) Bumps [github.com/anchore/grype](https://github.com/anchore/grype) from 0.73.4 to 0.73.5. - [Release notes](https://github.com/anchore/grype/releases) - [Changelog](https://github.com/anchore/grype/blob/main/.goreleaser.yaml) - [Commits](https://github.com/anchore/grype/compare/v0.73.4...v0.73.5) --- updated-dependencies: - dependency-name: github.com/anchore/grype dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 1c246e6949..8be70d20f3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.4 require ( github.com/adrg/xdg v0.4.0 github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc - github.com/anchore/grype v0.73.4 + github.com/anchore/grype v0.73.5 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.2 @@ -50,7 +50,7 @@ require ( github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect - github.com/CycloneDX/cyclonedx-go v0.7.2 // indirect + github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect github.com/DataDog/zstd v1.4.5 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -62,14 +62,14 @@ require ( github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect - github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 // indirect + github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b // indirect github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a // indirect github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect - github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 // indirect - github.com/anchore/syft v0.98.0 // indirect + github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89 // indirect + github.com/anchore/syft v0.99.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect @@ -125,7 +125,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.10.1 // indirect + github.com/go-git/go-git/v5 v5.11.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -142,13 +142,13 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-containerregistry v0.16.1 // indirect + github.com/google/go-containerregistry v0.17.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/licensecheck v0.3.1 // indirect github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gookit/color v1.5.4 // indirect @@ -246,7 +246,7 @@ require ( github.com/rivo/uniseg v0.4.3 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/saferwall/pe v1.4.7 // indirect + github.com/saferwall/pe v1.4.8 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect @@ -319,7 +319,7 @@ require ( modernc.org/libc v1.29.0 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.27.0 // indirect + modernc.org/sqlite v1.28.0 // indirect oras.land/oras-go v1.2.4 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/go.sum b/go.sum index ba245aaa7c..8354f51137 100644 --- a/go.sum +++ b/go.sum @@ -201,8 +201,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ= -github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= +github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= +github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -241,8 +241,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ= github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU= -github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4 h1:3jHs159SUguPb0YMH/mKN2+eKKme76r+6iwAZ1xlu7c= -github.com/anchore/fangs v0.0.0-20231106214039-d96c8f312db4/go.mod h1:yPsN3NUGhU5dcBtYBa1dMNzGu1yT5ZAfSjKq9DY4aV8= +github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A= +github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo= github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw= github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= @@ -253,14 +253,14 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= -github.com/anchore/grype v0.73.4 h1:j8HzRHbXLLZ6U2lmDDRFILd+VZtWbsfg/RYhatRZW9E= -github.com/anchore/grype v0.73.4/go.mod h1:5kJSAsHPoK47DsGZLHHArCfhHVGFGRkCfL2H87GdrdY= +github.com/anchore/grype v0.73.5 h1:1X81Snj5pGpl9ru7mQl1eYLX1Ek2ElfKhm9cwIgdCOw= +github.com/anchore/grype v0.73.5/go.mod h1:bdI7d2XeXQbmfbqql/Fqg+Lv2w4gO3nN3jfby/mBIcs= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83 h1:mxGIOmj+asEm8LUkPTG3/v0hi27WIlDVjiEVsUB9eqY= -github.com/anchore/stereoscope v0.0.0-20231117203853-3610f4ef3e83/go.mod h1:GKAnytSVV1hoqB5r5Gd9M5Ph3Rzqq0zPdEJesewjC2w= -github.com/anchore/syft v0.98.0 h1:mPDah48zZCFeSiGweqPd2C2++rOUh3/cAZylEy1VPwU= -github.com/anchore/syft v0.98.0/go.mod h1:FMj8zZFF3mP4IAuTxb6n14CZ6ouWXpI9RZqXpnkLK+Y= +github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89 h1:dymFMCwnENqLr74KQppq8zHKwOPL0M1ToYAU+KVfTew= +github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89/go.mod h1:GKAnytSVV1hoqB5r5Gd9M5Ph3Rzqq0zPdEJesewjC2w= +github.com/anchore/syft v0.99.0 h1:oqycIA7XfHCB09meroN7eY2RWTGUZIdtWsMQL2HlPvw= +github.com/anchore/syft v0.99.0/go.mod h1:tGZGyDxB2z/yu+x266+b67fMenGKCrUvSNVKED1euuo= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -497,8 +497,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -604,8 +604,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ= -github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= +github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -643,8 +643,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1031,8 +1031,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/saferwall/pe v1.4.7 h1:A+G3DxX49paJ5OsxBfHKskhyDtmTjShlDmBd81IsHlQ= -github.com/saferwall/pe v1.4.7/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= +github.com/saferwall/pe v1.4.8 h1:ey/L8FGBMrJ1Xh+Rltj1MAFPZ4LOQYGJqNa5B1Na6B0= +github.com/saferwall/pe v1.4.8/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= @@ -1877,8 +1877,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= -modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 490b6d535906922f6419c5e8ba3492f23137ae5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:23:49 -0700 Subject: [PATCH 045/169] Bump helm.sh/helm/v3 from 3.13.2 to 3.13.3 (#2384) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.13.2 to 3.13.3. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.13.2...v3.13.3) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8be70d20f3..7fcd6acd0a 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 - helm.sh/helm/v3 v3.13.2 + helm.sh/helm/v3 v3.13.3 k8s.io/api v0.29.0 k8s.io/apiextensions-apiserver v0.29.0 k8s.io/apimachinery v0.29.0 diff --git a/go.sum b/go.sum index 8354f51137..a827513d13 100644 --- a/go.sum +++ b/go.sum @@ -1838,8 +1838,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w= -helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0= +helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= +helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 00d16beedba64db7f895c986d1bb84628c962f23 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Mon, 25 Dec 2023 18:25:53 -0700 Subject: [PATCH 046/169] fix #2382 (#2386) --- internal/client/config.go | 11 ++++++++++- internal/client/config_test.go | 21 +++++++++++++-------- internal/client/testdata/config | 4 ++-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/internal/client/config.go b/internal/client/config.go index e7d47885e8..76d51f4bd1 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -96,7 +96,16 @@ func (c *Config) CurrentClusterName() (string, error) { return "", err } - ct := cfg.Contexts[cfg.CurrentContext] + ct, ok := cfg.Contexts[cfg.CurrentContext] + if !ok { + return "", fmt.Errorf("invalid current context specified: %q", cfg.CurrentContext) + } + if isSet(c.flags.Context) { + ct, ok = cfg.Contexts[*c.flags.Context] + if !ok { + return "", fmt.Errorf("invalid context specified: %q", *c.flags.Context) + } + } return ct.Cluster, nil diff --git a/internal/client/config_test.go b/internal/client/config_test.go index dda1e39ff3..f7a6f1a585 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -54,15 +54,20 @@ func TestConfigCurrentCluster(t *testing.T) { name, kubeConfig := "blee", "./testdata/config" uu := map[string]struct { flags *genericclioptions.ConfigFlags - context string + cluster string }{ "default": { - flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, - context: "fred", + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &kubeConfig, + }, + cluster: "zorg", }, "custom": { - flags: &genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Context: &name}, - context: "blee", + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &kubeConfig, + Context: &name, + }, + cluster: "blee", }, } @@ -70,9 +75,9 @@ func TestConfigCurrentCluster(t *testing.T) { u := uu[k] t.Run(k, func(t *testing.T) { cfg := client.NewConfig(u.flags) - ct, err := cfg.CurrentContextName() + ct, err := cfg.CurrentClusterName() assert.Nil(t, err) - assert.Equal(t, u.context, ct) + assert.Equal(t, u.cluster, ct) }) } } @@ -252,7 +257,7 @@ func TestConfigRestConfig(t *testing.T) { cfg := client.NewConfig(&flags) rc, err := cfg.RESTConfig() assert.Nil(t, err) - assert.Equal(t, "https://localhost:3000", rc.Host) + assert.Equal(t, "https://localhost:3002", rc.Host) } func TestConfigBadConfig(t *testing.T) { diff --git a/internal/client/testdata/config b/internal/client/testdata/config index 88e0a0e87f..a43df2c815 100644 --- a/internal/client/testdata/config +++ b/internal/client/testdata/config @@ -13,10 +13,10 @@ clusters: - cluster: insecure-skip-tls-verify: true server: https://localhost:3002 - name: duh + name: zorg contexts: - context: - cluster: fred + cluster: zorg user: fred name: fred - context: From fdcb9933f6a05995db0f06a8d0b6b34edd7acca6 Mon Sep 17 00:00:00 2001 From: Emanuele Ciurleo Date: Mon, 25 Dec 2023 22:28:52 -0300 Subject: [PATCH 047/169] v0.30.3 version update (#2383) * Update build v0.30.2 * v0.30.3 From 27cc859e6845c881b9a92838f0a03ffe4a5b2e50 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 27 Dec 2023 00:46:16 +0800 Subject: [PATCH 048/169] case sensitive for specific command args and flags (#2390) --- internal/view/cmd/args.go | 14 ++++++-------- internal/view/cmd/args_test.go | 10 ++++++++++ internal/view/cmd/interpreter.go | 6 +++--- internal/view/cmd/interpreter_test.go | 10 ++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go index aaef7d61e7..2f95cb2a07 100644 --- a/internal/view/cmd/args.go +++ b/internal/view/cmd/args.go @@ -31,19 +31,17 @@ func newArgs(p *Interpreter, aa []string) args { case strings.Index(a, fuzzyFlag) == 0: i++ - args[filterKey] = strings.TrimSpace(aa[i]) - continue + args[filterKey] = strings.ToLower(strings.TrimSpace(aa[i])) case strings.Index(a, filterFlag) == 0: - args[filterKey] = a[1:] + args[filterKey] = strings.ToLower(a[1:]) case strings.Contains(a, labelFlag): if ll := ToLabels(a); len(ll) != 0 { - args[labelKey] = a + args[labelKey] = strings.ToLower(a) } default: - a := strings.TrimSpace(aa[i]) switch { case p.IsContextCmd(): args[contextKey] = a @@ -53,12 +51,12 @@ func newArgs(p *Interpreter, aa []string) args { } case p.IsXrayCmd(): if _, ok := args[topicKey]; ok { - args[nsKey] = a + args[nsKey] = strings.ToLower(a) } else { - args[topicKey] = a + args[topicKey] = strings.ToLower(a) } default: - args[nsKey] = a + args[nsKey] = strings.ToLower(a) } } } diff --git a/internal/view/cmd/args_test.go b/internal/view/cmd/args_test.go index df4e4c319b..8efe229ef3 100644 --- a/internal/view/cmd/args_test.go +++ b/internal/view/cmd/args_test.go @@ -79,6 +79,16 @@ func TestFlagsNew(t *testing.T) { aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg", "@ctx1"}, ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1", contextKey: "ctx1"}, }, + "caps": { + i: NewInterpreter("po"), + aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg", "@Dev"}, + ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1", contextKey: "Dev"}, + }, + "ctx": { + i: NewInterpreter("ctx"), + aa: []string{"Dev"}, + ll: args{contextKey: "Dev"}, + }, } for k := range uu { diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go index 4b142e513f..b95149c5b9 100644 --- a/internal/view/cmd/interpreter.go +++ b/internal/view/cmd/interpreter.go @@ -15,7 +15,7 @@ type Interpreter struct { func NewInterpreter(s string) *Interpreter { c := Interpreter{ - line: strings.ToLower(s), + line: s, args: make(args), } c.grok() @@ -28,7 +28,7 @@ func (c *Interpreter) grok() { if len(ff) == 0 { return } - c.cmd = ff[0] + c.cmd = strings.ToLower(ff[0]) c.args = newArgs(c, ff[1:]) } @@ -59,7 +59,7 @@ func (c *Interpreter) Amend(c1 *Interpreter) { } func (c *Interpreter) Reset(s string) *Interpreter { - c.line = strings.ToLower(s) + c.line = s c.grok() return c diff --git a/internal/view/cmd/interpreter_test.go b/internal/view/cmd/interpreter_test.go index bb78da78c0..cc5c9ca989 100644 --- a/internal/view/cmd/interpreter_test.go +++ b/internal/view/cmd/interpreter_test.go @@ -242,6 +242,11 @@ func TestDirCmd(t *testing.T) { "toast-nodir": { cmd: "dir", }, + "caps": { + cmd: "dir DirName", + ok: true, + dir: "DirName", + }, } for k := range uu { @@ -330,6 +335,11 @@ func TestContextCmd(t *testing.T) { "toast": { cmd: "ctxto ctx1", }, + "caps": { + cmd: "ctx Dev", + ok: true, + ctx: "Dev", + }, } for k := range uu { From 34da44b441598c68fb6de1571f2a38f74d66bb01 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 26 Dec 2023 10:30:11 -0700 Subject: [PATCH 049/169] K9s/release v0.30.4 (#2392) * v0.30.3 version update (#2383) * Update build v0.30.2 * v0.30.3 * [Bug] Fix #2387 * [Bug] Fix #2391 * rel v0.30.4 --------- Co-authored-by: Emanuele Ciurleo --- Makefile | 2 +- change_logs/release_v0.30.4.md | 52 ++++++++++++++++++++++++++++ internal/client/client.go | 46 +++++++++++++++++++++++- internal/client/config.go | 4 +-- internal/client/config_test.go | 2 +- internal/config/data/dir.go | 11 ++---- internal/config/data/helpers_test.go | 31 +++++++++++++++++ internal/config/k9s.go | 3 +- internal/view/app.go | 6 ++-- snap/snapcraft.yaml | 2 +- 10 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 change_logs/release_v0.30.4.md diff --git a/Makefile b/Makefile index 298dfa8dfd..a84db0a79f 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.3 +VERSION ?= v0.30.4 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.4.md b/change_logs/release_v0.30.4.md new file mode 100644 index 0000000000..c7a1ad2063 --- /dev/null +++ b/change_logs/release_v0.30.4.md @@ -0,0 +1,52 @@ + + +# Release v0.30.4 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2391](https://github.com/derailed/k9s/issues/2391) Version 0.30.* has issues with : chars in the cluster names from AWS +* [#2397](https://github.com/derailed/k9s/issues/2387) Error: invalid namespace xxx +* [#2389](https://github.com/derailed/k9s/issues/2389) Mixed-case named contexts cannot be switched to from contexts view +* [#2382](https://github.com/derailed/k9s/issues/2382) Header always shows Cluster from kubeconfig current-context + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2390](https://github.com/derailed/k9s/pull/2390) case sensitive for specific command args and flags + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/client/client.go b/internal/client/client.go index 577c3a3fb5..66bcf3fa6d 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -216,15 +216,59 @@ func (a *APIClient) IsValidNamespace(ns string) bool { if IsAllNamespace(ns) { return true } + + ok, err := a.CanI(ClusterScope, "v1/namespaces", []string{ListVerb}) + if !ok || err != nil { + cool, err := a.isValidNamespace(ns) + if err != nil { + log.Error().Err(err).Msgf("unable to assert valid namespace") + } + return cool + } nn, err := a.ValidNamespaceNames() if err != nil { return false } - _, ok := nn[ns] + _, ok = nn[ns] return ok } +func (a *APIClient) cachedNamespaceNames() NamespaceNames { + cns, ok := a.cache.Get("validNamespaces") + if !ok { + return make(NamespaceNames) + } + + return cns.(NamespaceNames) +} + +func (a *APIClient) isValidNamespace(n string) (bool, error) { + if a == nil { + return false, errors.New("invalid client") + } + + cnss := a.cachedNamespaceNames() + if _, ok := cnss[n]; ok { + return true, nil + } + + dial, err := a.Dial() + if err != nil { + return false, err + } + ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout()) + defer cancel() + _, err = dial.CoreV1().Namespaces().Get(ctx, n, metav1.GetOptions{}) + if err != nil { + return false, err + } + cnss[n] = struct{}{} + a.cache.Add("validNamespaces", cnss, cacheExpiry) + + return true, nil +} + // ValidNamespaceNames returns all available namespaces. func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { if a == nil { diff --git a/internal/client/config.go b/internal/client/config.go index 76d51f4bd1..66990488c9 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -103,7 +103,7 @@ func (c *Config) CurrentClusterName() (string, error) { if isSet(c.flags.Context) { ct, ok = cfg.Contexts[*c.flags.Context] if !ok { - return "", fmt.Errorf("invalid context specified: %q", *c.flags.Context) + return "", fmt.Errorf("current-cluster - invalid context specified: %q", *c.flags.Context) } } @@ -156,7 +156,7 @@ func (c *Config) GetContext(n string) (*api.Context, error) { return c, nil } - return nil, fmt.Errorf("invalid context `%s specified", n) + return nil, fmt.Errorf("getcontext - invalid context specified: %q", n) } // Contexts fetch all available contexts. diff --git a/internal/client/config_test.go b/internal/client/config_test.go index f7a6f1a585..046e5dd69b 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -154,7 +154,7 @@ func TestConfigGetContext(t *testing.T) { }, "custom": { cluster: "bozo", - err: errors.New("invalid context `bozo specified"), + err: errors.New(`getcontext - invalid context specified: "bozo"`), }, } diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index 6c95775911..b8e5d88d07 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -34,14 +34,9 @@ func (d Dir) Load(n string, ct *api.Context) (*Config, error) { } var ( - path = filepath.Join( - d.root, - SanitizeFileName(ct.Cluster), - SanitizeFileName(n), - MainConfigFile, - ) - cfg *Config - err error + path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) + cfg *Config + err error ) if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 { log.Debug().Msgf("Context config not found! Generating... %q", path) diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go index be4a6a8537..ed82e4584b 100644 --- a/internal/config/data/helpers_test.go +++ b/internal/config/data/helpers_test.go @@ -12,6 +12,37 @@ import ( "github.com/stretchr/testify/assert" ) +func TestSanitizeFileName(t *testing.T) { + uu := map[string]struct { + file, e string + }{ + "empty": {}, + "plain": { + file: "bumble-bee-tuna", + e: "bumble-bee-tuna", + }, + "slash": { + file: "bumble/bee/tuna", + e: "bumble-bee-tuna", + }, + "column": { + file: "bumble::bee:tuna", + e: "bumble-bee-tuna", + }, + "eks": { + file: "arn:aws:eks:us-east-1:123456789:cluster/us-east-1-app-dev-common-eks", + e: "arn-aws-eks-us-east-1-123456789-cluster-us-east-1-app-dev-common-eks", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, data.SanitizeFileName(u.file)) + }) + } +} + func TestHelperInList(t *testing.T) { uu := []struct { item string diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 18e1b2daa0..fd8bdae67b 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -61,8 +61,7 @@ func (k *K9s) Save() error { if k.activeConfig != nil { path := filepath.Join( AppContextsDir, - k.activeConfig.Context.ClusterName, - k.activeContextName, + data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName), data.MainConfigFile, ) return k.activeConfig.Save(path) diff --git a/internal/view/app.go b/internal/view/app.go index 9cbf742dd2..bbba78a28d 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -100,7 +100,7 @@ func (a *App) Init(version string, rate int) error { a.factory = watch.NewFactory(a.Conn()) ok, err := a.isValidNS(ns) if !ok && err == nil { - return fmt.Errorf("invalid namespace %s", ns) + return fmt.Errorf("app-init - invalid namespace: %q", ns) } a.initFactory(ns) @@ -415,7 +415,7 @@ func (a *App) switchNS(ns string) error { return err } if !ok { - return fmt.Errorf("invalid namespace %q", ns) + return fmt.Errorf("switchns - invalid namespace: %q", ns) } if err := a.Config.SetActiveNamespace(ns); err != nil { return err @@ -433,7 +433,7 @@ func (a *App) isValidNS(ns string) (bool, error) { } if !a.Conn().IsValidNamespace(ns) { - return false, fmt.Errorf("invalid namespace: %q", ns) + return false, fmt.Errorf("isvalidns - invalid namespace: %q", ns) } return true, nil diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b612e4d7f1..342a0a8e1d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.3' +version: 'v0.30.4' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 8debcab3eb89f108c54cdad91ccc20385fb5fa9a Mon Sep 17 00:00:00 2001 From: Jose Alvarez <47305925+jalvarezit@users.noreply.github.com> Date: Tue, 26 Dec 2023 22:11:12 +0100 Subject: [PATCH 050/169] Add plugin to remove finalizers (#2359) * Add finalizer plugin * Add explanation and authorship * Move description and update plugin key to new k9s version --- plugins/remove_finalizers.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 plugins/remove_finalizers.yml diff --git a/plugins/remove_finalizers.yml b/plugins/remove_finalizers.yml new file mode 100644 index 0000000000..4abb7d46ac --- /dev/null +++ b/plugins/remove_finalizers.yml @@ -0,0 +1,32 @@ +# Removes all finalizers from the selected resource. Finalizers are namespaced keys that tell Kubernetes to wait +# until specific conditions are met before it fully deletes resources marked for deletion. +# Before deleting an object you need to ensure that all finalizers has been removed. Usually this would be done +# by the specific controller but under some circumstances it is possible to encounter a set of objects blocked +# for deletion. +# This plugins makes this task easier by providing a shortcut to directly removing them all. +# Be careful when using this plugin as it may leave dangling resources or instantly deleting resources that were +# blocked by the finalizers. +# Author: github.com/jalvarezit +plugins: + remove_finalizers: + shortCut: Ctrl-F + confirm: true + scopes: + - all + description: | + Removes all finalizers from selected resource. Be careful when using it, + it may leave dangling resources or delete them + command: kubectl + background: true + args: + - patch + - --context + - $CONTEXT + - --namespace + - $NAMESPACE + - $RESOURCE_NAME + - $NAME + - -p + - '{"metadata":{"finalizers":null}}' + - --type + - merge From 736f6bfaa5a7aa1591287dbef31a0b77125d774d Mon Sep 17 00:00:00 2001 From: Nicolas Karolak Date: Thu, 28 Dec 2023 00:00:09 +0100 Subject: [PATCH 051/169] feat: allow to customize logs dir through environment variable (#2396) Allow to customize log directory through `K9S_LOGS_DIR` environment variable. If not set, fallsback on default tmp directory. Fixes #2394 --- README.md | 16 ++++++++++++++++ internal/config/files.go | 21 +++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1b6b920df4..7a1c80e4fb 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,22 @@ tail -f /Users/fernand/.local/data/k9s/k9s.log k9s -l debug ``` +### Customize logs destination + +You can override the default log file destination either with the `--logFile` argument: + +```shell +k9s --logFile /tmp/k9s.log +less /tmp/k9s.log +``` + +Or through the `K9S_LOGS_DIR` environment variable: + +```shell +K9S_LOGS_DIR=/var/log k9s +less /var/log/k9s.log +``` + ## Key Bindings K9s uses aliases to navigate most K8s resources. diff --git a/internal/config/files.go b/internal/config/files.go index 0d8cee113d..b1ad0a8aa5 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -19,6 +19,9 @@ const ( // K9sConfigDir represents k9s configuration dir env var. K9sConfigDir = "K9S_CONFIG_DIR" + // K9sLogsDir represents k9s logs dir env var. + K9sLogsDir = "K9S_LOGS_DIR" + // AppName tracks k9s app name. AppName = "k9s" @@ -80,11 +83,20 @@ var ( // InitLogsLoc initializes K9s logs location. func InitLogLoc() error { - tmpDir, err := userTmpDir() - if err != nil { + var appLogDir string + if envDir := os.Getenv(K9sLogsDir); envDir != "" { + appLogDir = envDir + } else { + tmpDir, err := userTmpDir() + if err != nil { + return err + } + appLogDir = tmpDir + } + if err := data.EnsureFullPath(appLogDir, data.DefaultDirMod); err != nil { return err } - AppLogFile = filepath.Join(tmpDir, K9sLogsFile) + AppLogFile = filepath.Join(appLogDir, K9sLogsFile) return nil } @@ -271,9 +283,6 @@ func userTmpDir() (string, error) { } dir := filepath.Join(os.TempDir(), AppName, u.Username) - if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil { - return "", err - } return dir, nil } From f5f3278e17308413769ed932bd7252e84aeea8e7 Mon Sep 17 00:00:00 2001 From: Nicolas Karolak Date: Thu, 28 Dec 2023 00:51:03 +0100 Subject: [PATCH 052/169] fix: create user tmp directory before the app one (#2395) Otherwise on a shared machine, the `/tmp/k9s` directory will be owned by the first user to lanch `k9s` command. The other users will get a "permission denied" error. --- internal/config/files.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/files.go b/internal/config/files.go index b1ad0a8aa5..4913210f29 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -282,7 +282,7 @@ func userTmpDir() (string, error) { return "", err } - dir := filepath.Join(os.TempDir(), AppName, u.Username) + dir := filepath.Join(os.TempDir(), u.Username, AppName) return dir, nil } From 9e337a6be06a69d6d61be1a2ba24fe65f548b7e5 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 27 Dec 2023 17:40:44 -0700 Subject: [PATCH 053/169] K9s/release v0.30.5 (#2397) * [Bug] Fix #2393 * [Maint] Cleaning up * rel docs --- Makefile | 2 +- change_logs/release_v0.30.5.md | 52 ++++++++++++++++++++++++++++++++ cmd/root.go | 25 +++++++++------ internal/config/config.go | 22 ++++++++------ internal/config/data/context.go | 4 --- internal/config/data/dir.go | 15 +++------ internal/config/data/dir_test.go | 2 +- internal/config/data/ns.go | 16 +++++----- internal/config/files.go | 45 +++++++++++---------------- internal/config/files_test.go | 50 ++++++++++++++++++++++++++++-- internal/config/flags.go | 3 -- internal/config/helpers.go | 19 ++++++++++++ internal/config/k9s.go | 17 ++++++++--- internal/ui/config.go | 4 --- internal/ui/config_test.go | 8 ++--- internal/view/app.go | 31 ++++++++++++------- internal/view/browser.go | 1 - internal/view/command.go | 4 --- internal/view/log.go | 13 ++++---- snap/snapcraft.yaml | 2 +- 20 files changed, 225 insertions(+), 110 deletions(-) create mode 100644 change_logs/release_v0.30.5.md diff --git a/Makefile b/Makefile index a84db0a79f..7dc2db8daf 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.4 +VERSION ?= v0.30.5 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.5.md b/change_logs/release_v0.30.5.md new file mode 100644 index 0000000000..b59cf29af1 --- /dev/null +++ b/change_logs/release_v0.30.5.md @@ -0,0 +1,52 @@ + + +# Release v0.30.5 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2394](https://github.com/derailed/k9s/issues/2394) Allow setting custom log dir +* [#2393](https://github.com/derailed/k9s/issues/2393) When switching contexts k9s does not switching to cluster's pod/namespaces/other k8s kinds view +* [#2387](https://github.com/derailed/k9s/issues/2387) Invalid namespace xxx - with feelings! + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2396](https://github.com/derailed/k9s/pull/2396) feat: allow to customize logs dir through environment variable +* [#2395](https://github.com/derailed/k9s/pull/2395) fix: create user tmp directory before the app one + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index 703ddf289f..079ec37e45 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -91,7 +91,11 @@ func run(cmd *cobra.Command, args []string) error { log.Logger = log.Output(zerolog.ConsoleWriter{Out: file}) zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel)) - app := view.NewApp(loadConfiguration()) + cfg, err := loadConfiguration() + if err != nil { + return err + } + app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { return err } @@ -105,7 +109,7 @@ func run(cmd *cobra.Command, args []string) error { return nil } -func loadConfiguration() *config.Config { +func loadConfiguration() (*config.Config, error) { log.Info().Msg("🐶 K9s starting up...") k8sCfg := client.NewConfig(k8sFlags) @@ -116,26 +120,29 @@ func loadConfiguration() *config.Config { k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("refine failed") + return nil, err } conn, err := client.InitConnection(k8sCfg) - k9sCfg.SetConnection(conn) if err != nil { log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName()) - return k9sCfg + return nil, err } // Try to access server version if that fail. Connectivity issue? - if !k9sCfg.GetConnection().CheckConnectivity() { - log.Panic().Msgf("Cannot connect to context %s", k9sCfg.K9s.ActiveContextName()) + if !conn.CheckConnectivity() { + return nil, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) } - if !k9sCfg.GetConnection().ConnectionOK() { - panic("No connectivity") + if !conn.ConnectionOK() { + return nil, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) } + k9sCfg.SetConnection(conn) + log.Info().Msg("✅ Kubernetes connectivity") if err := k9sCfg.Save(); err != nil { log.Error().Err(err).Msg("Config save") + return nil, err } - return k9sCfg + return k9sCfg, nil } func parseLevel(level string) zerolog.Level { diff --git a/internal/config/config.go b/internal/config/config.go index d4e8e81b7a..c4b781b27d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,8 +26,8 @@ type Config struct { // K9sHome returns k9s configs home directory. func K9sHome() string { - if env := os.Getenv(K9sConfigDir); env != "" { - return env + if isEnvSet(K9sEnvConfigDir) { + return os.Getenv(K9sEnvConfigDir) } xdgK9sHome, err := xdg.ConfigFile(AppName) @@ -84,7 +84,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) - var ns = client.DefaultNamespace + var ns string switch { case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces): ns = client.NamespaceAll @@ -97,10 +97,12 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } ns = nss } + if ns == "" { + ns = client.DefaultNamespace + } if err := c.SetActiveNamespace(ns); err != nil { return err } - flags.Namespace = &ns return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod) } @@ -139,10 +141,10 @@ func (c *Config) ActiveNamespace() string { // ValidateFavorites ensure favorite ns are legit. func (c *Config) ValidateFavorites() { ct, err := c.K9s.ActiveContext() - if err == nil { - ct.Validate(c.conn, c.settings) - ct.Namespace.Validate(c.conn, c.settings) + if err != nil { + return } + ct.Validate(c.conn, c.settings) } // FavNamespaces returns fav namespaces in the current context. @@ -197,8 +199,10 @@ func (c *Config) GetConnection() client.Connection { // SetConnection set an api server connection. func (c *Config) SetConnection(conn client.Connection) { - c.conn, c.K9s.conn = conn, conn - c.Validate() + c.conn = conn + if conn != nil { + c.K9s.resetConnection(conn) + } } func (c *Config) ActiveContextName() string { diff --git a/internal/config/data/context.go b/internal/config/data/context.go index e08de8ffa2..081b133822 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -47,7 +47,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if c.PortForwardAddress == "" { c.PortForwardAddress = DefaultPFAddress } - if cl, err := ks.CurrentClusterName(); err != nil { c.ClusterName = cl } @@ -55,9 +54,6 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if c.Namespace == nil { c.Namespace = NewNamespace() } - if c.Namespace.Active == client.BlankNamespace { - c.Namespace.Active = client.DefaultNamespace - } c.Namespace.Validate(conn, ks) if c.View == nil { diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index b8e5d88d07..d20e545652 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -8,31 +8,28 @@ import ( "os" "path/filepath" - "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/client-go/tools/clientcmd/api" ) +// Dir tracks context configurations. type Dir struct { root string - conn client.Connection - ks KubeSettings } -func NewDir(root string, conn client.Connection, ks KubeSettings) *Dir { +// NewDir returns a new instance. +func NewDir(root string) *Dir { return &Dir{ root: root, - ks: ks, - conn: conn, } } -func (d Dir) Load(n string, ct *api.Context) (*Config, error) { +// Load loads context configuration. +func (d *Dir) Load(n string, ct *api.Context) (*Config, error) { if ct == nil { return nil, errors.New("api.Context must not be nil") } - var ( path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) cfg *Config @@ -51,7 +48,6 @@ func (d Dir) Load(n string, ct *api.Context) (*Config, error) { func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { cfg := NewConfig(ct) - cfg.Validate(d.conn, d.ks) if err := cfg.Save(path); err != nil { return nil, err } @@ -68,7 +64,6 @@ func (d *Dir) loadConfig(path string) (*Config, error) { if err := yaml.Unmarshal(bb, &cfg); err != nil { return nil, err } - cfg.Validate(d.conn, d.ks) return &cfg, nil } diff --git a/internal/config/data/dir_test.go b/internal/config/data/dir_test.go index 56d0bc67ce..b780a585c6 100644 --- a/internal/config/data/dir_test.go +++ b/internal/config/data/dir_test.go @@ -71,7 +71,7 @@ func TestDirLoad(t *testing.T) { assert.NoError(t, mock.EnsureDir(u.dir)) } - d := data.NewDir(u.dir, mock.NewMockConnection(), ks) + d := data.NewDir(u.dir) ct, err := ks.CurrentContext() assert.NoError(t, err) if err != nil { diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 252b4f5750..e9e9379cb5 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -29,28 +29,30 @@ func NewNamespace() *Namespace { } func NewActiveNamespace(n string) *Namespace { + if n == client.BlankNamespace { + n = client.DefaultNamespace + } return &Namespace{ Active: n, Favorites: []string{client.DefaultNamespace}, } } -// Validate a namespace is setup correctly. +// Validate validates a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { - if c == nil { - n = NewActiveNamespace(client.DefaultNamespace) + if n.Active == client.BlankNamespace || c == nil { + n.Active = client.DefaultNamespace } if c == nil { - log.Debug().Msgf("No connection found. Skipping ns validation") return } if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) { - log.Error().Msgf("[Config] Validation error active namespace %q does not exists", n.Active) + log.Error().Msgf("[Config] Validation failed active namespace %q does not exists. Resetting to default ns", n.Active) + n.Active = client.DefaultNamespace } - for _, ns := range n.Favorites { if ns != client.NamespaceAll && !c.IsValidNamespace(ns) { - log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) + log.Debug().Msgf("[Namespace] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) n.rmFavNS(ns) } } diff --git a/internal/config/files.go b/internal/config/files.go index 4913210f29..73ee51b6e1 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -6,7 +6,6 @@ package config import ( _ "embed" "os" - "os/user" "path/filepath" "github.com/derailed/k9s/internal/config/data" @@ -16,11 +15,11 @@ import ( ) const ( - // K9sConfigDir represents k9s configuration dir env var. - K9sConfigDir = "K9S_CONFIG_DIR" + // K9sEnvConfigDir represents k9s configuration dir env var. + K9sEnvConfigDir = "K9S_CONFIG_DIR" - // K9sLogsDir represents k9s logs dir env var. - K9sLogsDir = "K9S_LOGS_DIR" + // K9sEnvLogsDir represents k9s logs dir env var. + K9sEnvLogsDir = "K9S_LOGS_DIR" // AppName tracks k9s app name. AppName = "k9s" @@ -84,14 +83,21 @@ var ( // InitLogsLoc initializes K9s logs location. func InitLogLoc() error { var appLogDir string - if envDir := os.Getenv(K9sLogsDir); envDir != "" { - appLogDir = envDir - } else { - tmpDir, err := userTmpDir() + switch { + case isEnvSet(K9sEnvLogsDir): + appLogDir = os.Getenv(K9sEnvLogsDir) + case isEnvSet(K9sEnvConfigDir): + tmpDir, err := UserTmpDir() if err != nil { return err } appLogDir = tmpDir + default: + var err error + appLogDir, err = xdg.StateFile(AppName) + if err != nil { + return err + } } if err := data.EnsureFullPath(appLogDir, data.DefaultDirMod); err != nil { return err @@ -103,7 +109,7 @@ func InitLogLoc() error { // InitLocs initializes k9s artifacts locations. func InitLocs() error { - if hasK9sConfigEnv() { + if isEnvSet(K9sEnvConfigDir) { return initK9sEnvLocs() } @@ -111,7 +117,7 @@ func InitLocs() error { } func initK9sEnvLocs() error { - AppConfigDir = os.Getenv(K9sConfigDir) + AppConfigDir = os.Getenv(K9sEnvConfigDir) if err := data.EnsureFullPath(AppConfigDir, data.DefaultDirMod); err != nil { return err } @@ -269,20 +275,3 @@ func EnsureHotkeysCfgFile() (string, error) { func SkinFileFromName(n string) string { return filepath.Join(AppSkinsDir, n+".yaml") } - -// Helpers... - -func hasK9sConfigEnv() bool { - return os.Getenv(K9sConfigDir) != "" -} - -func userTmpDir() (string, error) { - u, err := user.Current() - if err != nil { - return "", err - } - - dir := filepath.Join(os.TempDir(), u.Username, AppName) - - return dir, nil -} diff --git a/internal/config/files_test.go b/internal/config/files_test.go index ca204e09ee..fb946ff2ff 100644 --- a/internal/config/files_test.go +++ b/internal/config/files_test.go @@ -5,17 +5,63 @@ package config_test import ( "os" + "path/filepath" "testing" + "github.com/adrg/xdg" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" "github.com/stretchr/testify/assert" ) +func TestInitLogLoc(t *testing.T) { + tmp, err := config.UserTmpDir() + assert.NoError(t, err) + + uu := map[string]struct { + dir string + e string + }{ + "log-env": { + dir: "/tmp/test/k9s/logs", + e: "/tmp/test/k9s/logs/k9s.log", + }, + "xdg-env": { + dir: "/tmp/test/xdg-state", + e: "/tmp/test/xdg-state/k9s/k9s.log", + }, + "cfg-env": { + dir: "/tmp/test/k9s-test", + e: filepath.Join(tmp, "k9s.log"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + os.Unsetenv(config.K9sEnvLogsDir) + os.Unsetenv("XDG_STATE_HOME") + os.Unsetenv(config.K9sEnvConfigDir) + switch k { + case "log-env": + os.Setenv(config.K9sEnvLogsDir, u.dir) + case "xdg-env": + os.Setenv("XDG_STATE_HOME", u.dir) + xdg.Reload() + case "cfg-env": + os.Setenv(config.K9sEnvConfigDir, u.dir) + } + err := config.InitLogLoc() + assert.NoError(t, err) + assert.Equal(t, u.e, config.AppLogFile) + assert.NoError(t, os.RemoveAll(config.AppLogFile)) + }) + } +} func TestEnsureBenchmarkCfg(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod)) assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod)) diff --git a/internal/config/flags.go b/internal/config/flags.go index 8e7a78db45..a1fc7699c3 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -14,9 +14,6 @@ const ( DefaultCommand = "" ) -// DefaultLogFile represents the default K9s log file. -// var DefaultLogFile = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", MustK9sUser())) - // Flags represents K9s configuration flags. type Flags struct { RefreshRate *int diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 0e8d0c2d1d..752644d7d3 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -4,13 +4,32 @@ package config import ( + "os" "os/user" + "path/filepath" "github.com/derailed/k9s/internal/config/data" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" ) +// isEnvSet checks if env var is set. +func isEnvSet(env string) bool { + return os.Getenv(env) != "" +} + +// UserTmpDir returns the temp dir with the current user name. +func UserTmpDir() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + + dir := filepath.Join(os.TempDir(), u.Username, AppName) + + return dir, nil +} + // InNSList check if ns is in an ns collection. func InNSList(nn []interface{}, ns string) bool { ss := make([]string, len(nn)) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index fd8bdae67b..56c8922368 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -50,12 +50,16 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { Thresholds: NewThreshold(), ShellPod: NewShellPod(), ImageScans: NewImageScans(), - dir: data.NewDir(AppContextsDir, conn, ks), + dir: data.NewDir(AppContextsDir), conn: conn, ks: ks, } } +func (k *K9s) resetConnection(conn client.Connection) { + k.conn = conn +} + // Save saves the k9s config to dis. func (k *K9s) Save() error { if k.activeConfig != nil { @@ -177,19 +181,20 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { if err != nil { return nil, err } - cfg, err := k.dir.Load(n, ct) + k.activeConfig, err = k.dir.Load(n, ct) if err != nil { return nil, err } - k.activeConfig = cfg // If the context specifies a default namespace, use it! if k.conn != nil { if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace { k.activeConfig.Context.Namespace.Active = ns + } else { + k.activeConfig.Context.Namespace.Active = client.DefaultNamespace } } - return cfg.Context, nil + return k.activeConfig.Context, nil } // OverrideRefreshRate set the refresh rate manually. @@ -319,4 +324,8 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.Thresholds = NewThreshold() } k.Thresholds.Validate(c, ks) + + if k.activeConfig != nil { + k.activeConfig.Validate(c, ks) + } } diff --git a/internal/ui/config.go b/internal/ui/config.go index 6ca8a4951a..11539ea3d8 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -116,10 +116,6 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error } }() - log.Debug().Msgf("SkinWatcher watching %q", config.K9sHome()) - if err := w.Add(config.K9sHome()); err != nil { - return err - } log.Debug().Msgf("SkinWatcher watching %q", config.AppSkinsDir) return w.Add(config.AppSkinsDir) } diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 9234131738..3a91554ccf 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -19,9 +19,9 @@ import ( ) func TestBenchConfig(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1") assert.NoError(t, error) @@ -29,9 +29,9 @@ func TestBenchConfig(t *testing.T) { } func TestSkinnedContext(t *testing.T) { - os.Setenv(config.K9sConfigDir, "/tmp/test-config") + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sConfigDir)) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) sf := filepath.Join("..", "config", "testdata", "black_and_wtf.yaml") raw, err := os.ReadFile(sf) diff --git a/internal/view/app.go b/internal/view/app.go index bbba78a28d..ab9431e760 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -407,6 +407,10 @@ func (a *App) refreshCluster(context.Context) error { } func (a *App) switchNS(ns string) error { + if a.Config.ActiveNamespace() == ns { + return nil + } + if ns == client.ClusterScope { ns = client.BlankNamespace } @@ -433,7 +437,7 @@ func (a *App) isValidNS(ns string) (bool, error) { } if !a.Conn().IsValidNamespace(ns) { - return false, fmt.Errorf("isvalidns - invalid namespace: %q", ns) + return false, fmt.Errorf("invalid namespace: %q", ns) } return true, nil @@ -445,16 +449,9 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { return nil } - log.Debug().Msgf("--> Switching Context %q--%q", name, a.Config.ActiveView()) a.Halt() defer a.Resume() { - p := cmd.NewInterpreter(a.Config.ActiveView()) - if p.IsContextCmd() { - a.Config.SetActiveView("pod") - } - p.ResetContextArg() - a.Config.Reset() ct, err := a.Config.K9s.ActivateContext(name) if err != nil { @@ -466,14 +463,26 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { if cns, ok := ci.NSArg(); ok { ct.Namespace.Active = cns } + + p := cmd.NewInterpreter(a.Config.ActiveView()) + p.ResetContextArg() + if p.IsContextCmd() { + a.Config.SetActiveView("pod") + } + ns := a.Config.ActiveNamespace() + if !a.Conn().IsValidNamespace(ns) { + ns = client.DefaultNamespace + if err := a.Config.SetActiveNamespace(ns); err != nil { + return err + } + } if err := a.Config.Save(); err != nil { log.Error().Err(err).Msg("config save failed!") } - - ns := a.Config.ActiveNamespace() a.initFactory(ns) - a.Flash().Infof("Switching context to %s", name) + log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView()) + a.Flash().Infof("Switching context to %q::%q", name, ns) a.ReloadStyles(name) a.gotoResource(a.Config.ActiveView(), "", true) a.clusterModel.Reset(a.factory) diff --git a/internal/view/browser.go b/internal/view/browser.go index 429e3e94bb..21176d69ba 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -133,7 +133,6 @@ func (b *Browser) SetInstance(path string) { // Start initializes browser updates. func (b *Browser) Start() { - b.app.Config.ValidateFavorites() ns := b.app.Config.ActiveNamespace() if n := b.GetModel().GetNamespace(); !client.IsClusterScoped(n) { ns = n diff --git a/internal/view/command.go b/internal/view/command.go index 0fdb7f7a8f..3604c89aa1 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -131,10 +131,6 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { if c.specialCmd(p) { return nil } - // if _, ok := c.alias.Check(p.Cmd()); !ok { - // return fmt.Errorf("command not found %q", p.Cmd()) - // } - gvr, v, err := c.viewMetaFor(p) if err != nil { return err diff --git a/internal/view/log.go b/internal/view/log.go index b6120bc6b2..88b0cd8bfe 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -16,6 +16,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/ui" @@ -420,19 +421,17 @@ func ensureDir(dir string) error { return os.MkdirAll(dir, 0744) } -func saveData(dir, fqn, data string) (string, error) { +func saveData(dir, fqn, logs string) (string, error) { if err := ensureDir(dir); err != nil { return "", err } - now := time.Now().UnixNano() - fName := fmt.Sprintf("%s-%d.log", strings.Replace(fqn, "/", "-", 1), now) - - path := filepath.Join(dir, fName) + f := fmt.Sprintf("%s-%d.log", fqn, time.Now().UnixNano()) + path := filepath.Join(dir, data.SanitizeFileName(f)) mod := os.O_CREATE | os.O_WRONLY file, err := os.OpenFile(path, mod, 0600) if err != nil { - log.Error().Err(err).Msgf("LogFile create %s", path) + log.Error().Err(err).Msgf("Log file save failed: %q", path) return "", nil } defer func() { @@ -440,7 +439,7 @@ func saveData(dir, fqn, data string) (string, error) { log.Error().Err(err).Msg("Closing Log file") } }() - if _, err := file.Write([]byte(data)); err != nil { + if _, err := file.WriteString(logs); err != nil { return "", err } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 342a0a8e1d..c83d786315 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.4' +version: 'v0.30.5' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From cbe13e8c63cc583cafc98e7a82443092be85cde2 Mon Sep 17 00:00:00 2001 From: Tobias Germer Date: Thu, 28 Dec 2023 18:36:34 +0100 Subject: [PATCH 054/169] add kubectl-blame plugin (#2338) * add kubectl-blame plugin * add install docs & credits for kubectl-blame * rename to plugins needed for v0.30.x --- plugins/blame.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 plugins/blame.yml diff --git a/plugins/blame.yml b/plugins/blame.yml new file mode 100644 index 0000000000..97220d2935 --- /dev/null +++ b/plugins/blame.yml @@ -0,0 +1,18 @@ +plugins: + # kubectl-blame by knight42 + # Annotate each line in the given resource's YAML with information from the managedFields to show who last modified the field. + # Source: https://github.com/knight42/kubectl-blame + # Install via: + # krew: `kubectl krew install blame` + # go: `go install github.com/knight42/kubectl-blame@latest` + blame: + shortCut: b + confirm: false + description: "Blame" + scopes: + - all + command: sh + background: false + args: + - -c + - "kubectl-blame $RESOURCE_NAME $NAME -n $NAMESPACE --context $CONTEXT | less" From 6a43167f1a30db50ed2acd4e3ffcfad25f657679 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Thu, 28 Dec 2023 14:16:59 -0700 Subject: [PATCH 055/169] K9s/release v0.30.6 (#2403) * fix #2401 * fix #2400 * fix #2387 * rel notes --- Makefile | 2 +- change_logs/release_v0.30.6.md | 43 ++++++++ cmd/root.go | 8 +- internal/client/client.go | 36 ++++--- internal/client/client_test.go | 137 ++++++++++++++++++++++++ internal/client/config_test.go | 26 +++++ internal/client/helper_test.go | 185 +++++++++++++++++++++++++++++++++ internal/config/data/ns.go | 11 +- internal/view/app.go | 8 +- internal/view/cmd/helpers.go | 1 + snap/snapcraft.yaml | 2 +- 11 files changed, 422 insertions(+), 37 deletions(-) create mode 100644 change_logs/release_v0.30.6.md diff --git a/Makefile b/Makefile index 7dc2db8daf..14a039da62 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.5 +VERSION ?= v0.30.6 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.6.md b/change_logs/release_v0.30.6.md new file mode 100644 index 0000000000..51050bd4df --- /dev/null +++ b/change_logs/release_v0.30.6.md @@ -0,0 +1,43 @@ + + +# Release v0.30.6 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## 🎄 Maintenance Release! 🎄 + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2401](https://github.com/derailed/k9s/issues/2401) Context completion broken with mixed case context names +* [#2400](https://github.com/derailed/k9s/issues/2400) Panic on start if dns lookup fails +* [#2387](https://github.com/derailed/k9s/issues/2387) Invalid namespace xxx - with feelings?? + +--- + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index 079ec37e45..4f1f484785 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,7 +93,7 @@ func run(cmd *cobra.Command, args []string) error { cfg, err := loadConfiguration() if err != nil { - return err + log.Error().Err(err).Msgf("load configuration failed") } app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { @@ -120,12 +120,11 @@ func loadConfiguration() (*config.Config, error) { k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("refine failed") - return nil, err } conn, err := client.InitConnection(k8sCfg) + k9sCfg.SetConnection(conn) if err != nil { - log.Error().Err(err).Msgf("failed to connect to context %q", k9sCfg.K9s.ActiveContextName()) - return nil, err + return k9sCfg, err } // Try to access server version if that fail. Connectivity issue? if !conn.CheckConnectivity() { @@ -134,7 +133,6 @@ func loadConfiguration() (*config.Config, error) { if !conn.ConnectionOK() { return nil, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) } - k9sCfg.SetConnection(conn) log.Info().Msg("✅ Kubernetes connectivity") if err := k9sCfg.Save(); err != nil { diff --git a/internal/client/client.go b/internal/client/client.go index 66bcf3fa6d..0cc6cf790b 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -30,6 +30,7 @@ const ( cacheExpiry = 5 * time.Minute cacheMXAPIKey = "metricsAPI" serverVersion = "serverVersion" + cacheNSKey = "validNamespaces" ) var supportedMetricsAPIVersions = []string{"v1beta1"} @@ -213,29 +214,28 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { } func (a *APIClient) IsValidNamespace(ns string) bool { - if IsAllNamespace(ns) { + if IsClusterWide(ns) || ns == NotNamespaced { return true } ok, err := a.CanI(ClusterScope, "v1/namespaces", []string{ListVerb}) - if !ok || err != nil { - cool, err := a.isValidNamespace(ns) - if err != nil { - log.Error().Err(err).Msgf("unable to assert valid namespace") - } - return cool + if ok && err == nil { + nn, _ := a.ValidNamespaceNames() + _, ok = nn[ns] + return ok } - nn, err := a.ValidNamespaceNames() - if err != nil { - return false + + ok, err = a.isValidNamespace(ns) + if ok && err == nil { + return ok } - _, ok = nn[ns] + log.Warn().Err(err).Msgf("namespace validation failed for: %q", ns) - return ok + return false } func (a *APIClient) cachedNamespaceNames() NamespaceNames { - cns, ok := a.cache.Get("validNamespaces") + cns, ok := a.cache.Get(cacheNSKey) if !ok { return make(NamespaceNames) } @@ -244,6 +244,10 @@ func (a *APIClient) cachedNamespaceNames() NamespaceNames { } func (a *APIClient) isValidNamespace(n string) (bool, error) { + if IsClusterWide(n) || n == NotNamespaced { + return true, nil + } + if a == nil { return false, errors.New("invalid client") } @@ -264,7 +268,7 @@ func (a *APIClient) isValidNamespace(n string) (bool, error) { return false, err } cnss[n] = struct{}{} - a.cache.Add("validNamespaces", cnss, cacheExpiry) + a.cache.Add(cacheNSKey, cnss, cacheExpiry) return true, nil } @@ -275,7 +279,7 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { return nil, fmt.Errorf("validNamespaces: no available client found") } - if nn, ok := a.cache.Get("validNamespaces"); ok { + if nn, ok := a.cache.Get(cacheNSKey); ok { if nss, ok := nn.(NamespaceNames); ok { return nss, nil } @@ -294,7 +298,7 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { for _, n := range nn.Items { nns[n.Name] = struct{}{} } - a.cache.Add("validNamespaces", nns, cacheExpiry) + a.cache.Add(cacheNSKey, nns, cacheExpiry) return nns, nil } diff --git a/internal/client/client_test.go b/internal/client/client_test.go index e777b29877..e5c17ddd71 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -8,8 +8,145 @@ import ( "time" "github.com/stretchr/testify/assert" + authorizationv1 "k8s.io/api/authorization/v1" ) +func TestMakeSAR(t *testing.T) { + uu := map[string]struct { + ns string + gvr GVR + sar *authorizationv1.SelfSubjectAccessReview + }{ + "all-pods": { + ns: NamespaceAll, + gvr: NewGVR("v1/pods"), + sar: &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: NamespaceAll, + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + "ns-pods": { + ns: "fred", + gvr: NewGVR("v1/pods"), + sar: &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: "fred", + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + "clusterscope-ns": { + ns: ClusterScope, + gvr: NewGVR("v1/namespaces"), + sar: &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Version: "v1", + Resource: "namespaces", + }, + }, + }, + }, + "subres-pods": { + ns: "fred", + gvr: NewGVR("v1/pods:logs"), + sar: &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: "fred", + Version: "v1", + Resource: "pods", + Subresource: "logs", + }, + }, + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr.String())) + }) + } +} + +func TestIsValidNamespace(t *testing.T) { + c := NewTestAPIClient() + + uu := map[string]struct { + ns string + cache NamespaceNames + ok bool + }{ + "all-ns": { + ns: NamespaceAll, + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + ok: true, + }, + "blank-ns": { + ns: BlankNamespace, + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + ok: true, + }, + "cluster-ns": { + ns: ClusterScope, + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + ok: true, + }, + "no-ns": { + ns: NotNamespaced, + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + ok: true, + }, + "default-ns": { + ns: DefaultNamespace, + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + ok: true, + }, + "valid-ns": { + ns: "fred", + cache: NamespaceNames{ + "fred": {}, + }, + ok: true, + }, + "invalid-ns": { + ns: "fred", + cache: NamespaceNames{ + DefaultNamespace: {}, + }, + }, + } + + expiry := 1 * time.Millisecond + for k := range uu { + u := uu[k] + c.cache.Add("validNamespaces", u.cache, expiry) + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.ok, c.IsValidNamespace(u.ns)) + }) + } +} + func TestCheckCacheBool(t *testing.T) { c := NewTestAPIClient() diff --git a/internal/client/config_test.go b/internal/client/config_test.go index 046e5dd69b..c92593aff8 100644 --- a/internal/client/config_test.go +++ b/internal/client/config_test.go @@ -7,6 +7,7 @@ import ( "errors" "os" "testing" + "time" "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog" @@ -18,6 +19,31 @@ func init() { zerolog.SetGlobalLevel(zerolog.FatalLevel) } +func TestCallTimeout(t *testing.T) { + uu := map[string]struct { + t string + e time.Duration + }{ + "custom": { + t: "1m", + e: 1 * time.Minute, + }, + "default": { + e: 15 * time.Second, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + flags := genericclioptions.NewConfigFlags(false) + flags.Timeout = &u.t + cfg := client.NewConfig(flags) + assert.Equal(t, u.e, cfg.CallTimeout()) + }) + } +} + func TestConfigCurrentContext(t *testing.T) { var kubeConfig = "./testdata/config" diff --git a/internal/client/helper_test.go b/internal/client/helper_test.go index 74e4988da9..6103e5eaba 100644 --- a/internal/client/helper_test.go +++ b/internal/client/helper_test.go @@ -8,8 +8,193 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestMetaFQN(t *testing.T) { + uu := map[string]struct { + meta metav1.ObjectMeta + e string + }{ + "empty": { + e: "-/", + }, + "full": { + meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"}, + e: "ns1/blee", + }, + "no-ns": { + meta: metav1.ObjectMeta{Name: "blee"}, + e: "-/blee", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.MetaFQN(u.meta)) + }) + } +} + +func TestCoFQN(t *testing.T) { + uu := map[string]struct { + meta metav1.ObjectMeta + co string + e string + }{ + "empty": { + e: "-/:", + }, + "full": { + meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"}, + co: "fred", + e: "ns1/blee:fred", + }, + "no-co": { + meta: metav1.ObjectMeta{Name: "blee", Namespace: "ns1"}, + e: "ns1/blee:", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.CoFQN(u.meta, u.co)) + }) + } +} + +func TestIsClusterScoped(t *testing.T) { + uu := map[string]struct { + ns string + e bool + }{ + "empty": {}, + "all": { + ns: client.NamespaceAll, + }, + "none": { + ns: client.BlankNamespace, + }, + "custom": { + ns: "fred", + }, + "scoped": { + ns: "-", + e: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.IsClusterScoped(u.ns)) + }) + } +} + +func TestIsNamespaced(t *testing.T) { + uu := map[string]struct { + ns string + e bool + }{ + "empty": {}, + "all": { + ns: client.NamespaceAll, + }, + "none": { + ns: client.BlankNamespace, + }, + "custom": { + ns: "fred", + e: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.IsNamespaced(u.ns)) + }) + } +} + +func TestIsAllNamespaces(t *testing.T) { + uu := map[string]struct { + ns string + e bool + }{ + "empty": { + e: true, + }, + "all": { + ns: client.NamespaceAll, + e: true, + }, + "none": { + ns: client.BlankNamespace, + e: true, + }, + "custom": { + ns: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.IsAllNamespaces(u.ns)) + }) + } +} + +func TestIsAllNamespace(t *testing.T) { + uu := map[string]struct { + ns string + e bool + }{ + "empty": {}, + "all": { + ns: client.NamespaceAll, + e: true, + }, + "custom": { + ns: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.IsAllNamespace(u.ns)) + }) + } +} + +func TestCleanseNamespace(t *testing.T) { + uu := map[string]struct { + ns, e string + }{ + "empty": {}, + "all": { + ns: client.NamespaceAll, + e: client.BlankNamespace, + }, + "custom": { + ns: "fred", + e: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, client.CleanseNamespace(u.ns)) + }) + } +} + func TestNamespaced(t *testing.T) { uu := []struct { p, ns, n string diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index e9e9379cb5..2800a45d91 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -40,18 +40,11 @@ func NewActiveNamespace(n string) *Namespace { // Validate validates a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { - if n.Active == client.BlankNamespace || c == nil { - n.Active = client.DefaultNamespace - } - if c == nil { + if c == nil || !c.IsValidNamespace(n.Active) { return } - if !n.isAllNamespaces() && !c.IsValidNamespace(n.Active) { - log.Error().Msgf("[Config] Validation failed active namespace %q does not exists. Resetting to default ns", n.Active) - n.Active = client.DefaultNamespace - } for _, ns := range n.Favorites { - if ns != client.NamespaceAll && !c.IsValidNamespace(ns) { + if !c.IsValidNamespace(ns) { log.Debug().Msgf("[Namespace] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) n.rmFavNS(ns) } diff --git a/internal/view/app.go b/internal/view/app.go index ab9431e760..e6bfd1a1c5 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -184,9 +184,9 @@ func (a *App) suggestCommand() model.SuggestionFunc { return a.cmdHistory.List() } - s = strings.ToLower(s) + ls := strings.ToLower(s) for _, k := range a.command.alias.Aliases.Keys() { - if suggest, ok := cmd.ShouldAddSuggest(s, k); ok { + if suggest, ok := cmd.ShouldAddSuggest(ls, k); ok { entries = append(entries, suggest) } } @@ -195,12 +195,10 @@ func (a *App) suggestCommand() model.SuggestionFunc { if err != nil { log.Error().Err(err).Msg("failed to list namespaces") } - entries = append(entries, cmd.SuggestSubCommand(s, namespaceNames, contextNames)...) if len(entries) == 0 { return nil } - entries.Sort() return } @@ -214,11 +212,11 @@ func (a *App) contextNames() ([]string, error) { if err != nil { return nil, err } - contextNames := make([]string, 0, len(contexts)) for ctxName := range contexts { contextNames = append(contextNames, ctxName) } + return contextNames, nil } diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go index 58b8335f68..6ccbcfb7c6 100644 --- a/internal/view/cmd/helpers.go +++ b/internal/view/cmd/helpers.go @@ -94,6 +94,7 @@ func SuggestSubCommand(command string, namespaces client.NamespaceNames, context } func completeNS(s string, nn client.NamespaceNames) []string { + s = strings.ToLower(s) var suggests []string if suggest, ok := ShouldAddSuggest(s, client.NamespaceAll); ok { suggests = append(suggests, suggest) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c83d786315..9c915a8b4c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.5' +version: 'v0.30.6' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 39d93223d9de6558850e52459a6bfea9b1e544ff Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 3 Jan 2024 01:20:18 +0800 Subject: [PATCH 056/169] add boundary check for args parser (#2415) --- internal/view/cmd/args.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go index 2f95cb2a07..d242941429 100644 --- a/internal/view/cmd/args.go +++ b/internal/view/cmd/args.go @@ -30,8 +30,9 @@ func newArgs(p *Interpreter, aa []string) args { args[contextKey] = a[1:] case strings.Index(a, fuzzyFlag) == 0: - i++ - args[filterKey] = strings.ToLower(strings.TrimSpace(aa[i])) + if i++; i < len(aa) { + args[filterKey] = strings.ToLower(strings.TrimSpace(aa[i])) + } case strings.Index(a, filterFlag) == 0: args[filterKey] = strings.ToLower(a[1:]) From a76632908386086368a2f6cee59ebfde8a361f60 Mon Sep 17 00:00:00 2001 From: Pavan Gudiwada <25551553+pavangudiwada@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:03:11 +0530 Subject: [PATCH 057/169] Add support for resource recommendations using Robusta KRR (#2399) * Adding Robusta KRR as a K9s plugin * Fixed file extension * Updated details for simplicity * Updated Author and dependency details --- plugins/README.md | 21 +++++++++++---------- plugins/resource-recommendations.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 plugins/resource-recommendations.yml diff --git a/plugins/README.md b/plugins/README.md index f49134e2e4..4afe2cf166 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -4,16 +4,17 @@ K9s plugins extend the tool to provide additional functionality via actions to f Following is an example of some of plugin files in this directory. Other files are not listed in this table. -| Plugin-Name | Description | Available on Views | Shortcut | Kubectl plugin, external dependencies | -|--------------------|------------------------------------------------------------------|--------------------|----------|---------------------------------------------------------------------------------------| -| debug-container.yml| Add [ephemeral debug container][1]
([nicolaka/netshoot][2]) | containers | Shift-d | | -| dive.yml | Dive image layers | containers | d | [Dive](https://github.com/wagoodman/dive) | -| get-all.yml | get all resources in a namespace | all | g | [Krew](https://krew.sigs.k8s.io/), [ketall](https://github.com/corneliusweig/ketall/) | -| job_suspend.yml | Suspends a running cronjob | cronjobs | Ctrl-s | | -| k3d_root_shell.yml | Root shell to k3d container | containers | Shift-s | [jq](https://stedolan.github.io/jq/) | -| log_stern.yml | View resource logs using stern | pods | Ctrl-l | | -| log_jq.yml | View resource logs using jq | pods | Ctrl-j | kubectl-plugins/kubectl-jq | -| log_full.yml | get full logs from pod/container | pods/containers | Ctrl-l | | +| Plugin-Name | Description | Available on Views | Shortcut | Kubectl plugin, external dependencies | +|--------------------|------------------------------------------------------------------|--------------------------|----------|---------------------------------------------------------------------------------------| +| debug-container.yml| Add [ephemeral debug container][1]
([nicolaka/netshoot][2]) | containers | Shift-d | | +| dive.yml | Dive image layers | containers | d | [Dive](https://github.com/wagoodman/dive) | +| get-all.yml | get all resources in a namespace | all | g | [Krew](https://krew.sigs.k8s.io/), [ketall](https://github.com/corneliusweig/ketall/) | +| job_suspend.yml | Suspends a running cronjob | cronjobs | Ctrl-s | | +| k3d_root_shell.yml | Root shell to k3d container | containers | Shift-s | [jq](https://stedolan.github.io/jq/) | +| resource-recommendations.yml | View recommendations for CPU/Memory requests based on historical data | deployments/daemonsets/statefulsets | Shift-k | [Robusta KRR](https://github.com/robusta-dev/krr) | +| log_stern.yml | View resource logs using stern | pods | Ctrl-l | | +| log_jq.yml | View resource logs using jq | pods | Ctrl-j | kubectl-plugins/kubectl-jq | +| log_full.yml | get full logs from pod/container | pods/containers | Ctrl-l | | [1]: https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/#ephemeral-container [2]: https://github.com/nicolaka/netshoot diff --git a/plugins/resource-recommendations.yml b/plugins/resource-recommendations.yml new file mode 100644 index 0000000000..b85518f60e --- /dev/null +++ b/plugins/resource-recommendations.yml @@ -0,0 +1,28 @@ +plugins: +# Author: Daniel Rubin +# Get recommendations for CPU/Memory requests and limits using Robusta KRR +# Requires Prometheus in the Cluster and Robusta KRR (https://github.com/robusta-dev/krr) on your system +# Open K9s in deployments/daemonsets/statefulsets view, then: +# Shift-K to get recommendations + krr: + shortCut: Shift-K + description: Get krr + scopes: + - deployments + - daemonsets + - statefulsets + command: bash + background: false + confirm: false + args: + - -c + - | + LABELS=$(kubectl get $RESOURCE_NAME $NAME -n $NAMESPACE --context $CONTEXT --show-labels | awk '{print $NF}' | awk '{if(NR>1)print}') + krr simple --cluster $CONTEXT --selector $LABELS + echo "Press 'q' to exit" + while : ; do + read -n 1 k <&1 + if [[ $k = q ]] ; then + break + fi + done \ No newline at end of file From d93041b18773ff5fbb56715a12fa62ee120aac6d Mon Sep 17 00:00:00 2001 From: braykov Date: Tue, 2 Jan 2024 19:41:57 +0200 Subject: [PATCH 058/169] Use dash as a standard word separator in skin names (#2411) Co-authored-by: Branimir Braykov --- skins/{black_and_wtf.yaml => black-and-wtf.yaml} | 0 skins/{in_the_navy.yaml => in-the-navy.yaml} | 0 skins/{one_dark.yaml => one-dark.yaml} | 0 skins/{rose_pine.yaml => rose-pine.yaml} | 0 skins/{solarized_dark.yaml => solarized-dark.yaml} | 0 skins/{solarized_light.yaml => solarized-light.yaml} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename skins/{black_and_wtf.yaml => black-and-wtf.yaml} (100%) rename skins/{in_the_navy.yaml => in-the-navy.yaml} (100%) rename skins/{one_dark.yaml => one-dark.yaml} (100%) rename skins/{rose_pine.yaml => rose-pine.yaml} (100%) rename skins/{solarized_dark.yaml => solarized-dark.yaml} (100%) rename skins/{solarized_light.yaml => solarized-light.yaml} (100%) diff --git a/skins/black_and_wtf.yaml b/skins/black-and-wtf.yaml similarity index 100% rename from skins/black_and_wtf.yaml rename to skins/black-and-wtf.yaml diff --git a/skins/in_the_navy.yaml b/skins/in-the-navy.yaml similarity index 100% rename from skins/in_the_navy.yaml rename to skins/in-the-navy.yaml diff --git a/skins/one_dark.yaml b/skins/one-dark.yaml similarity index 100% rename from skins/one_dark.yaml rename to skins/one-dark.yaml diff --git a/skins/rose_pine.yaml b/skins/rose-pine.yaml similarity index 100% rename from skins/rose_pine.yaml rename to skins/rose-pine.yaml diff --git a/skins/solarized_dark.yaml b/skins/solarized-dark.yaml similarity index 100% rename from skins/solarized_dark.yaml rename to skins/solarized-dark.yaml diff --git a/skins/solarized_light.yaml b/skins/solarized-light.yaml similarity index 100% rename from skins/solarized_light.yaml rename to skins/solarized-light.yaml From 982bf6a72873131773862779694520e1ef2dc7e8 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 2 Jan 2024 23:57:07 -0700 Subject: [PATCH 059/169] K9s/release v0.30.7 (#2416) * [Bug] Fix #2413 * [Bug] Fix #2414 * [Bug] Fix #2407 * cleaning up * Add config files watcher * rel notes --- Makefile | 2 +- change_logs/release_v0.30.7.md | 51 +++++++++++ internal/client/client.go | 6 +- internal/config/data/ns.go | 4 + internal/config/k9s.go | 17 +++- internal/config/mock/test_helpers.go | 10 +- internal/dao/helpers.go | 21 ++--- internal/dao/log_items.go | 4 +- internal/model/describe.go | 4 +- internal/model/helpers.go | 7 -- internal/model/helpers_test.go | 31 ------- internal/model/rev_values.go | 4 +- internal/model/text.go | 4 +- internal/model/values.go | 4 +- internal/model/yaml.go | 4 +- internal/render/helpers.go | 2 +- internal/render/helpers_test.go | 33 +++++-- internal/ui/app.go | 6 +- internal/ui/config.go | 126 +++++++++++++++++++++----- internal/ui/config_int_test.go | 58 ++++++++++++ internal/ui/config_test.go | 25 ++--- internal/ui/table.go | 14 +-- internal/ui/table_helper.go | 31 +------ internal/ui/table_helper_test.go | 5 +- internal/view/actions.go | 11 ++- internal/view/app.go | 19 ++-- internal/view/cmd/args.go | 12 ++- internal/view/cmd/args_test.go | 33 ++++++- internal/view/cmd/helpers.go | 7 +- internal/view/cmd/interpreter.go | 57 +++++++++--- internal/view/cmd/interpreter_test.go | 5 - internal/view/command.go | 11 ++- internal/view/exec.go | 46 +++++++--- internal/view/img_scan.go | 11 ++- internal/view/sanitizer.go | 6 +- internal/view/table.go | 4 +- internal/view/xray.go | 6 +- internal/vul/scanner.go | 1 + skins/axual.yaml | 2 +- snap/snapcraft.yaml | 2 +- 40 files changed, 474 insertions(+), 232 deletions(-) create mode 100644 change_logs/release_v0.30.7.md create mode 100644 internal/ui/config_int_test.go diff --git a/Makefile b/Makefile index 14a039da62..c2639ec4ad 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.6 +VERSION ?= v0.30.7 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.7.md b/change_logs/release_v0.30.7.md new file mode 100644 index 0000000000..b6025dfbea --- /dev/null +++ b/change_logs/release_v0.30.7.md @@ -0,0 +1,51 @@ + + +# Release v0.30.7 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2414](https://github.com/derailed/k9s/issues/2414) View pods with context filter, along with namespace filter, prompts an error if the namespace exists only in the desired context +* [#2413](https://github.com/derailed/k9s/issues/2413) Typing apply -f in command bar causes k9s to crash +* [#2407](https://github.com/derailed/k9s/issues/2407) Long-running background plugins block UI rendering + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2415](https://github.com/derailed/k9s/pull/2415) Add boundary check for args parser +* [#2411](https://github.com/derailed/k9s/pull/2411) Use dash as a standard word separator in skin names + + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/client/client.go b/internal/client/client.go index 0cc6cf790b..3a47816749 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -352,11 +352,7 @@ func (a *APIClient) Config() *Config { // HasMetrics checks if the cluster supports metrics. func (a *APIClient) HasMetrics() bool { - err := a.supportsMetricsResources() - if err != nil { - log.Debug().Msgf("Metrics server detect failed: %s", err) - } - return err == nil + return a.supportsMetricsResources() != nil } // DialLogs returns a handle to api server for logs. diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 2800a45d91..69ab9cd521 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -84,6 +84,10 @@ func (n *Namespace) addFavNS(ns string) { } func (n *Namespace) rmFavNS(ns string) { + if n.LockFavorites { + return + } + victim := -1 for i, f := range n.Favorites { if f == ns { diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 56c8922368..decac450a2 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -177,7 +177,7 @@ func (k *K9s) ActiveContext() (*data.Context, error) { // ActivateContext initializes the active context is not present. func (k *K9s) ActivateContext(n string) (*data.Context, error) { k.activeContextName = n - ct, err := k.ks.GetContext(k.activeContextName) + ct, err := k.ks.GetContext(n) if err != nil { return nil, err } @@ -197,6 +197,21 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { return k.activeConfig.Context, nil } +// Reload reloads the active config from disk. +func (k *K9s) Reload() error { + ct, err := k.ks.GetContext(k.activeContextName) + if err != nil { + return err + } + + k.activeConfig, err = k.dir.Load(k.activeContextName, ct) + if err != nil { + return err + } + + return nil +} + // OverrideRefreshRate set the refresh rate manually. func (k *K9s) OverrideRefreshRate(r int) { k.manualRefreshRate = r diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index 2fa94be082..49db4a24c6 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -104,11 +104,17 @@ func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) { return mm, nil } -type mockConnection struct{} +type mockConnection struct { + ct string +} func NewMockConnection() mockConnection { return mockConnection{} } +func NewMockConnectionWithContext(ct string) mockConnection { + return mockConnection{ct: ct} +} + func (m mockConnection) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil } @@ -155,7 +161,7 @@ func (m mockConnection) CheckConnectivity() bool { return false } func (m mockConnection) ActiveContext() string { - return "" + return m.ct } func (m mockConnection) ActiveNamespace() string { return "" diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index d837d6ead9..f4818f4863 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -9,8 +9,6 @@ import ( "math" "regexp" - "github.com/derailed/tview" - runewidth "github.com/mattn/go-runewidth" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -21,7 +19,7 @@ const defaultServiceAccount = "default" var ( inverseRx = regexp.MustCompile(`\A\!`) - fuzzyRx = regexp.MustCompile(`\A\-f`) + fuzzyRx = regexp.MustCompile(`\A-f\s?([\w-]+)\b`) ) func inList(ll []string, s string) bool { @@ -41,12 +39,14 @@ func IsInverseSelector(s string) bool { return inverseRx.MatchString(s) } -// IsFuzzySelector checks if filter is fuzzy or not. -func IsFuzzySelector(s string) bool { - if s == "" { - return false +// HasFuzzySelector checks if query is fuzzy. +func HasFuzzySelector(s string) (string, bool) { + mm := fuzzyRx.FindStringSubmatch(s) + if len(mm) != 2 { + return "", false } - return fuzzyRx.MatchString(s) + + return mm[1], true } func toPerc(v1, v2 float64) float64 { @@ -56,11 +56,6 @@ func toPerc(v1, v2 float64) float64 { return math.Round((v1 / v2) * 100) } -// Truncate a string to the given l and suffix ellipsis if needed. -func Truncate(str string, width int) string { - return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis)) -} - // ToYAML converts a resource to its YAML representation. func ToYAML(o runtime.Object, showManaged bool) (string, error) { if o == nil { diff --git a/internal/dao/log_items.go b/internal/dao/log_items.go index e84c874002..d8cd1707cf 100644 --- a/internal/dao/log_items.go +++ b/internal/dao/log_items.go @@ -174,8 +174,8 @@ func (l *LogItems) Filter(index int, q string, showTime bool) ([]int, [][]int, e if q == "" { return nil, nil, nil } - if IsFuzzySelector(q) { - mm, ii := l.fuzzyFilter(index, strings.TrimSpace(q[2:]), showTime) + if f, ok := HasFuzzySelector(q); ok { + mm, ii := l.fuzzyFilter(index, f, showTime) return mm, ii, nil } matches, indices, err := l.filterLogs(index, q, showTime) diff --git a/internal/model/describe.go b/internal/model/describe.go index 82ca20b41c..29c380caca 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -65,8 +65,8 @@ func (d *Describe) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if dao.IsFuzzySelector(q) { - return d.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + if f, ok := dao.HasFuzzySelector(q); ok { + return d.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) } diff --git a/internal/model/helpers.go b/internal/model/helpers.go index 0646b1ce0f..2173149472 100644 --- a/internal/model/helpers.go +++ b/internal/model/helpers.go @@ -9,8 +9,6 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/derailed/tview" - "github.com/mattn/go-runewidth" "github.com/sahilm/fuzzy" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,11 +26,6 @@ func FQN(ns, n string) string { return ns + "/" + n } -// Truncate a string to the given l and suffix ellipsis if needed. -func Truncate(str string, width int) string { - return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis)) -} - // NewExpBackOff returns a new exponential backoff timer. func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOffContext { bf := backoff.NewExponentialBackOff() diff --git a/internal/model/helpers_test.go b/internal/model/helpers_test.go index 813889e27e..69ed2529e5 100644 --- a/internal/model/helpers_test.go +++ b/internal/model/helpers_test.go @@ -33,34 +33,3 @@ func TestMetaFQN(t *testing.T) { }) } } - -func TestTruncate(t *testing.T) { - uu := map[string]struct { - data string - size int - e string - }{ - "same": { - data: "fred", - size: 4, - e: "fred", - }, - "small": { - data: "fred", - size: 10, - e: "fred", - }, - "larger": { - data: "fred", - size: 3, - e: "fr…", - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, model.Truncate(u.data, u.size)) - }) - } -} diff --git a/internal/model/rev_values.go b/internal/model/rev_values.go index e25ef7b410..08badab6d5 100644 --- a/internal/model/rev_values.go +++ b/internal/model/rev_values.go @@ -84,8 +84,8 @@ func (v *RevValues) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if dao.IsFuzzySelector(q) { - return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + if f, ok := dao.HasFuzzySelector(q); ok { + return v.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) } diff --git a/internal/model/text.go b/internal/model/text.go index d5c5fe893b..64e4d4f90a 100644 --- a/internal/model/text.go +++ b/internal/model/text.go @@ -111,8 +111,8 @@ func (t *Text) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if dao.IsFuzzySelector(q) { - return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + if f, ok := dao.HasFuzzySelector(q); ok { + return t.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) } diff --git a/internal/model/values.go b/internal/model/values.go index 890facd0b9..25870d5a3b 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -113,8 +113,8 @@ func (v *Values) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if dao.IsFuzzySelector(q) { - return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + if f, ok := dao.HasFuzzySelector(q); ok { + return v.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) } diff --git a/internal/model/yaml.go b/internal/model/yaml.go index 8ef8d64d1b..7e7dd1a243 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -74,8 +74,8 @@ func (y *YAML) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if dao.IsFuzzySelector(q) { - return y.fuzzyFilter(strings.TrimSpace(q[2:]), lines) + if f, ok := dao.HasFuzzySelector(q); ok { + return y.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) } diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 548912f8e0..bafe900a20 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -13,7 +13,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/vul" "github.com/derailed/tview" - runewidth "github.com/mattn/go-runewidth" + "github.com/mattn/go-runewidth" "github.com/rs/zerolog/log" "golang.org/x/text/language" "golang.org/x/text/message" diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 26eb18ca17..05b0f89d6b 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -224,18 +224,33 @@ func TestNa(t *testing.T) { } func TestTruncate(t *testing.T) { - uu := []struct { - s string - l int - e string + uu := map[string]struct { + data string + size int + e string }{ - {"fred", 3, "fr…"}, - {"fred", 2, "f…"}, - {"fred", 10, "fred"}, + "same": { + data: "fred", + size: 4, + e: "fred", + }, + "small": { + data: "fred", + size: 10, + e: "fred", + }, + "larger": { + data: "fred", + size: 3, + e: "fr…", + }, } - for _, u := range uu { - assert.Equal(t, u.e, Truncate(u.s, u.l)) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, Truncate(u.data, u.size)) + }) } } diff --git a/internal/ui/app.go b/internal/ui/app.go index b7b4d22816..1b843f9136 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -39,7 +39,7 @@ func NewApp(cfg *config.Config, context string) *App { flash: model.NewFlash(model.DefaultFlashDelay), cmdBuff: model.NewFishBuff(':', model.CommandBuffer), } - a.ReloadStyles(context) + a.ReloadStyles() a.views = map[string]tview.Primitive{ "menu": NewMenu(a.Styles), @@ -135,8 +135,8 @@ func (a *App) StylesChanged(s *config.Styles) { } // ReloadStyles reloads skin file. -func (a *App) ReloadStyles(context string) { - a.RefreshStyles(context) +func (a *App) ReloadStyles() { + a.RefreshStyles() } // Conn returns an api server connection. diff --git a/internal/ui/config.go b/internal/ui/config.go index 11539ea3d8..1ddb04f1fb 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -7,6 +7,7 @@ import ( "context" "errors" "os" + "path/filepath" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" @@ -82,8 +83,8 @@ func (c *Configurator) RefreshCustomViews() error { return c.CustomView.Load(config.AppViewsFile) } -// StylesWatcher watches for skin file changes. -func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error { +// SkinsDirWatcher watches for skin directory file changes. +func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) error { if !c.HasSkin() { return nil } @@ -100,7 +101,7 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod { log.Debug().Msgf("Skin changed: %s", c.skinFile) s.QueueUpdateDraw(func() { - c.RefreshStyles(c.Config.K9s.ActiveContextName()) + c.RefreshStyles() }) } case err := <-w.Errors: @@ -120,48 +121,127 @@ func (c *Configurator) StylesWatcher(ctx context.Context, s synchronizer) error return w.Add(config.AppSkinsDir) } -// RefreshStyles load for skin configuration changes. -func (c *Configurator) RefreshStyles(context string) { - cluster := "na" - if c.Config != nil { - if ct, err := c.Config.K9s.ActiveContext(); err == nil { - cluster = ct.ClusterName +// ConfigWatcher watches for skin settings changes. +func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error { + w, err := fsnotify.NewWatcher() + if err != nil { + return err + } + + go func() { + for { + select { + case evt := <-w.Events: + if evt.Has(fsnotify.Create) || evt.Has(fsnotify.Write) { + log.Debug().Msgf("ConfigWatcher file changed: %s -- %#v", evt.Name, evt.Op.String()) + if evt.Name == config.AppConfigFile { + if err := c.Config.Load(evt.Name); err != nil { + log.Error().Err(err).Msgf("Config reload failed") + } + } else { + if err := c.Config.K9s.Reload(); err != nil { + log.Error().Err(err).Msgf("Context config reload failed") + } + } + s.QueueUpdateDraw(func() { + c.RefreshStyles() + }) + } + case err := <-w.Errors: + log.Info().Err(err).Msg("ConfigWatcher failed") + return + case <-ctx.Done(): + log.Debug().Msg("ConfigWatcher CANCELED") + if err := w.Close(); err != nil { + log.Error().Err(err).Msg("Canceling ConfigWatcher") + } + return + } } + }() + + log.Debug().Msgf("ConfigWatcher watching: %q", config.AppConfigFile) + if err := w.Add(config.AppConfigFile); err != nil { + return err } - if bc, err := config.EnsureBenchmarksCfgFile(cluster, context); err != nil { - log.Warn().Err(err).Msgf("No benchmark config file found for context: %s", context) - } else { - c.BenchFile = bc + cl, ct, ok := c.activeConfig() + if !ok { + return nil } + ctConfigFile := filepath.Join(config.AppContextConfig(cl, ct)) + log.Debug().Msgf("ConfigWatcher watching: %q", ctConfigFile) + return w.Add(ctConfigFile) +} + +func (c *Configurator) activeSkin() (string, bool) { + var skin string + if c.Config == nil || c.Config.K9s == nil { + return skin, false + } + + if ct, err := c.Config.K9s.ActiveContext(); err == nil { + skin = ct.Skin + } + if skin == "" { + skin = c.Config.K9s.UI.Skin + } + + return skin, skin != "" +} + +func (c *Configurator) activeConfig() (cluster string, context string, ok bool) { + if c.Config == nil || c.Config.K9s == nil { + return + } + ct, err := c.Config.K9s.ActiveContext() + if err != nil { + return + } + cluster, context = ct.ClusterName, c.Config.K9s.ActiveContextName() + if cluster != "" && context != "" { + ok = true + } + + return +} + +// RefreshStyles load for skin configuration changes. +func (c *Configurator) RefreshStyles() { if c.Styles == nil { c.Styles = config.NewStyles() } else { c.Styles.Reset() } - var skin string - if c.Config != nil { - skin = c.Config.K9s.UI.Skin - if ct, err := c.Config.K9s.ActiveContext(); err != nil { - log.Warn().Msgf("No active context found. Using default skin") - } else if ct.Skin != "" { - skin = ct.Skin - } + cl, ct, ok := c.activeConfig() + if !ok { + return } - if skin == "" { + + if bc, err := config.EnsureBenchmarksCfgFile(cl, ct); err != nil { + log.Warn().Err(err).Msgf("No benchmark config file found: %q@%q", cl, ct) + } else { + c.BenchFile = bc + } + + skin, ok := c.activeSkin() + if !ok { + log.Debug().Msgf("No custom skin found. Loading default") c.updateStyles("") return } - var skinFile = config.SkinFileFromName(skin) + skinFile := config.SkinFileFromName(skin) if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.AppSkinsDir) } else { log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err) } + c.updateStyles("") } else { + log.Debug().Msgf("Loading skin file: %q", skinFile) c.updateStyles(skinFile) } } diff --git a/internal/ui/config_int_test.go b/internal/ui/config_int_test.go new file mode 100644 index 0000000000..8f4c9d62eb --- /dev/null +++ b/internal/ui/config_int_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package ui + +import ( + "os" + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/mock" + "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +func Test_activeConfig(t *testing.T) { + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) + + cl, ct := "cl-1", "ct-1-1" + uu := map[string]struct { + cl, ct string + cfg *Configurator + ok bool + }{ + "empty": { + cfg: &Configurator{}, + }, + + "plain": { + cfg: &Configurator{Config: config.NewConfig( + mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }))}, + cl: cl, + ct: ct, + ok: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cfg := u.cfg + if cfg.Config != nil { + _, err := cfg.Config.K9s.ActivateContext(ct) + assert.NoError(t, err) + } + cl, ct, ok := cfg.activeConfig() + assert.Equal(t, u.ok, ok) + if ok { + assert.Equal(t, u.cl, cl) + assert.Equal(t, u.ct, ct) + } + }) + } +} diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 3a91554ccf..b8a81c27c4 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -18,18 +18,9 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" ) -func TestBenchConfig(t *testing.T) { - os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") - assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) - - bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1") - assert.NoError(t, error) - assert.Equal(t, "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", bc) -} - func TestSkinnedContext(t *testing.T) { os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) @@ -50,10 +41,22 @@ func TestSkinnedContext(t *testing.T) { cfg.Config.K9s = config.NewK9s( mock.NewMockConnection(), mock.NewMockKubeSettings(&flags)) + _, err = cfg.Config.K9s.ActivateContext("ct-1-1") + assert.NoError(t, err) cfg.Config.K9s.UI = config.UI{Skin: "black_and_wtf"} - cfg.RefreshStyles("ct-1") + cfg.RefreshStyles() assert.True(t, cfg.HasSkin()) assert.Equal(t, tcell.ColorGhostWhite.TrueColor(), render.StdColor) assert.Equal(t, tcell.ColorWhiteSmoke.TrueColor(), render.ErrColor) } + +func TestBenchConfig(t *testing.T) { + os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") + assert.NoError(t, config.InitLocs()) + defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) + + bc, error := config.EnsureBenchmarksCfgFile("cl-1", "ct-1") + assert.NoError(t, error) + assert.Equal(t, "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", bc) +} diff --git a/internal/ui/table.go b/internal/ui/table.go index acf4d866ba..4f51a2f97b 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/vul" @@ -407,14 +408,13 @@ func (t *Table) filtered(data *render.TableData) *render.TableData { } q := t.cmdBuff.GetText() - if IsFuzzySelector(q) { - return fuzzyFilter(q[2:], filtered) + if f, ok := dao.HasFuzzySelector(q); ok { + return fuzzyFilter(f, filtered) } - filtered, err := rxFilter(q, IsInverseSelector(q), filtered) + filtered, err := rxFilter(q, dao.IsInverseSelector(q), filtered) if err != nil { - log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp") - // t.cmdBuff.ClearText(true) + log.Error().Err(errors.New("invalid filter expression")).Msg("Regexp") } return filtered @@ -471,9 +471,9 @@ func (t *Table) styleTitle() string { buff := t.cmdBuff.GetText() if IsLabelSelector(buff) { - buff = truncate(TrimLabelSelector(buff), maxTruncate) + buff = render.Truncate(TrimLabelSelector(buff), maxTruncate) } else if l := t.GetModel().GetLabelFilter(); l != "" { - buff = truncate(l, maxTruncate) + buff = render.Truncate(l, maxTruncate) } if buff == "" { diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 68650b7c64..bd6ea48118 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -42,9 +42,7 @@ const ( var ( // LabelRx identifies a label query. - LabelRx = regexp.MustCompile(`\A\-l`) - inverseRx = regexp.MustCompile(`\A\!`) - fuzzyRx = regexp.MustCompile(`\A\-f`) + LabelRx = regexp.MustCompile(`\A\-l`) ) func mustExtractStyles(ctx context.Context) *config.Styles { @@ -67,9 +65,6 @@ func TrimCell(tv *SelectTable, row, col int) string { // IsLabelSelector checks if query is a label query. func IsLabelSelector(s string) bool { - if s == "" { - return false - } if LabelRx.MatchString(s) { return true } @@ -77,22 +72,6 @@ func IsLabelSelector(s string) bool { return !strings.Contains(s, " ") && cmd.ToLabels(s) != nil } -// IsFuzzySelector checks if query is fuzzy. -func IsFuzzySelector(s string) bool { - if s == "" { - return false - } - return fuzzyRx.MatchString(s) -} - -// IsInverseSelector checks if inverse char has been provided. -func IsInverseSelector(s string) bool { - if s == "" { - return false - } - return inverseRx.MatchString(s) -} - // TrimLabelSelector extracts label query. func TrimLabelSelector(s string) string { if strings.Index(s, "-l") == 0 { @@ -102,14 +81,6 @@ func TrimLabelSelector(s string) string { return s } -func truncate(s string, max int) string { - if len(s) < max { - return s - } - - return s[:max] + "..." -} - // SkinTitle decorates a title. func SkinTitle(fmat string, style config.Frame) string { bgColor := style.Title.BgColor diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index dc86cd396c..219ffaa3d3 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -6,6 +6,7 @@ package ui import ( "testing" + "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) @@ -16,7 +17,7 @@ func TestTruncate(t *testing.T) { "empty": {}, "max": { s: "/app.kubernetes.io/instance=prom,app.kubernetes.io/name=prometheus,app.kubernetes.io/component=server", - e: "/app.kubernetes.io/instance=prom,app.kubernetes.io...", + e: "/app.kubernetes.io/instance=prom,app.kubernetes.i…", }, "less": { s: "app=fred,env=blee", @@ -27,7 +28,7 @@ func TestTruncate(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, truncate(u.s, 50)) + assert.Equal(t, u.e, render.Truncate(u.s, 50)) }) } } diff --git a/internal/view/actions.go b/internal/view/actions.go index 324e623318..7ce1e6da6b 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -142,9 +142,9 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { pipes: p.Pipes, args: args, } - suspend, errChan := run(r.App(), opts) + suspend, errChan, statusChan := run(r.App(), opts) if !suspend { - r.App().Flash().Info("Plugin command failed!") + r.App().Flash().Infof("Plugin command failed: %q", p.Description) return } var errs error @@ -155,7 +155,12 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { r.App().cowCmd(errs.Error()) return } - r.App().Flash().Info("Plugin command launched successfully!") + go func() { + for st := range statusChan { + r.App().Flash().Infof("Plugin command launched successfully: %q", st) + } + }() + } if p.Confirm { msg := fmt.Sprintf("Run?\n%s %s", p.Command, strings.Join(args, " ")) diff --git a/internal/view/app.go b/internal/view/app.go index e6bfd1a1c5..20ceb2c897 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -324,8 +324,12 @@ func (a *App) Resume() { ctx, a.cancelFn = context.WithCancel(context.Background()) go a.clusterUpdater(ctx) - if err := a.StylesWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("Styles watcher failed") + if err := a.ConfigWatcher(ctx, a); err != nil { + log.Warn().Err(err).Msgf("ConfigWatcher failed") + } + + if err := a.SkinsDirWatcher(ctx, a); err != nil { + log.Warn().Err(err).Msgf("SkinsWatcher failed") } if err := a.CustomViewsWatcher(ctx, a); err != nil { log.Warn().Err(err).Msgf("CustomView watcher failed") @@ -408,17 +412,9 @@ func (a *App) switchNS(ns string) error { if a.Config.ActiveNamespace() == ns { return nil } - if ns == client.ClusterScope { ns = client.BlankNamespace } - ok, err := a.isValidNS(ns) - if err != nil { - return err - } - if !ok { - return fmt.Errorf("switchns - invalid namespace: %q", ns) - } if err := a.Config.SetActiveNamespace(ns); err != nil { return err } @@ -469,6 +465,7 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { } ns := a.Config.ActiveNamespace() if !a.Conn().IsValidNamespace(ns) { + a.Flash().Errf("Unable to validate namespace %q. Using %q namespace", ns, client.DefaultNamespace) ns = client.DefaultNamespace if err := a.Config.SetActiveNamespace(ns); err != nil { return err @@ -481,7 +478,7 @@ func (a *App) switchContext(ci *cmd.Interpreter) error { log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView()) a.Flash().Infof("Switching context to %q::%q", name, ns) - a.ReloadStyles(name) + a.ReloadStyles() a.gotoResource(a.Config.ActiveView(), "", true) a.clusterModel.Reset(a.factory) } diff --git a/internal/view/cmd/args.go b/internal/view/cmd/args.go index d242941429..a594cf55ea 100644 --- a/internal/view/cmd/args.go +++ b/internal/view/cmd/args.go @@ -11,6 +11,7 @@ const ( nsKey = "ns" topicKey = "topic" filterKey = "filter" + fuzzyKey = "fuzzy" labelKey = "labels" contextKey = "context" ) @@ -30,8 +31,12 @@ func newArgs(p *Interpreter, aa []string) args { args[contextKey] = a[1:] case strings.Index(a, fuzzyFlag) == 0: - if i++; i < len(aa) { - args[filterKey] = strings.ToLower(strings.TrimSpace(aa[i])) + if a == fuzzyFlag { + if i++; i < len(aa) { + args[fuzzyKey] = strings.ToLower(strings.TrimSpace(aa[i])) + } + } else { + args[fuzzyKey] = strings.ToLower(a[2:]) } case strings.Index(a, filterFlag) == 0: @@ -67,7 +72,8 @@ func newArgs(p *Interpreter, aa []string) args { func (a args) hasFilters() bool { _, fok := a[filterKey] + _, zok := a[fuzzyKey] _, lok := a[labelKey] - return fok || lok + return fok || zok || lok } diff --git a/internal/view/cmd/args_test.go b/internal/view/cmd/args_test.go index 8efe229ef3..9c1a7e2a28 100644 --- a/internal/view/cmd/args_test.go +++ b/internal/view/cmd/args_test.go @@ -42,7 +42,12 @@ func TestFlagsNew(t *testing.T) { "fuzzy-filter": { i: NewInterpreter("po"), aa: []string{"-f", "fred"}, - ll: args{filterKey: "fred"}, + ll: args{fuzzyKey: "fred"}, + }, + "fuzzy-filter-nospace": { + i: NewInterpreter("po"), + aa: []string{"-ffred"}, + ll: args{fuzzyKey: "fred"}, }, "filter+ns": { i: NewInterpreter("po"), @@ -72,23 +77,43 @@ func TestFlagsNew(t *testing.T) { "full-monty": { i: NewInterpreter("po"), aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg"}, - ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1"}, + ll: args{ + filterKey: "zorg", + fuzzyKey: "blee", + labelKey: "app=fred", + nsKey: "ns1", + }, }, "full-monty+ctx": { i: NewInterpreter("po"), aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg", "@ctx1"}, - ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1", contextKey: "ctx1"}, + ll: args{ + filterKey: "zorg", + fuzzyKey: "blee", + labelKey: "app=fred", + nsKey: "ns1", + contextKey: "ctx1", + }, }, "caps": { i: NewInterpreter("po"), aa: []string{"app=fred", "ns1", "-f", "blee", "/zorg", "@Dev"}, - ll: args{filterKey: "zorg", labelKey: "app=fred", nsKey: "ns1", contextKey: "Dev"}, + ll: args{ + filterKey: "zorg", + fuzzyKey: "blee", + labelKey: "app=fred", + nsKey: "ns1", + contextKey: "Dev"}, }, "ctx": { i: NewInterpreter("ctx"), aa: []string{"Dev"}, ll: args{contextKey: "Dev"}, }, + "bork": { + i: NewInterpreter("apply -f"), + ll: args{}, + }, } for k := range uu { diff --git a/internal/view/cmd/helpers.go b/internal/view/cmd/helpers.go index 6ccbcfb7c6..9ceec0dae8 100644 --- a/internal/view/cmd/helpers.go +++ b/internal/view/cmd/helpers.go @@ -11,8 +11,10 @@ import ( ) func ToLabels(s string) map[string]string { - ll := strings.Split(s, ",") - lbls := make(map[string]string, len(ll)) + var ( + ll = strings.Split(s, ",") + lbls = make(map[string]string, len(ll)) + ) for _, l := range ll { kv := strings.Split(l, "=") if len(kv) < 2 || kv[0] == "" || kv[1] == "" { @@ -20,7 +22,6 @@ func ToLabels(s string) map[string]string { } lbls[kv[0]] = kv[1] } - if len(lbls) == 0 { return nil } diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go index b95149c5b9..e47a911d30 100644 --- a/internal/view/cmd/interpreter.go +++ b/internal/view/cmd/interpreter.go @@ -7,12 +7,14 @@ import ( "strings" ) +// Interpreter tracks user prompt input. type Interpreter struct { line string cmd string args args } +// NewInterpreter returns a new instance. func NewInterpreter(s string) *Interpreter { c := Interpreter{ line: s, @@ -32,20 +34,24 @@ func (c *Interpreter) grok() { c.args = newArgs(c, ff[1:]) } +// HasNS returns true if ns is present in prompt. func (c *Interpreter) HasNS() bool { ns, ok := c.args[nsKey] return ok && ns != "" } +// Cmd returns the command. func (c *Interpreter) Cmd() string { return c.cmd } +// IsBlank returns true if prompt is empty. func (c *Interpreter) IsBlank() bool { return c.line == "" } +// Amend merges prompts. func (c *Interpreter) Amend(c1 *Interpreter) { c.cmd = c1.cmd if c.args == nil { @@ -58,6 +64,7 @@ func (c *Interpreter) Amend(c1 *Interpreter) { } } +// Reset resets with new command. func (c *Interpreter) Reset(s string) *Interpreter { c.line = s c.grok() @@ -65,50 +72,60 @@ func (c *Interpreter) Reset(s string) *Interpreter { return c } +// GetLine teturns the prompt. func (c *Interpreter) GetLine() string { return strings.TrimSpace(c.line) } +// IsCowCmd returns true if cow cmd is detected. func (c *Interpreter) IsCowCmd() bool { return c.cmd == cowCmd } +// IsHelpCmd returns true if help cmd is detected. func (c *Interpreter) IsHelpCmd() bool { _, ok := helpCmd[c.cmd] return ok } +// IsBailCmd returns true if quit cmd is detected. func (c *Interpreter) IsBailCmd() bool { _, ok := bailCmd[c.cmd] return ok } +// IsAliasCmd returns true if alias cmd is detected. func (c *Interpreter) IsAliasCmd() bool { _, ok := aliasCmd[c.cmd] return ok } +// IsXrayCmd returns true if xray cmd is detected. func (c *Interpreter) IsXrayCmd() bool { _, ok := xrayCmd[c.cmd] return ok } +// IsContextCmd returns true if context cmd is detected. func (c *Interpreter) IsContextCmd() bool { _, ok := contextCmd[c.cmd] return ok } +// IsDirCmd returns true if dir cmd is detected. func (c *Interpreter) IsDirCmd() bool { _, ok := dirCmd[c.cmd] return ok } +// IsRBACCmd returns true if rbac cmd is detected. func (c *Interpreter) IsRBACCmd() bool { return c.cmd == canCmd } +// ContextArg returns context cmd arg. func (c *Interpreter) ContextArg() (string, bool) { if !c.IsContextCmd() { return "", false @@ -117,26 +134,32 @@ func (c *Interpreter) ContextArg() (string, bool) { return c.args[contextKey], true } +// ResetContextArg deletes context arg. func (c *Interpreter) ResetContextArg() { delete(c.args, contextFlag) } +// DirArg returns the directory is present. func (c *Interpreter) DirArg() (string, bool) { - if !c.IsDirCmd() || c.args[topicKey] == "" { + if !c.IsDirCmd() { return "", false } + d, ok := c.args[topicKey] - return c.args[topicKey], true + return d, ok && d != "" } +// CowArg returns the cow message. func (c *Interpreter) CowArg() (string, bool) { - if !c.IsCowCmd() || c.args[nsKey] == "" { + if !c.IsCowCmd() { return "", false } + m, ok := c.args[nsKey] - return c.args[nsKey], true + return m, ok && m != "" } +// RBACArgs returns the subject and topic is any. func (c *Interpreter) RBACArgs() (string, string, bool) { if !c.IsRBACCmd() { return "", "", false @@ -149,6 +172,7 @@ func (c *Interpreter) RBACArgs() (string, string, bool) { return tt[1], tt[2], true } +// XRayArgs return the gvr and ns if any. func (c *Interpreter) XrayArgs() (string, string, bool) { if !c.IsXrayCmd() { return "", "", false @@ -169,32 +193,37 @@ func (c *Interpreter) XrayArgs() (string, string, bool) { } } +// FilterArg returns the current filter if any. func (c *Interpreter) FilterArg() (string, bool) { f, ok := c.args[filterKey] - return f, ok + return f, ok && f != "" } +// FuzzyArg returns the fuzzy filter if any. +func (c *Interpreter) FuzzyArg() (string, bool) { + f, ok := c.args[fuzzyKey] + + return f, ok && f != "" +} + +// NSArg returns the current ns if any. func (c *Interpreter) NSArg() (string, bool) { ns, ok := c.args[nsKey] - return ns, ok + return ns, ok && ns != "" } +// HasContext returns the current context if any. func (c *Interpreter) HasContext() (string, bool) { ctx, ok := c.args[contextKey] - if !ok || ctx == "" { - return "", false - } - return ctx, ok + return ctx, ok && ctx != "" } +// LabelsArg return the labels map if any. func (c *Interpreter) LabelsArg() (map[string]string, bool) { ll, ok := c.args[labelKey] - if !ok { - return nil, false - } - return ToLabels(ll), true + return ToLabels(ll), ok } diff --git a/internal/view/cmd/interpreter_test.go b/internal/view/cmd/interpreter_test.go index cc5c9ca989..7266043dff 100644 --- a/internal/view/cmd/interpreter_test.go +++ b/internal/view/cmd/interpreter_test.go @@ -317,11 +317,6 @@ func TestContextCmd(t *testing.T) { ctx string }{ "empty": {}, - "plain": { - cmd: "context", - ok: true, - ctx: "", - }, "happy-full": { cmd: "context ctx1", ok: true, diff --git a/internal/view/command.go b/internal/view/command.go index 3604c89aa1..30f2e3cb2f 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -79,13 +79,13 @@ func allowedXRay(gvr client.GVR) bool { } func (c *Command) contextCmd(p *cmd.Interpreter) error { - ctx, ok := p.ContextArg() + ct, ok := p.ContextArg() if !ok { return fmt.Errorf("invalid command use `context xxx`") } - if ctx != "" { - return useContext(c.app, ctx) + if ct != "" { + return useContext(c.app, ct) } gvr, v, err := c.viewMetaFor(p) @@ -93,7 +93,7 @@ func (c *Command) contextCmd(p *cmd.Interpreter) error { return err } - return c.exec(p, gvr, c.componentFor(gvr, ctx, v), true) + return c.exec(p, gvr, c.componentFor(gvr, ct, v), true) } func (c *Command) xrayCmd(p *cmd.Interpreter) error { @@ -169,6 +169,9 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { if f, ok := p.FilterArg(); ok { co.SetFilter(f) } + if f, ok := p.FuzzyArg(); ok { + co.SetFilter("-f " + f) + } if ll, ok := p.LabelsArg(); ok { co.SetLabelFilter(ll) } diff --git a/internal/view/exec.go b/internal/view/exec.go index ed54d98888..effe8c9502 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -16,6 +16,8 @@ import ( "syscall" "time" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" @@ -75,7 +77,7 @@ func runK(a *App, opts shellOpts) error { } opts.binary = bin - suspended, errChan := run(a, opts) + suspended, errChan, _ := run(a, opts) if !suspended { return fmt.Errorf("unable to run command") } @@ -87,28 +89,29 @@ func runK(a *App, opts shellOpts) error { return errs } -func run(a *App, opts shellOpts) (bool, chan error) { +func run(a *App, opts shellOpts) (bool, chan error, chan string) { errChan := make(chan error, 1) + statusChan := make(chan string, 1) if opts.background { - if err := execute(opts); err != nil { + if err := execute(opts, statusChan); err != nil { errChan <- err a.Flash().Errf("Exec failed %q: %s", opts, err) } close(errChan) - return true, errChan + return true, errChan, statusChan } a.Halt() defer a.Resume() return a.Suspend(func() { - if err := execute(opts); err != nil { + if err := execute(opts, statusChan); err != nil { errChan <- err a.Flash().Errf("Exec failed %q: %s", opts, err) } close(errChan) - }), errChan + }), errChan, statusChan } func edit(a *App, opts shellOpts) bool { @@ -122,18 +125,20 @@ func edit(a *App, opts shellOpts) bool { } opts.binary, opts.background = bin, false - suspended, errChan := run(a, opts) + suspended, errChan, _ := run(a, opts) if !suspended { a.Flash().Errf("edit command failed") } + status := true for e := range errChan { a.Flash().Err(e) - return false + status = false } - return true + + return status } -func execute(opts shellOpts) error { +func execute(opts shellOpts, statusChan chan<- string) error { if opts.clear { clearScreen() } @@ -174,7 +179,7 @@ func execute(opts shellOpts) error { } var o, e bytes.Buffer - err := pipe(ctx, opts, &o, &e, cmds...) + err := pipe(ctx, opts, statusChan, &o, &e, cmds...) if err != nil { log.Err(err).Msgf("Command failed") return errors.Join(err, fmt.Errorf("%s", e.String())) @@ -458,7 +463,7 @@ func asResource(r config.Limits) v1.ResourceRequirements { } } -func pipe(_ context.Context, opts shellOpts, w, e io.Writer, cmds ...*exec.Cmd) error { +func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.Writer, cmds ...*exec.Cmd) error { if len(cmds) == 0 { return nil } @@ -466,8 +471,17 @@ func pipe(_ context.Context, opts shellOpts, w, e io.Writer, cmds ...*exec.Cmd) if len(cmds) == 1 { cmd := cmds[0] if opts.background { - cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, w, e - return cmd.Run() + go func() { + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, w, e + if err := cmd.Run(); err != nil { + log.Error().Err(err).Msgf("Command failed: %s", err) + } else { + statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20)) + log.Info().Msgf("Command completed successfully: %q", cmd.String()) + } + close(statusChan) + }() + return nil } cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr _, _ = cmd.Stdout.Write([]byte(opts.banner)) @@ -475,6 +489,10 @@ func pipe(_ context.Context, opts shellOpts, w, e io.Writer, cmds ...*exec.Cmd) log.Debug().Msgf("Running Start") err := cmd.Run() log.Debug().Msgf("Running Done: %s", err) + if err == nil { + statusChan <- fmt.Sprintf("Command completed successfully: %q", cmd.String()) + } + close(statusChan) return err } diff --git a/internal/view/img_scan.go b/internal/view/img_scan.go index edc6501129..f4f5290b8f 100644 --- a/internal/view/img_scan.go +++ b/internal/view/img_scan.go @@ -4,6 +4,7 @@ package view import ( + "errors" "runtime" "strings" @@ -68,7 +69,7 @@ func (s *ImageScan) viewCVE(app *App, _ ui.Tabular, _ client.GVR, path string) { } site += cve - ok, errChan := run(app, shellOpts{ + ok, errChan, _ := run(app, shellOpts{ background: true, binary: bin, args: []string{site}, @@ -77,9 +78,11 @@ func (s *ImageScan) viewCVE(app *App, _ ui.Tabular, _ client.GVR, path string) { app.Flash().Errf("unable to run browser command") return } + var errs error for e := range errChan { - if e != nil { - app.Flash().Err(e) - } + errs = errors.Join(e) + } + if errs != nil { + app.Flash().Err(errs) } } diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index bae27e7fa0..51388cebec 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -243,11 +243,11 @@ func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode { } s.UpdateTitle() - if ui.IsFuzzySelector(q) { - return root.Filter(q, fuzzyFilter) + if f, ok := dao.HasFuzzySelector(q); ok { + return root.Filter(f, fuzzyFilter) } - if ui.IsInverseSelector(q) { + if dao.IsInverseSelector(q) { return root.Filter(q, rxInverseFilter) } diff --git a/internal/view/table.go b/internal/view/table.go index 7e24dcf816..8a38693382 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -5,12 +5,14 @@ package view import ( "context" + "path/filepath" "strings" "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" @@ -173,7 +175,7 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { if path, err := saveTable(t.app.Config.K9s.ActiveScreenDumpsDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { t.app.Flash().Err(err) } else { - t.app.Flash().Infof("File %s saved successfully!", path) + t.app.Flash().Infof("File saved successfully: %q", render.Truncate(filepath.Base(path), 50)) } return nil diff --git a/internal/view/xray.go b/internal/view/xray.go index 26c9caff77..564ed188f5 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -478,11 +478,11 @@ func (x *Xray) filter(root *xray.TreeNode) *xray.TreeNode { } x.UpdateTitle() - if ui.IsFuzzySelector(q) { - return root.Filter(q, fuzzyFilter) + if f, ok := dao.HasFuzzySelector(q); ok { + return root.Filter(f, fuzzyFilter) } - if ui.IsInverseSelector(q) { + if dao.IsInverseSelector(q) { return root.Filter(q, rxInverseFilter) } diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index 64cbfc5794..c22567036b 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -67,6 +67,7 @@ func (s *imageScanner) GetScan(img string) (*Scan, bool) { func (s *imageScanner) setScan(img string, sc *Scan) { s.mx.Lock() defer s.mx.Unlock() + s.scans[img] = sc } diff --git a/skins/axual.yaml b/skins/axual.yaml index bc7b8701df..816eccc2cf 100644 --- a/skins/axual.yaml +++ b/skins/axual.yaml @@ -111,7 +111,7 @@ k9s: indicator: fgColor: *red bgColor: *blue - toggleOnColor: *green + toggleOnColor: *yellow toggleOffColor: *grey # Chart drawing diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9c915a8b4c..7515aa753a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.6' +version: 'v0.30.7' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 1c19ef6ad6b59f3afcc9d9ee8397dc3e0e65f8e5 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 3 Jan 2024 23:26:22 +0800 Subject: [PATCH 060/169] fix the check for whether the cluster supports metrics (#2424) --- internal/client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/client/client.go b/internal/client/client.go index 3a47816749..28ecc2f0fb 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -352,7 +352,7 @@ func (a *APIClient) Config() *Config { // HasMetrics checks if the cluster supports metrics. func (a *APIClient) HasMetrics() bool { - return a.supportsMetricsResources() != nil + return a.supportsMetricsResources() == nil } // DialLogs returns a handle to api server for logs. From d0f874e01a747d8851f0751e2fd7677266733f7f Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 3 Jan 2024 12:07:02 -0700 Subject: [PATCH 061/169] K9s/release v0.30.8 (#2427) * [Bug] Fix #2418 * cleaning up * rel notes --- Makefile | 2 +- change_logs/release_v0.30.8.md | 48 +++++++++++++++++++++++++++++++++ internal/config/data/context.go | 2 +- internal/config/data/ns.go | 13 ++++----- internal/config/data/ns_test.go | 8 +++--- internal/config/k9s.go | 14 +++++++--- internal/config/logger.go | 7 +---- internal/config/logger_test.go | 4 +-- internal/config/shell_pod.go | 4 +-- internal/config/styles.go | 4 --- internal/config/threshold.go | 7 +---- internal/ui/config.go | 9 +++---- internal/view/exec.go | 22 +++++++++++---- snap/snapcraft.yaml | 2 +- 14 files changed, 98 insertions(+), 48 deletions(-) create mode 100644 change_logs/release_v0.30.8.md diff --git a/Makefile b/Makefile index c2639ec4ad..7add385c24 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.7 +VERSION ?= v0.30.8 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.30.8.md b/change_logs/release_v0.30.8.md new file mode 100644 index 0000000000..f76143f37d --- /dev/null +++ b/change_logs/release_v0.30.8.md @@ -0,0 +1,48 @@ + + +# Release v0.30.8 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2423](https://github.com/derailed/k9s/issues/2423) CPU and MEM counters of AKS clusters show not available +* [#2418](https://github.com/derailed/k9s/issues/2418) Boom! runtime error: invalid memory address or nil pointer dereference + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2424](https://github.com/derailed/k9s/pull/2424) fix the check for whether the cluster supports metrics + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 081b133822..8f38676e54 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -54,7 +54,7 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if c.Namespace == nil { c.Namespace = NewNamespace() } - c.Namespace.Validate(conn, ks) + c.Namespace.Validate(conn) if c.View == nil { c.View = NewView() diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 69ab9cd521..ba8aacae27 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -22,10 +22,7 @@ type Namespace struct { // NewNamespace create a new namespace configuration. func NewNamespace() *Namespace { - return &Namespace{ - Active: client.DefaultNamespace, - Favorites: []string{client.DefaultNamespace}, - } + return NewActiveNamespace(client.DefaultNamespace) } func NewActiveNamespace(n string) *Namespace { @@ -39,7 +36,7 @@ func NewActiveNamespace(n string) *Namespace { } // Validate validates a namespace is setup correctly. -func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { +func (n *Namespace) Validate(c client.Connection) { if c == nil || !c.IsValidNamespace(n.Active) { return } @@ -56,7 +53,11 @@ func (n *Namespace) SetActive(ns string, ks KubeSettings) error { if ns == client.BlankNamespace { ns = client.NamespaceAll } - n.Active = ns + if n == nil { + n = NewActiveNamespace(ns) + } else { + n.Active = ns + } if ns != "" && !n.LockFavorites { n.addFavNS(ns) } diff --git a/internal/config/data/ns_test.go b/internal/config/data/ns_test.go index 7a45ead59c..d66b6903ed 100644 --- a/internal/config/data/ns_test.go +++ b/internal/config/data/ns_test.go @@ -13,7 +13,7 @@ import ( func TestNSValidate(t *testing.T) { ns := data.NewNamespace() - ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + ns.Validate(mock.NewMockConnection()) assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{"default"}, ns.Favorites) @@ -21,7 +21,7 @@ func TestNSValidate(t *testing.T) { func TestNSValidateMissing(t *testing.T) { ns := data.NewNamespace() - ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + ns.Validate(mock.NewMockConnection()) assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{"default"}, ns.Favorites) @@ -29,7 +29,7 @@ func TestNSValidateMissing(t *testing.T) { func TestNSValidateNoNS(t *testing.T) { ns := data.NewNamespace() - ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + ns.Validate(mock.NewMockConnection()) assert.Equal(t, "default", ns.Active) assert.Equal(t, []string{"default"}, ns.Favorites) @@ -61,7 +61,7 @@ func TestNSSetActive(t *testing.T) { func TestNSValidateRmFavs(t *testing.T) { ns := data.NewNamespace() ns.Favorites = []string{"default", "fred"} - ns.Validate(mock.NewMockConnection(), mock.NewMockKubeSettings(makeFlags("cl-1", "ct-1"))) + ns.Validate(mock.NewMockConnection()) assert.Equal(t, []string{"default", "fred"}, ns.Favorites) } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index decac450a2..69e4ddd5a0 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -163,6 +163,13 @@ func (k *K9s) ActiveContextName() string { // ActiveContext returns the currently active context. func (k *K9s) ActiveContext() (*data.Context, error) { if k.activeConfig != nil { + if k.activeConfig.Context == nil { + ct, err := k.ks.CurrentContext() + if err != nil { + return nil, err + } + k.activeConfig.Context = data.NewContextFromConfig(ct) + } return k.activeConfig.Context, nil } @@ -187,6 +194,7 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { } // If the context specifies a default namespace, use it! if k.conn != nil { + k.Validate(k.conn, k.ks) if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace { k.activeConfig.Context.Namespace.Active = ns } else { @@ -328,17 +336,17 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { if k.ShellPod == nil { k.ShellPod = NewShellPod() } - k.ShellPod.Validate(c, ks) + k.ShellPod.Validate() if k.Logger == nil { k.Logger = NewLogger() } else { - k.Logger.Validate(c, ks) + k.Logger.Validate() } if k.Thresholds == nil { k.Thresholds = NewThreshold() } - k.Thresholds.Validate(c, ks) + k.Thresholds.Validate() if k.activeConfig != nil { k.activeConfig.Validate(c, ks) diff --git a/internal/config/logger.go b/internal/config/logger.go index 4cca7e9736..014d724c16 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -3,11 +3,6 @@ package config -import ( - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config/data" -) - const ( // DefaultLoggerTailCount tracks default log tail size. DefaultLoggerTailCount = 100 @@ -39,7 +34,7 @@ func NewLogger() *Logger { } // Validate checks thresholds and make sure we're cool. If not use defaults. -func (l *Logger) Validate(_ client.Connection, _ data.KubeSettings) { +func (l *Logger) Validate() { if l.TailCount <= 0 { l.TailCount = DefaultLoggerTailCount } diff --git a/internal/config/logger_test.go b/internal/config/logger_test.go index e0253505f9..51625df65e 100644 --- a/internal/config/logger_test.go +++ b/internal/config/logger_test.go @@ -12,7 +12,7 @@ import ( func TestNewLogger(t *testing.T) { l := config.NewLogger() - l.Validate(nil, nil) + l.Validate() assert.Equal(t, int64(100), l.TailCount) assert.Equal(t, 5000, l.BufferSize) @@ -20,7 +20,7 @@ func TestNewLogger(t *testing.T) { func TestLoggerValidate(t *testing.T) { var l config.Logger - l.Validate(nil, nil) + l.Validate() assert.Equal(t, int64(100), l.TailCount) assert.Equal(t, 5000, l.BufferSize) diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index 19ad05a0c2..c431bc1b0f 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -4,8 +4,6 @@ package config import ( - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config/data" v1 "k8s.io/api/core/v1" ) @@ -37,7 +35,7 @@ func NewShellPod() *ShellPod { } // Validate validates the configuration. -func (s *ShellPod) Validate(client.Connection, data.KubeSettings) { +func (s *ShellPod) Validate() { if s.Image == "" { s.Image = defaultDockerShellImage } diff --git a/internal/config/styles.go b/internal/config/styles.go index 5dd3c09950..06690d7d9a 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -445,10 +445,6 @@ func (s *Styles) Reset() { s.K9s = newStyle() } -// DefaultSkin loads the default skin. -func (s *Styles) DefaultSkin() { -} - // FgColor returns the foreground color. func (s *Styles) FgColor() tcell.Color { return s.Body().FgColor.Color() diff --git a/internal/config/threshold.go b/internal/config/threshold.go index f15bd8c1fa..e746775580 100644 --- a/internal/config/threshold.go +++ b/internal/config/threshold.go @@ -3,11 +3,6 @@ package config -import ( - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config/data" -) - const ( // SeverityLow tracks low severity. SeverityLow SeverityLevel = iota @@ -66,7 +61,7 @@ func NewThreshold() Threshold { } // Validate a namespace is setup correctly. -func (t Threshold) Validate(c client.Connection, ks data.KubeSettings) { +func (t Threshold) Validate() { for _, k := range []string{"cpu", "memory"} { v, ok := t[k] if !ok { diff --git a/internal/ui/config.go b/internal/ui/config.go index 1ddb04f1fb..4509280c2a 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -211,12 +211,12 @@ func (c *Configurator) activeConfig() (cluster string, context string, ok bool) func (c *Configurator) RefreshStyles() { if c.Styles == nil { c.Styles = config.NewStyles() - } else { - c.Styles.Reset() } cl, ct, ok := c.activeConfig() if !ok { + log.Debug().Msgf("No custom skin found. Using stock skin") + c.updateStyles("") return } @@ -228,7 +228,7 @@ func (c *Configurator) RefreshStyles() { skin, ok := c.activeSkin() if !ok { - log.Debug().Msgf("No custom skin found. Loading default") + log.Debug().Msgf("No custom skin found. Using stock skin") c.updateStyles("") return } @@ -248,9 +248,6 @@ func (c *Configurator) RefreshStyles() { func (c *Configurator) updateStyles(f string) { c.skinFile = f - if !c.HasSkin() { - c.Styles.DefaultSkin() - } c.Styles.Update() render.ModColor = c.Styles.Frame().Status.ModifyColor.Color() diff --git a/internal/view/exec.go b/internal/view/exec.go index effe8c9502..97d0dc539b 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -38,6 +38,8 @@ const ( bannerFmt = "<> Pod: %s | Container: %s \n" ) +var editorEnvVars = []string{"KUBE_EDITOR", "K9S_EDITOR", "EDITOR"} + type shellOpts struct { clear, background bool pipes []string @@ -115,14 +117,24 @@ func run(a *App, opts shellOpts) (bool, chan error, chan string) { } func edit(a *App, opts shellOpts) bool { - bin, err := exec.LookPath(os.Getenv("K9S_EDITOR")) - if err != nil { - bin, err = exec.LookPath(os.Getenv("EDITOR")) + var ( + bin string + err error + ) + for _, e := range editorEnvVars { + env := os.Getenv(e) + if env != "" { + continue + } + bin, err = exec.LookPath(env) if err != nil { - log.Error().Err(err).Msgf("K9S_EDITOR|EDITOR not set") - return false + continue } } + if bin == "" { + a.Flash().Errf("You must set at least one of those env vars: %s", strings.Join(editorEnvVars, "|")) + return false + } opts.binary, opts.background = bin, false suspended, errChan, _ := run(a, opts) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 7515aa753a..2b3acfe19d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.7' +version: 'v0.30.8' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 3137b2b55a00581c8dac0bbc97b7ffc10854745b Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Thu, 4 Jan 2024 13:56:21 +0800 Subject: [PATCH 062/169] supports referencing envs in hotkeys (#2420) * supports referencing envs in hotkeys * add testcase for hotkeys * rename attr name to keepHistory --- README.md | 8 ++++++++ internal/config/hotkey.go | 1 + internal/config/hotkey_test.go | 1 + internal/config/testdata/hotkeys.yaml | 1 + internal/view/actions.go | 13 ++++++++++--- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a1c80e4fb..8fa956f30c 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,12 @@ In order to surface hotkeys globally please follow these steps: shortCut: Shift-2 description: Xray Deployments command: xray deploy + # Hitting Ctrl-U view the resources in the namespace of your current selection + ctrl-u: + shortCut: Ctrl-U + description: Namespaced resources + command: "$RESOURCE_NAME $NAMESPACE" + keepHistory: true # whether you can return to the previous view ``` Not feeling so hot? Your custom hotkeys will be listed in the help view `?`. @@ -532,6 +538,8 @@ In order to surface hotkeys globally please follow these steps: You can choose any keyboard shortcuts that make sense to you, provided they are not part of the standard K9s shortcuts list. + Similarly, referencing environment variables in hotkeys is also supported. The available environment variables can refer to the description in the [Plugins](#plugins) section. + > NOTE: This feature/configuration might change in future releases! --- diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index fa292a98f9..b98c7c436e 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -19,6 +19,7 @@ type HotKey struct { ShortCut string `yaml:"shortCut"` Description string `yaml:"description"` Command string `yaml:"command"` + KeepHistory bool `yaml:"keepHistory"` } // NewHotKeys returns a new plugin. diff --git a/internal/config/hotkey_test.go b/internal/config/hotkey_test.go index 80b244c735..28936d3490 100644 --- a/internal/config/hotkey_test.go +++ b/internal/config/hotkey_test.go @@ -21,4 +21,5 @@ func TestHotKeyLoad(t *testing.T) { assert.Equal(t, "shift-0", k.ShortCut) assert.Equal(t, "Launch pod view", k.Description) assert.Equal(t, "pods", k.Command) + assert.Equal(t, true, k.KeepHistory) } diff --git a/internal/config/testdata/hotkeys.yaml b/internal/config/testdata/hotkeys.yaml index 255555b3e1..55fadf8491 100644 --- a/internal/config/testdata/hotkeys.yaml +++ b/internal/config/testdata/hotkeys.yaml @@ -3,3 +3,4 @@ hotKeys: shortCut: shift-0 description: Launch pod view command: pods + keepHistory: true diff --git a/internal/view/actions.go b/internal/view/actions.go index 7ce1e6da6b..b6c14afca2 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -74,16 +74,23 @@ func hotKeyActions(r Runner, aa ui.KeyActions) { log.Warn().Err(fmt.Errorf("HOT-KEY Doh! you are trying to override an existing command `%s", k)).Msg("Invalid shortcut") continue } + + command, err := r.EnvFn()().Substitute(hk.Command) + if err != nil { + log.Warn().Err(err).Msg("Invalid shortcut command") + continue + } + aa[key] = ui.NewSharedKeyAction( hk.Description, - gotoCmd(r, hk.Command, ""), + gotoCmd(r, command, "", !hk.KeepHistory), false) } } -func gotoCmd(r Runner, cmd, path string) ui.ActionHandler { +func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler { return func(evt *tcell.EventKey) *tcell.EventKey { - r.App().gotoResource(cmd, path, true) + r.App().gotoResource(cmd, path, clearStack) return nil } } From a19fabd885c0b70c5ecda5627afa7cb109041fbe Mon Sep 17 00:00:00 2001 From: Alex Barbato Date: Thu, 4 Jan 2024 01:35:36 -0500 Subject: [PATCH 063/169] Update carvel plugin kick to shift K (#2426) --- plugins/carvel.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/carvel.yaml b/plugins/carvel.yaml index d3bfeb7ab0..dd8a4d9e90 100644 --- a/plugins/carvel.yaml +++ b/plugins/carvel.yaml @@ -34,7 +34,7 @@ plugins: - -c - "export FORCE_COLOR=1;kctrl app pause -a $NAME --namespace $NAMESPACE --kubeconfig-context $CONTEXT --yes --color --tty | less -RK" kctrl-app-kick: - shortCut: Shift-Z + shortCut: Shift-K confirm: false description: kctrl app kick scopes: From b56c151d94dfcafbb3cffd7b0b3130d4df928161 Mon Sep 17 00:00:00 2001 From: Max Schmidt <35741000+mooxl@users.noreply.github.com> Date: Thu, 4 Jan 2024 07:35:51 +0100 Subject: [PATCH 064/169] fix typo (#2419) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fa956f30c..553195b1b9 100644 --- a/README.md +++ b/README.md @@ -911,7 +911,7 @@ k9s: crumbsless: false noIcons: false # By default all contexts wil use the dracula skin unless explicitly overridden in the context config file. - skin: dracula # => assumes the file skins/dracular.yaml is present in the $XDG_DATA_HOME/k9s/skins directory + skin: dracula # => assumes the file skins/dracula.yaml is present in the $XDG_DATA_HOME/k9s/skins directory skipLatestRevCheck: false disablePodCounting: false shellPod: From d63fe83edb87a8b9f0a75fff238251bebe8db8ee Mon Sep 17 00:00:00 2001 From: Joshua Ward Date: Thu, 4 Jan 2024 01:38:37 -0500 Subject: [PATCH 065/169] chore: rebasing off main and resolving conflicts (#2314) Co-authored-by: Joshua Ward --- internal/dao/pod.go | 4 ++++ internal/render/pod.go | 28 +++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/internal/dao/pod.go b/internal/dao/pod.go index f426f4ef2b..850454499c 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -544,6 +544,10 @@ func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { fallthrough case render.PhaseImagePullBackOff: fallthrough + case render.PhaseContainerStatusUnknown: + fallthrough + case render.PhaseEvicted: + fallthrough case render.PhaseOOMKilled: // !!BOZO!! Might need to bump timeout otherwise rev limit if too many?? log.Debug().Msgf("Sanitizing %s:%s", pod.Namespace, pod.Name) diff --git a/internal/render/pod.go b/internal/render/pod.go index bf99be0123..f90e5c4f7b 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -28,19 +28,21 @@ const ( ) const ( - PhaseTerminating = "Terminating" - PhaseInitialized = "Initialized" - PhaseRunning = "Running" - PhaseNotReady = "NoReady" - PhaseCompleted = "Completed" - PhaseContainerCreating = "ContainerCreating" - PhasePodInitializing = "PodInitializing" - PhaseUnknown = "Unknown" - PhaseCrashLoop = "CrashLoopBackOff" - PhaseError = "Error" - PhaseImagePullBackOff = "ImagePullBackOff" - PhaseOOMKilled = "OOMKilled" - PhasePending = "Pending" + PhaseTerminating = "Terminating" + PhaseInitialized = "Initialized" + PhaseRunning = "Running" + PhaseNotReady = "NoReady" + PhaseCompleted = "Completed" + PhaseContainerCreating = "ContainerCreating" + PhasePodInitializing = "PodInitializing" + PhaseUnknown = "Unknown" + PhaseCrashLoop = "CrashLoopBackOff" + PhaseError = "Error" + PhaseImagePullBackOff = "ImagePullBackOff" + PhaseOOMKilled = "OOMKilled" + PhasePending = "Pending" + PhaseContainerStatusUnknown = "ContainerStatusUnknown" + PhaseEvicted = "Evicted" ) // Pod renders a K8s Pod to screen. From fa4d3acbd14225ef0195f1f4ec0c9893c7b16d92 Mon Sep 17 00:00:00 2001 From: Denis Iskandarov Date: Thu, 4 Jan 2024 21:50:09 +0400 Subject: [PATCH 066/169] README update (#2429) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 553195b1b9..441e19e68d 100644 --- a/README.md +++ b/README.md @@ -368,7 +368,7 @@ K9s uses aliases to navigate most K8s resources. ## K9s Configuration - K9s keeps its configurations as YAML files inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9SCONFIG` to tell K9s the directory location to pull its configurations from. + K9s keeps its configurations as YAML files inside of a `k9s` directory and the location depends on your operating system. K9s leverages [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) to load its various configurations files. For information on the default locations for your OS please see [this link](https://github.com/adrg/xdg/blob/master/README.md). If you are still confused a quick `k9s info` will reveal where k9s is loading its configurations from. Alternatively, you can set `K9S_CONFIG_DIR` to tell K9s the directory location to pull its configurations from. | Unix | macOS | Windows | |-----------------|------------------------------------|-----------------------| From 96a7e5a4d44fd630f9ea1109d7d1943bd63e814e Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Mon, 8 Jan 2024 01:03:59 +0800 Subject: [PATCH 067/169] switch contexts only when needed (#2433) --- internal/view/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/view/app.go b/internal/view/app.go index 20ceb2c897..b907dcedcc 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -439,7 +439,7 @@ func (a *App) isValidNS(ns string) (bool, error) { func (a *App) switchContext(ci *cmd.Interpreter) error { name, ok := ci.HasContext() - if !ok { + if !ok || a.Config.ActiveContextName() == name { return nil } From 21b9718804ce75c7ccd644cc520eeab8fe66ffd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:15:40 -0700 Subject: [PATCH 068/169] Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 (#2438) Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.3 to 1.3.7. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.3.3...v1.3.7) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7fcd6acd0a..9eff0a9b3d 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd v1.7.11 // indirect github.com/containerd/continuity v0.4.2 // indirect diff --git a/go.sum b/go.sum index a827513d13..7a9743af8d 100644 --- a/go.sum +++ b/go.sum @@ -336,8 +336,9 @@ github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2u github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= From ffd8d51a8bce74a0edb1fe135acdbc084d20aef8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:15:59 -0700 Subject: [PATCH 069/169] Bump github.com/anchore/grype from 0.73.5 to 0.74.0 (#2439) Bumps [github.com/anchore/grype](https://github.com/anchore/grype) from 0.73.5 to 0.74.0. - [Release notes](https://github.com/anchore/grype/releases) - [Changelog](https://github.com/anchore/grype/blob/main/.goreleaser.yaml) - [Commits](https://github.com/anchore/grype/compare/v0.73.5...v0.74.0) --- updated-dependencies: - dependency-name: github.com/anchore/grype dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9eff0a9b3d..25fba7d04c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.4 require ( github.com/adrg/xdg v0.4.0 github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc - github.com/anchore/grype v0.73.5 + github.com/anchore/grype v0.74.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.2 @@ -68,8 +68,8 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect - github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89 // indirect - github.com/anchore/syft v0.99.0 // indirect + github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 // indirect + github.com/anchore/syft v0.100.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect diff --git a/go.sum b/go.sum index 7a9743af8d..311b078894 100644 --- a/go.sum +++ b/go.sum @@ -253,14 +253,14 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= -github.com/anchore/grype v0.73.5 h1:1X81Snj5pGpl9ru7mQl1eYLX1Ek2ElfKhm9cwIgdCOw= -github.com/anchore/grype v0.73.5/go.mod h1:bdI7d2XeXQbmfbqql/Fqg+Lv2w4gO3nN3jfby/mBIcs= +github.com/anchore/grype v0.74.0 h1:YesFYnishQEC646iCt0hAvuEfQTvLrhCGJrANyB0c2Q= +github.com/anchore/grype v0.74.0/go.mod h1:kxRA1NCUGjTyO0C+babd46oSH2VL3PnF8b+tWGcT21k= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89 h1:dymFMCwnENqLr74KQppq8zHKwOPL0M1ToYAU+KVfTew= -github.com/anchore/stereoscope v0.0.0-20231215220732-4b999b76ca89/go.mod h1:GKAnytSVV1hoqB5r5Gd9M5Ph3Rzqq0zPdEJesewjC2w= -github.com/anchore/syft v0.99.0 h1:oqycIA7XfHCB09meroN7eY2RWTGUZIdtWsMQL2HlPvw= -github.com/anchore/syft v0.99.0/go.mod h1:tGZGyDxB2z/yu+x266+b67fMenGKCrUvSNVKED1euuo= +github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 h1:i2YK5QEs9H2YB3B2zv+AGR44ves0nmAGOD07lMphH14= +github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU= +github.com/anchore/syft v0.100.0 h1:XUpV4xWmD2cBS9hhhEdJEppItz0AxG8f5W3JhI2tQvY= +github.com/anchore/syft v0.100.0/go.mod h1:laFRFA/okrA+ut+wPCU32hNkdPEwQfXyaB7E21ymWFc= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= From 6cc4374e83d06b7786efa77df7b3ee04d04d5042 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Mon, 8 Jan 2024 18:30:49 -0700 Subject: [PATCH 070/169] K9s/release v0.31.0 (#2440) * cleaning up * [Bug] Fix #2425 - no context skin issue * [Bug] Fix #2428 * [Feat] schema validation * v0.31.0 Release notes --- .github/ISSUE_TEMPLATE/bug_report.md | 3 + Makefile | 2 +- README.md | 48 +- change_logs/release_v0.31.0.md | 153 ++++ cmd/info.go | 5 +- cmd/root.go | 7 +- go.mod | 4 +- internal/client/client.go | 4 +- internal/client/metrics.go | 6 +- internal/client/types.go | 2 +- internal/config/alias.go | 32 +- internal/config/alias_test.go | 82 ++- internal/config/benchmark_test.go | 12 +- internal/config/color.go | 25 +- internal/config/color_test.go | 119 +++ internal/config/config.go | 123 ++-- internal/config/config_test.go | 687 ++++++++++++++---- internal/config/data/config.go | 8 +- internal/config/data/context.go | 30 +- internal/config/data/dir.go | 6 + internal/config/data/helpers_test.go | 2 +- internal/config/data/types.go | 4 + internal/config/files.go | 6 +- internal/config/files_int_test.go | 76 ++ internal/config/files_test.go | 28 +- internal/config/flags_test.go | 29 + internal/config/helpers.go | 13 + internal/config/hotkey.go | 24 +- internal/config/hotkey_test.go | 2 +- internal/config/json/schemas/aliases.json | 14 + internal/config/json/schemas/context.json | 44 ++ internal/config/json/schemas/hotkeys.json | 20 + internal/config/json/schemas/k9s.json | 109 +++ internal/config/json/schemas/plugins.json | 33 + internal/config/json/schemas/skin.json | 185 +++++ internal/config/json/schemas/views.json | 24 + .../config/json/testdata/aliases/cool.yaml | 3 + .../config/json/testdata/aliases/toast.yaml | 3 + .../config/json/testdata/context/cool.yaml | 15 + .../config/json/testdata/context/toast.yaml | 16 + .../config/json/testdata/hotkeys/cool.yaml | 33 + internal/config/json/testdata/k9s/cool.yaml | 40 + internal/config/json/testdata/k9s/toast.yaml | 34 + .../config/json/testdata/plugins/cool.yaml | 23 + .../config/json/testdata/plugins/toast.yaml | 21 + internal/config/json/testdata/skins/cool.yaml | 109 +++ .../config/json/testdata/skins/toast.yaml | 103 +++ internal/config/json/testdata/views/cool.yaml | 12 + .../config/json/testdata/views/toast.yaml | 9 + internal/config/json/validator.go | 152 ++++ internal/config/json/validator_test.go | 223 ++++++ internal/config/k9s.go | 287 ++++---- internal/config/k9s_int_test.go | 128 ++++ internal/config/k9s_test.go | 139 +++- internal/config/logger.go | 20 +- internal/config/logger_test.go | 4 +- internal/config/mock/test_helpers.go | 4 +- internal/config/plugin.go | 16 +- internal/config/plugin_test.go | 24 +- internal/config/scans.go | 14 +- internal/config/scans_test.go | 93 +++ internal/config/shell_pod.go | 26 +- internal/config/styles.go | 228 +++--- internal/config/styles_int_test.go | 18 + internal/config/styles_test.go | 65 +- internal/config/templates/aliases.yaml | 14 +- internal/config/testdata/aliases/aliases.yaml | 9 + .../{alias.yaml => aliases/plain.yaml} | 0 .../{ => benchmarks}/b_containers.yaml | 0 .../{ => benchmarks}/b_containers_1.yaml | 0 .../testdata/{ => benchmarks}/b_good.yaml | 0 .../testdata/{ => benchmarks}/b_toast.yaml | 0 .../testdata/{ => benchmarks}/bench-fred.yaml | 0 internal/config/testdata/configs/default.yaml | 41 ++ .../config/testdata/configs/expected.yaml | 41 ++ internal/config/testdata/configs/k9s.yaml | 41 ++ .../config/testdata/configs/k9s_toast.yaml | 40 + internal/config/testdata/empty_skin.yaml | 0 .../testdata/{ => hotkeys}/hotkeys.yaml | 0 internal/config/testdata/k9s.yaml | 35 - internal/config/testdata/k9s1.yaml | 8 - internal/config/testdata/k9s_old.yaml | 13 - internal/config/testdata/k9s_readonly.yaml | 30 - internal/config/testdata/k9s_toast.yaml | 27 - .../{kubeconfig-test.yaml => kubes/test.yaml} | 15 +- .../black-and-wtf.yaml} | 15 +- .../{skin_boarked.yaml => skins/boarked.yaml} | 0 internal/config/testdata/skins/empty.yaml | 2 + internal/config/testdata/view_settings.yaml | 8 - internal/config/testdata/views/views.yaml | 7 + internal/config/threshold.go | 4 +- internal/config/types.go | 15 +- internal/config/views.go | 36 +- internal/config/views_test.go | 6 +- internal/dao/popeye.go | 4 +- internal/model/cluster_info.go | 51 +- internal/model/log.go | 2 +- internal/ui/action.go | 51 +- internal/ui/app.go | 8 +- internal/ui/config.go | 65 +- internal/ui/config_test.go | 29 +- internal/ui/logo.go | 7 + internal/ui/table.go | 8 +- internal/ui/table_test.go | 7 +- internal/view/actions.go | 51 +- internal/view/app.go | 42 +- internal/view/browser.go | 35 +- internal/view/cluster_info.go | 19 +- internal/view/command.go | 15 +- internal/view/container.go | 22 +- internal/view/details.go | 2 +- internal/view/dir.go | 16 +- internal/view/exec.go | 4 +- internal/view/helm_history.go | 9 +- internal/view/help.go | 2 +- internal/view/help_test.go | 2 +- internal/view/helpers.go | 7 +- internal/view/helpers_test.go | 4 +- internal/view/live_view.go | 2 +- internal/view/log.go | 2 +- internal/view/log_indicator.go | 2 +- internal/view/log_test.go | 2 +- internal/view/logger.go | 2 +- internal/view/node.go | 29 +- internal/view/pod.go | 40 +- internal/view/restart_extender.go | 5 +- internal/view/scale_extender.go | 6 +- internal/view/screen_dump.go | 2 +- internal/view/table.go | 8 +- internal/view/table_int_test.go | 2 +- internal/view/workload.go | 16 +- internal/view/xray.go | 8 +- internal/vul/scanner.go | 7 +- plugins/{blame.yml => blame.yaml} | 0 .../{helm_values.yaml => helm-values.yaml} | 0 .../{job_suspend.yaml => job-suspend.yaml} | 0 ...3d_root_shell.yaml => k3d-root-shell.yaml} | 0 plugins/{log_full.yaml => log-full.yaml} | 0 plugins/{log_jq.yaml => log-jq.yaml} | 0 plugins/{log_stern.yaml => log-stern.yaml} | 0 ..._finalizers.yml => remove-finalizers.yaml} | 0 ...ions.yml => resource-recommendations.yaml} | 0 plugins/schema.json | 54 -- .../{watch_events.yaml => watch-events.yaml} | 1 - skins/black-and-wtf.yaml | 2 +- skins/nightfox.yaml | 2 +- skins/transparent.yaml | 2 +- snap/snapcraft.yaml | 2 +- 148 files changed, 3866 insertions(+), 1032 deletions(-) create mode 100644 change_logs/release_v0.31.0.md create mode 100644 internal/config/color_test.go create mode 100644 internal/config/files_int_test.go create mode 100644 internal/config/flags_test.go create mode 100644 internal/config/json/schemas/aliases.json create mode 100644 internal/config/json/schemas/context.json create mode 100644 internal/config/json/schemas/hotkeys.json create mode 100644 internal/config/json/schemas/k9s.json create mode 100644 internal/config/json/schemas/plugins.json create mode 100644 internal/config/json/schemas/skin.json create mode 100644 internal/config/json/schemas/views.json create mode 100644 internal/config/json/testdata/aliases/cool.yaml create mode 100644 internal/config/json/testdata/aliases/toast.yaml create mode 100644 internal/config/json/testdata/context/cool.yaml create mode 100644 internal/config/json/testdata/context/toast.yaml create mode 100644 internal/config/json/testdata/hotkeys/cool.yaml create mode 100644 internal/config/json/testdata/k9s/cool.yaml create mode 100644 internal/config/json/testdata/k9s/toast.yaml create mode 100644 internal/config/json/testdata/plugins/cool.yaml create mode 100644 internal/config/json/testdata/plugins/toast.yaml create mode 100644 internal/config/json/testdata/skins/cool.yaml create mode 100644 internal/config/json/testdata/skins/toast.yaml create mode 100644 internal/config/json/testdata/views/cool.yaml create mode 100644 internal/config/json/testdata/views/toast.yaml create mode 100644 internal/config/json/validator.go create mode 100644 internal/config/json/validator_test.go create mode 100644 internal/config/k9s_int_test.go create mode 100644 internal/config/scans_test.go create mode 100644 internal/config/styles_int_test.go create mode 100644 internal/config/testdata/aliases/aliases.yaml rename internal/config/testdata/{alias.yaml => aliases/plain.yaml} (100%) rename internal/config/testdata/{ => benchmarks}/b_containers.yaml (100%) rename internal/config/testdata/{ => benchmarks}/b_containers_1.yaml (100%) rename internal/config/testdata/{ => benchmarks}/b_good.yaml (100%) rename internal/config/testdata/{ => benchmarks}/b_toast.yaml (100%) rename internal/config/testdata/{ => benchmarks}/bench-fred.yaml (100%) create mode 100644 internal/config/testdata/configs/default.yaml create mode 100644 internal/config/testdata/configs/expected.yaml create mode 100644 internal/config/testdata/configs/k9s.yaml create mode 100644 internal/config/testdata/configs/k9s_toast.yaml delete mode 100644 internal/config/testdata/empty_skin.yaml rename internal/config/testdata/{ => hotkeys}/hotkeys.yaml (100%) delete mode 100644 internal/config/testdata/k9s.yaml delete mode 100644 internal/config/testdata/k9s1.yaml delete mode 100644 internal/config/testdata/k9s_old.yaml delete mode 100644 internal/config/testdata/k9s_readonly.yaml delete mode 100644 internal/config/testdata/k9s_toast.yaml rename internal/config/testdata/{kubeconfig-test.yaml => kubes/test.yaml} (68%) rename internal/config/testdata/{black_and_wtf.yaml => skins/black-and-wtf.yaml} (82%) rename internal/config/testdata/{skin_boarked.yaml => skins/boarked.yaml} (100%) create mode 100644 internal/config/testdata/skins/empty.yaml delete mode 100644 internal/config/testdata/view_settings.yaml create mode 100644 internal/config/testdata/views/views.yaml rename plugins/{blame.yml => blame.yaml} (100%) rename plugins/{helm_values.yaml => helm-values.yaml} (100%) rename plugins/{job_suspend.yaml => job-suspend.yaml} (100%) rename plugins/{k3d_root_shell.yaml => k3d-root-shell.yaml} (100%) rename plugins/{log_full.yaml => log-full.yaml} (100%) rename plugins/{log_jq.yaml => log-jq.yaml} (100%) rename plugins/{log_stern.yaml => log-stern.yaml} (100%) rename plugins/{remove_finalizers.yml => remove-finalizers.yaml} (100%) rename plugins/{resource-recommendations.yml => resource-recommendations.yaml} (100%) delete mode 100644 plugins/schema.json rename plugins/{watch_events.yaml => watch-events.yaml} (99%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4a65fcef0b..8a4d6fdacb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,6 +25,9 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error +**Historical Documents** +When applicable please include any supporting artifacts: k9s debug logs, configurations, resource manifests, ... + **Expected behavior** A clear and concise description of what you expected to happen. diff --git a/Makefile b/Makefile index 7add385c24..7e77251a8c 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.30.8 +VERSION ?= v0.31.0 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index 441e19e68d..2d92ceb037 100644 --- a/README.md +++ b/README.md @@ -225,13 +225,11 @@ Binaries for Linux, Windows and Mac are available as tarballs in the [release pa export TERM=xterm-256color ``` -* In order to issue manifest edit commands make sure your EDITOR env is set. +* In order to issue resource edit commands make sure your EDITOR and KUBE_EDITOR env vars are set. ```shell # Kubectl edit command will use this env var. - export EDITOR=my_fav_editor - # Should your editor deal with streamed vs on disk files differently, also set... - export K9S_EDITOR=my_fav_editor + export KUBE_EDITOR=my_fav_editor ``` * K9s prefers recent kubernetes versions ie 1.28+ @@ -607,24 +605,23 @@ Here is a sample views configuration that customize a pods and services views. ```yaml # $XDG_CONFIG_HOME/k9s/views.yaml -k9s: - views: - v1/pods: - columns: - - AGE - - NAMESPACE - - NAME - - IP - - NODE - - STATUS - - READY - v1/services: - columns: - - AGE - - NAMESPACE - - NAME - - TYPE - - CLUSTER-IP +views: + v1/pods: + columns: + - AGE + - NAMESPACE + - NAME + - IP + - NODE + - STATUS + - READY + v1/services: + columns: + - AGE + - NAMESPACE + - NAME + - TYPE + - CLUSTER-IP ``` --- @@ -897,6 +894,7 @@ k9s: You can also specify a default skin for all contexts in the root k9s config file as so: ```yaml +# $XDG_CONFIG_HOME/k9s/config.yaml k9s: liveViewAutoRefresh: false screenDumpDir: /tmp/dumps @@ -910,6 +908,8 @@ k9s: logoless: false crumbsless: false noIcons: false + # Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false. + reactive: false # By default all contexts wil use the dracula skin unless explicitly overridden in the context config file. skin: dracula # => assumes the file skins/dracula.yaml is present in the $XDG_DATA_HOME/k9s/skins directory skipLatestRevCheck: false @@ -929,7 +929,7 @@ k9s: tail: 100 buffer: 5000 sinceSeconds: -1 - fullScreenLogs: false + fullScreen: false textWrap: false showTime: false thresholds: @@ -942,7 +942,7 @@ k9s: ``` ```yaml -# $XDG_DATA_HOME/k9s/skins/in_the_navy.yaml +# $XDG_DATA_HOME/k9s/skins/in_the_navy.yaml # Skin InTheNavy! k9s: # General K9s styles diff --git a/change_logs/release_v0.31.0.md b/change_logs/release_v0.31.0.md new file mode 100644 index 0000000000..d4b92246bc --- /dev/null +++ b/change_logs/release_v0.31.0.md @@ -0,0 +1,153 @@ + + +# Release v0.31.0 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +## ♫ Sounds Behind The Release ♭ + +* [Border Crossing - Eek A Mouse](https://www.youtube.com/watch?v=KaAC9dBPcOM) +* [The Weight - The Band](https://www.youtube.com/watch?v=FFqb1I-hiHE) +* [Wonderin' - Neil Young](https://www.youtube.com/watch?v=h0PlwVPbM5k) +* [When Your Lover Has Gone - Louis Armstrong](https://www.youtube.com/watch?v=1tdfIj0fvlA) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Jacky Nguyen](https://github.com/nktpro) +* [Eckl, Máté](https://github.com/ecklm) +* [Jörgen](https://github.com/wthrbtn) +* [kmath313](https://github.com/kmath313) +* [a-thomas-22](https://github.com/a-thomas-22) +* [wpbeckwith](https://github.com/wpbeckwith) +* [Dima Altukhov](https://github.com/alt-dima) +* [Shoshin Nikita](https://github.com/ShoshinNikita) +* [Tu Hoang](https://github.com/rebyn) +* [Andreas Frangopoulos](https://github.com/qubeio) + +> Sponsorship cancellations since the last release: **7!** 🥹 + +## Feature Release! + +😳 Found a few issues in the neutrino drive... +This is another fairly heavy drop so bracing for impact 😱 +Be sure to dial in the v0.31.0 SneakPeek video below for the gory details! + +😵 Hopefully we've move the needle in the right direction on this drop... 🤞 + +Thank you all for your kindness, feedback and assistance in flushing out issues!! + +### Hold My Hand... + +In this drop, we've added schema validation to ensure various configs are setup as expected. +K9s will now run validation checks on the following configurations: + +1. K9s main configuration (config.yaml) +2. Context specific configs (clusterX/contextY/config.yaml) +3. Skins +4. Aliases +5. HotKeys +6. Plugins +7. Views + +K9s behavior changed in this release if the main configuration does not match schema expectations. +In the past, the configuration will be validated, updated and saved should validation checks failed. Now the app will stop and report validation issues. + +The schemas are set to be a bit loose for the time being. Once we/ve vetted they are cool, we could publish them out (with additional TLC!) so k9s users can leverage them in their favorite editors. + +In the meantime, you'll need to keep k9s logs handy, to check for validation errors. The validation messages can be somewhat cryptic at times and so please be sure to include your debug logs and config settings when reporting issues which might be plenty ;(. + +### Breaking Bad! + +Configuration changes: + +1. DRY fullScreenLogs -> fullScreens (k9s root config.yaml) + + ```yaml + # $XDG_CONFIG_HOME/k9s/config.yaml + k9s: + liveViewAutoRefresh: false + logger: + sinceSeconds: -1 + fullScreen: false # => Was fullScreenLogs + ... + ``` + +2. Views Configuration. + To match other configurations the root is now `views:` vs `k9s: views:` + + ```yaml + # $XDG_CONFIG_HOME/k9s/views.yaml + views: # => Was k9s:\n views: + v1/pods: + columns: + - AGE + - NAMESPACE + ... + ``` + +### Serenity Now! + + You can now opt in/out of the `reactive ui` feature. This feature enable users to make change to some configurations and see changes reflected live in the ui. This feature is now disabled by default and one must opt-in to enable via `k9s.UI.reactive` + Reactive UI provides for monitoring various config files on disk and update the UI when changes to those files occur. This is handy while tuning skins, plugins, aliases, hotkeys and benchmarks parameters. + + ```yaml + # $XDG_CONFIG_HOME/k9s/config.yaml + k9s: + liveViewAutoRefresh: false + UI: + ... + reactive: true # => enable/disable reactive UI + ... + ``` + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2434](https://github.com/derailed/k9s/issues/2434) readOnly: true in config.yaml doesnt get overriden by readOnly: false in cluster config +* [#2430](https://github.com/derailed/k9s/issues/2430) Referencing a namespace with the name of an alias inside an alias causes infinite loop +* [#2428](https://github.com/derailed/k9s/issues/2428) Boom!! runtime error: invalid memory address or nil pointer dereference - v0.30.8 +* [#2421](https://github.com/derailed/k9s/issues/2421) k9s/config.yaml configuration file is overwritten on launch + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2433](https://github.com/derailed/k9s/pull/2433) switch contexts only when needed +* [#2429](https://github.com/derailed/k9s/pull/2429) Reference correct configuration ENV var in README +* [#2426](https://github.com/derailed/k9s/pull/2426) Update carvel plugin kick to shift K +* [#2420](https://github.com/derailed/k9s/pull/2420) supports referencing envs in hotkeys +* [#2419](https://github.com/derailed/k9s/pull/2419) fix typo + + © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/info.go b/cmd/info.go index 719c946176..8fbb01e500 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -69,6 +69,9 @@ func getScreenDumpDirForInfo() string { log.Error().Err(err).Msgf("Unmarshal k9s config %v", err) return config.AppDumpsDir } + if cfg.K9s == nil { + return config.AppDumpsDir + } - return cfg.K9s.GetScreenDumpDir() + return cfg.K9s.AppScreenDumpDir() } diff --git a/cmd/root.go b/cmd/root.go index 4f1f484785..c7412fa549 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,7 +93,7 @@ func run(cmd *cobra.Command, args []string) error { cfg, err := loadConfiguration() if err != nil { - log.Error().Err(err).Msgf("load configuration failed") + return err } app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { @@ -115,11 +115,12 @@ func loadConfiguration() (*config.Config, error) { k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) if err := k9sCfg.Load(config.AppConfigFile); err != nil { - log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") + return nil, err } k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { - log.Error().Err(err).Msgf("refine failed") + log.Error().Err(err).Msgf("config refine failed") + return nil, err } conn, err := client.InitConnection(k8sCfg) k9sCfg.SetConnection(conn) diff --git a/go.mod b/go.mod index 25fba7d04c..4ca83ec743 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,10 @@ require ( github.com/sahilm/fuzzy v0.1.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 + github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.13.3 k8s.io/api v0.29.0 k8s.io/apiextensions-apiserver v0.29.0 @@ -276,7 +278,6 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect @@ -310,7 +311,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.25.5 // indirect k8s.io/apiserver v0.29.0 // indirect k8s.io/component-base v0.29.0 // indirect diff --git a/internal/client/client.go b/internal/client/client.go index 28ecc2f0fb..02b1e6aa56 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -517,12 +517,12 @@ func (a *APIClient) supportsMetricsResources() error { a.cache.Add(cacheMXAPIKey, supported, cacheExpiry) }() - dial, err := a.CachedDiscovery() + dial, err := a.Dial() if err != nil { log.Warn().Err(err).Msgf("Unable to dial discovery API") return err } - apiGroups, err := dial.ServerGroups() + apiGroups, err := dial.Discovery().ServerGroups() if err != nil { return err } diff --git a/internal/client/metrics.go b/internal/client/metrics.go index fe2e90baf1..c31ba6c5b0 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -90,7 +90,7 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL func (m *MetricsServer) checkAccess(ns, gvr, msg string) error { if !m.HasMetrics() { - return errors.New("No metrics-server detected on cluster") + return errors.New("no metrics-server detected on cluster") } auth, err := m.CanI(ns, gvr, ListAccess) @@ -193,7 +193,7 @@ func (m *MetricsServer) FetchNodeMetrics(ctx context.Context, n string) (*mv1bet mx, ok := mmx[n] if !ok { - return nil, fmt.Errorf("Unable to retrieve node metrics for %q", n) + return nil, fmt.Errorf("unable to retrieve node metrics for %q", n) } return mx, nil } @@ -283,7 +283,7 @@ func (m *MetricsServer) FetchPodMetrics(ctx context.Context, fqn string) (*mv1be } pmx, ok := mmx[fqn] if !ok { - return nil, fmt.Errorf("Unable to locate pod metrics for pod %q", fqn) + return nil, fmt.Errorf("unable to locate pod metrics for pod %q", fqn) } return pmx, nil diff --git a/internal/client/types.go b/internal/client/types.go index 24d66aad69..c6bcf760a1 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -117,7 +117,7 @@ type Connection interface { // HasMetrics checks if metrics server is available. HasMetrics() bool - // ValidNamespaces returns all available namespace names. + // ValidNamespaceNames returns all available namespace names. ValidNamespaceNames() (NamespaceNames, error) // IsValidNamespace checks if given namespace is known. diff --git a/internal/config/alias.go b/internal/config/alias.go index 6ce8365a3c..798a0576c7 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -4,10 +4,12 @@ package config import ( + "fmt" "os" "sync" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" ) @@ -120,18 +122,26 @@ func (a *Aliases) LoadFile(path string) error { if path == "" { return nil } - f, err := os.ReadFile(path) - if err == nil { - var aa Aliases - if err := yaml.Unmarshal(f, &aa); err != nil { - return err - } + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } - a.mx.Lock() - defer a.mx.Unlock() - for k, v := range aa.Alias { - a.Alias[k] = v - } + bb, err := os.ReadFile(path) + if err != nil { + return err + } + if err := data.JSONValidator.Validate(json.AliasesSchema, bb); err != nil { + return fmt.Errorf("validation failed for %q: %w", path, err) + } + + var aa Aliases + if err := yaml.Unmarshal(bb, &aa); err != nil { + return err + } + a.mx.Lock() + defer a.mx.Unlock() + for k, v := range aa.Alias { + a.Alias[k] = v } return nil diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index f65ddb9993..d8551bfccf 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -4,25 +4,59 @@ package config_test import ( + "fmt" + "os" + "slices" "testing" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/stretchr/testify/assert" ) +func TestAliasClear(t *testing.T) { + a := testAliases() + a.Clear() + + assert.Equal(t, 0, len(a.Keys())) +} + +func TestAliasKeys(t *testing.T) { + a := testAliases() + kk := a.Keys() + slices.Sort(kk) + + assert.Equal(t, []string{"a1", "a11", "a2", "a3"}, kk) +} + +func TestAliasShortNames(t *testing.T) { + a := testAliases() + ess := config.ShortNames{ + "gvr1": []string{"a1", "a11"}, + "gvr2": []string{"a2"}, + "gvr3": []string{"a3"}, + } + ss := a.ShortNames() + assert.Equal(t, len(ess), len(ss)) + for k, v := range ss { + v1, ok := ess[k] + assert.True(t, ok, fmt.Sprintf("missing: %q", k)) + slices.Sort(v) + assert.Equal(t, v1, v) + } +} + func TestAliasDefine(t *testing.T) { type aliasDef struct { cmd string aliases []string } - uu := []struct { - name string + uu := map[string]struct { aliases []aliasDef registeredCommands map[string]string }{ - { - name: "simple aliases", + "simple": { aliases: []aliasDef{ { cmd: "one", @@ -34,8 +68,7 @@ func TestAliasDefine(t *testing.T) { "duh": "one", }, }, - { - name: "duplicated aliases", + "duplicates": { aliases: []aliasDef{ { cmd: "one", @@ -54,9 +87,9 @@ func TestAliasDefine(t *testing.T) { }, } - for i := range uu { - u := uu[i] - t.Run(u.name, func(t *testing.T) { + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { configAlias := config.NewAliases() for _, aliases := range u.aliases { for _, a := range aliases.aliases { @@ -73,18 +106,35 @@ func TestAliasDefine(t *testing.T) { } func TestAliasesLoad(t *testing.T) { + config.AppConfigDir = "testdata/aliases" a := config.NewAliases() - assert.Nil(t, a.LoadFile("testdata/alias.yaml")) - assert.Equal(t, 2, len(a.Alias)) + assert.Nil(t, a.Load("testdata/aliases/plain.yaml")) + assert.Equal(t, 56, len(a.Alias)) } func TestAliasesSave(t *testing.T) { + assert.NoError(t, data.EnsureFullPath("/tmp/test-aliases", data.DefaultDirMod)) + defer assert.NoError(t, os.RemoveAll("/tmp/test-aliases")) + + config.AppAliasesFile = "/tmp/test-aliases/aliases.yaml" + a := testAliases() + c := len(a.Alias) + + assert.Equal(t, c, len(a.Alias)) + assert.Nil(t, a.Save()) + assert.Nil(t, a.LoadFile("/tmp/test-aliases/aliases.yaml")) + assert.Equal(t, c, len(a.Alias)) +} + +// Helpers... + +func testAliases() *config.Aliases { a := config.NewAliases() - a.Alias["test"] = "fred" - a.Alias["blee"] = "duh" + a.Alias["a1"] = "gvr1" + a.Alias["a11"] = "gvr1" + a.Alias["a2"] = "gvr2" + a.Alias["a3"] = "gvr3" - assert.Nil(t, a.SaveAliases("/tmp/a.yaml")) - assert.Nil(t, a.LoadFile("/tmp/a.yaml")) - assert.Equal(t, 2, len(a.Alias)) + return a } diff --git a/internal/config/benchmark_test.go b/internal/config/benchmark_test.go index 7a4b54caa8..cd80d44220 100644 --- a/internal/config/benchmark_test.go +++ b/internal/config/benchmark_test.go @@ -35,14 +35,14 @@ func TestBenchLoad(t *testing.T) { coCount int }{ "goodConfig": { - "testdata/b_good.yaml", + "testdata/benchmarks/b_good.yaml", 2, 1000, 2, 0, }, "malformed": { - "testdata/b_toast.yaml", + "testdata/benchmarks/b_toast.yaml", 1, 200, 0, @@ -103,7 +103,7 @@ func TestBenchServiceLoad(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - b, err := NewBench("testdata/b_good.yaml") + b, err := NewBench("testdata/benchmarks/b_good.yaml") assert.Nil(t, err) assert.Equal(t, 2, len(b.Benchmarks.Services)) @@ -122,11 +122,11 @@ func TestBenchServiceLoad(t *testing.T) { } func TestBenchReLoad(t *testing.T) { - b, err := NewBench("testdata/b_containers.yaml") + b, err := NewBench("testdata/benchmarks/b_containers.yaml") assert.Nil(t, err) assert.Equal(t, 2, b.Benchmarks.Defaults.C) - assert.Nil(t, b.Reload("testdata/b_containers_1.yaml")) + assert.NoError(t, b.Reload("testdata/benchmarks/b_containers_1.yaml")) assert.Equal(t, 20, b.Benchmarks.Defaults.C) } @@ -174,7 +174,7 @@ func TestBenchContainerLoad(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - b, err := NewBench("testdata/b_containers.yaml") + b, err := NewBench("testdata/benchmarks/b_containers.yaml") assert.Nil(t, err) assert.Equal(t, 2, len(b.Benchmarks.Services)) diff --git a/internal/config/color.go b/internal/config/color.go index 59fd4d0a0f..17fb595f94 100644 --- a/internal/config/color.go +++ b/internal/config/color.go @@ -17,6 +17,22 @@ const ( TransparentColor Color = "-" ) +// Colors tracks multiple colors. +type Colors []Color + +// Colors converts series string colors to colors. +func (c Colors) Colors() []tcell.Color { + cc := make([]tcell.Color, 0, len(c)) + for _, color := range c { + cc = append(cc, color.Color()) + } + + return cc +} + +// Color represents a color. +type Color string + // NewColor returns a new color. func NewColor(c string) Color { return Color(c) @@ -50,12 +66,3 @@ func (c Color) Color() tcell.Color { return tcell.GetColor(string(c)).TrueColor() } - -// Colors converts series string colors to colors. -func (c Colors) Colors() []tcell.Color { - cc := make([]tcell.Color, 0, len(c)) - for _, color := range c { - cc = append(cc, color.Color()) - } - return cc -} diff --git a/internal/config/color_test.go b/internal/config/color_test.go new file mode 100644 index 0000000000..09d41ae82e --- /dev/null +++ b/internal/config/color_test.go @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/derailed/tcell/v2" + "github.com/stretchr/testify/assert" +) + +func TestColors(t *testing.T) { + uu := map[string]struct { + cc []string + ee []tcell.Color + }{ + "empty": { + ee: []tcell.Color{}, + }, + "default": { + cc: []string{"default"}, + ee: []tcell.Color{tcell.ColorDefault}, + }, + "multi": { + cc: []string{ + "default", + "transparent", + "blue", + "green", + }, + ee: []tcell.Color{ + tcell.ColorDefault, + tcell.ColorDefault, + tcell.ColorBlue.TrueColor(), + tcell.ColorGreen.TrueColor(), + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cc := make(config.Colors, 0, len(u.cc)) + for _, c := range u.cc { + cc = append(cc, config.NewColor(c)) + } + assert.Equal(t, u.ee, cc.Colors()) + }) + } +} + +func TestColorString(t *testing.T) { + uu := map[string]struct { + c string + e string + }{ + "empty": { + e: "-", + }, + "default": { + c: "default", + e: "-", + }, + "transparent": { + c: "-", + e: "-", + }, + "blue": { + c: "blue", + e: "#0000ff", + }, + "lightgray": { + c: "lightgray", + e: "#d3d3d3", + }, + "hex": { + c: "#00ff00", + e: "#00ff00", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := config.NewColor(u.c) + assert.Equal(t, u.e, c.String()) + }) + } +} + +func TestColorToColor(t *testing.T) { + uu := map[string]struct { + c string + e tcell.Color + }{ + "default": { + c: "default", + e: tcell.ColorDefault, + }, + "transparent": { + c: "-", + e: tcell.ColorDefault, + }, + "aqua": { + c: "aqua", + e: tcell.ColorAqua.TrueColor(), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := config.NewColor(u.c) + assert.Equal(t, u.e, c.Color()) + }) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index c4b781b27d..d1ccc302a8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,12 +6,10 @@ package config import ( "fmt" "os" - "path/filepath" - "strings" - "github.com/adrg/xdg" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -19,25 +17,11 @@ import ( // Config tracks K9s configuration options. type Config struct { - K9s *K9s `yaml:"k9s"` + K9s *K9s `yaml:"k9s" json:"k9s"` conn client.Connection settings data.KubeSettings } -// K9sHome returns k9s configs home directory. -func K9sHome() string { - if isEnvSet(K9sEnvConfigDir) { - return os.Getenv(K9sEnvConfigDir) - } - - xdgK9sHome, err := xdg.ConfigFile(AppName) - if err != nil { - log.Fatal().Err(err).Msg("Unable to create configuration directory for k9s") - } - - return xdgK9sHome -} - // NewConfig creates a new default config. func NewConfig(ks data.KubeSettings) *Config { return &Config{ @@ -46,6 +30,16 @@ func NewConfig(ks data.KubeSettings) *Config { } } +// ContextHotKeysPath returns a context specific hotkeys file spec. +func (c *Config) ContextHotkeysPath() string { + ct, err := c.K9s.ActiveContext() + if err != nil { + return "" + } + + return AppContextHotkeysFile(ct.ClusterName, c.K9s.activeContextName) +} + // ContextAliasesPath returns a context specific aliases file spec. func (c *Config) ContextAliasesPath() string { ct, err := c.K9s.ActiveContext() @@ -53,13 +47,14 @@ func (c *Config) ContextAliasesPath() string { return "" } - return AppContextAliasesFile(ct.ClusterName, c.K9s.activeContextName) + return AppContextAliasesFile(ct.GetClusterName(), c.K9s.activeContextName) } // ContextPluginsPath returns a context specific plugins file spec. func (c *Config) ContextPluginsPath() string { ct, err := c.K9s.ActiveContext() if err != nil { + log.Error().Err(err).Msgf("active context load failed") return "" } @@ -68,7 +63,10 @@ func (c *Config) ContextPluginsPath() string { // Refine the configuration based on cli args. func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, cfg *client.Config) error { - if isSet(flags.Context) { + if flags == nil { + return nil + } + if isStringSet(flags.Context) { if _, err := c.K9s.ActivateContext(*flags.Context); err != nil { return err } @@ -88,7 +86,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c switch { case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces): ns = client.NamespaceAll - case isSet(flags.Namespace): + case isStringSet(flags.Namespace): ns = *flags.Namespace default: nss, err := c.K9s.ActiveContextNamespace() @@ -104,7 +102,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c return err } - return data.EnsureDirPath(c.K9s.GetScreenDumpDir(), data.DefaultDirMod) + return data.EnsureDirPath(c.K9s.AppScreenDumpDir(), data.DefaultDirMod) } // Reset resets the context to the new current context/cluster. @@ -115,7 +113,7 @@ func (c *Config) Reset() { func (c *Config) SetCurrentContext(n string) (*data.Context, error) { ct, err := c.K9s.ActivateContext(n) if err != nil { - return nil, fmt.Errorf("set current context %q failed: %w", n, err) + return nil, fmt.Errorf("set current context failed. %w", err) } return ct, nil @@ -138,21 +136,13 @@ func (c *Config) ActiveNamespace() string { return ns } -// ValidateFavorites ensure favorite ns are legit. -func (c *Config) ValidateFavorites() { - ct, err := c.K9s.ActiveContext() - if err != nil { - return - } - ct.Validate(c.conn, c.settings) -} - // FavNamespaces returns fav namespaces in the current context. func (c *Config) FavNamespaces() []string { ct, err := c.K9s.ActiveContext() if err != nil { return nil } + ct.Validate(c.conn, c.settings) return ct.Namespace.Favorites } @@ -209,23 +199,26 @@ func (c *Config) ActiveContextName() string { return c.K9s.activeContextName } +func (c *Config) Merge(c1 *Config) { + c.K9s.Merge(c1.K9s) +} + // Load loads K9s configuration from file. func (c *Config) Load(path string) error { - f, err := os.ReadFile(path) + bb, err := os.ReadFile(path) if err != nil { return err } + if err := data.JSONValidator.Validate(json.K9sSchema, bb); err != nil { + return fmt.Errorf("k9s config file %q load failed:\n%w", path, err) + } var cfg Config - if err := yaml.Unmarshal(f, &cfg); err != nil { + if err := yaml.Unmarshal(bb, &cfg); err != nil { return err } - if cfg.K9s != nil { - c.K9s.Refine(cfg.K9s) - } - if c.K9s.Logger == nil { - c.K9s.Logger = NewLogger() - } + c.Merge(&cfg) + return nil } @@ -235,7 +228,11 @@ func (c *Config) Save() error { if err := c.K9s.Save(); err != nil { return err } - return c.SaveFile(AppConfigFile) + if _, err := os.Stat(AppConfigFile); os.IsNotExist(err) { + return c.SaveFile(AppConfigFile) + } + + return nil } // SaveFile K9s configuration to disk. @@ -248,48 +245,26 @@ func (c *Config) SaveFile(path string) error { log.Error().Msgf("[Config] Unable to save K9s config file: %v", err) return err } + return os.WriteFile(path, cfg, 0644) } // Validate the configuration. func (c *Config) Validate() { + if c.K9s == nil { + c.K9s = NewK9s(c.conn, c.settings) + } + c.K9s.Validate(c.conn, c.settings) } -// Dump debug... +// Dump for debug... func (c *Config) Dump(msg string) { ct, err := c.K9s.ActiveContext() - if err != nil { - log.Debug().Msgf("Current Contexts: %s\n", ct.ClusterName) - } -} - -// YamlExtension tries to find the correct extension for a YAML file -func YamlExtension(path string) string { - if !isYamlFile(path) { - log.Error().Msgf("Config: File %s is not a yaml file", path) - return path - } - - // Strip any extension, if there is no extension the path will remain unchanged - path = strings.TrimSuffix(path, filepath.Ext(path)) - result := path + ".yml" - - if _, err := os.Stat(result); os.IsNotExist(err) { - return path + ".yaml" + if err == nil { + bb, _ := yaml.Marshal(ct) + fmt.Printf("Dump: %q\n%s\n", msg, string(bb)) + } else { + fmt.Println("BOOM!", err) } - - return result -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func isSet(s *string) bool { - return s != nil && len(*s) > 0 -} - -func isYamlFile(file string) bool { - ext := filepath.Ext(file) - return ext == ".yml" || ext == ".yaml" } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 280fac4a0a..21bdd56400 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,12 +4,16 @@ package config_test import ( + "errors" "fmt" "os" "path/filepath" "testing" + "github.com/adrg/xdg" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/mock" m "github.com/petergtz/pegomock" "github.com/rs/zerolog" @@ -21,37 +25,478 @@ func init() { zerolog.SetGlobalLevel(zerolog.FatalLevel) } +func TestConfigSave(t *testing.T) { + config.AppConfigFile = "/tmp/k9s-test/k9s.yaml" + sd := "/tmp/k9s-test/screen-dumps" + cl, ct := "cl-1", "ct-1-1" + _ = os.RemoveAll(("/tmp/k9s-test")) + + uu := map[string]struct { + ct string + flags *genericclioptions.ConfigFlags + k9sFlags *config.Flags + }{ + "happy": { + ct: "ct-1-1", + flags: &genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }, + k9sFlags: &config.Flags{ + ScreenDumpDir: &sd, + }, + }, + } + + for k := range uu { + xdg.Reload() + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, err := c.K9s.ActivateContext(u.ct) + assert.NoError(t, err) + if u.flags != nil { + c.K9s.Override(u.k9sFlags) + assert.NoError(t, c.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags))) + } + assert.NoError(t, c.Save()) + bb, err := os.ReadFile(config.AppConfigFile) + assert.NoError(t, err) + ee, err := os.ReadFile("testdata/configs/default.yaml") + assert.NoError(t, err) + assert.Equal(t, string(ee), string(bb)) + }) + } +} + +func TestSetActiveView(t *testing.T) { + var ( + cfgFile = "testdata/kubes/test.yaml" + view = "dp" + ) + + uu := map[string]struct { + ct string + flags *genericclioptions.ConfigFlags + k9sFlags *config.Flags + view string + e string + }{ + "empty": { + view: data.DefaultView, + e: data.DefaultView, + }, + "not-exists": { + ct: "fred", + view: data.DefaultView, + e: data.DefaultView, + }, + "happy": { + ct: "ct-1-1", + view: "xray", + e: "xray", + }, + "cli-override": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + }, + k9sFlags: &config.Flags{ + Command: &view, + }, + ct: "ct-1-1", + view: "xray", + e: "dp", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + if u.flags != nil { + assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags))) + c.K9s.Override(u.k9sFlags) + } + c.SetActiveView(u.view) + assert.Equal(t, u.e, c.ActiveView()) + }) + } +} + +func TestActiveContextName(t *testing.T) { + var ( + cfgFile = "testdata/kubes/test.yaml" + ct2 = "ct-1-2" + ) + + uu := map[string]struct { + flags *genericclioptions.ConfigFlags + k9sFlags *config.Flags + ct string + e string + }{ + "empty": {}, + "happy": { + ct: "ct-1-1", + e: "ct-1-1", + }, + "cli-override": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Context: &ct2, + }, + k9sFlags: &config.Flags{}, + ct: "ct-1-1", + e: "ct-1-2", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + if u.flags != nil { + assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags))) + c.K9s.Override(u.k9sFlags) + } + assert.Equal(t, u.e, c.ActiveContextName()) + }) + } +} + +func TestActiveView(t *testing.T) { + var ( + cfgFile = "testdata/kubes/test.yaml" + view = "dp" + ) + + uu := map[string]struct { + ct string + flags *genericclioptions.ConfigFlags + k9sFlags *config.Flags + e string + }{ + "empty": { + e: data.DefaultView, + }, + "not-exists": { + ct: "fred", + e: data.DefaultView, + }, + "happy": { + ct: "ct-1-1", + e: data.DefaultView, + }, + "cli-override": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + }, + k9sFlags: &config.Flags{ + Command: &view, + }, + e: "dp", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + if u.flags != nil { + assert.NoError(t, c.Refine(u.flags, nil, client.NewConfig(u.flags))) + c.K9s.Override(u.k9sFlags) + } + assert.Equal(t, u.e, c.ActiveView()) + }) + } +} + +func TestFavNamespaces(t *testing.T) { + uu := map[string]struct { + ct string + e []string + }{ + "empty": {}, + "not-exists": { + ct: "fred", + }, + "happy": { + ct: "ct-1-1", + e: []string{client.DefaultNamespace}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + assert.Equal(t, u.e, c.FavNamespaces()) + }) + } +} + +func TestContextAliasesPath(t *testing.T) { + uu := map[string]struct { + ct string + e string + }{ + "empty": {}, + "not-exists": { + ct: "fred", + }, + "happy": { + ct: "ct-1-1", + e: "/tmp/test/cl-1/ct-1-1/aliases.yaml", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + assert.Equal(t, u.e, c.ContextAliasesPath()) + }) + } +} + +func TestContextPluginsPath(t *testing.T) { + uu := map[string]struct { + ct string + e string + }{ + "empty": {}, + "happy": { + ct: "ct-1-1", + e: "/tmp/test/cl-1/ct-1-1/plugins.yaml", + }, + "not-exists": { + ct: "fred", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := mock.NewMockConfig() + _, _ = c.K9s.ActivateContext(u.ct) + assert.Equal(t, u.e, c.ContextPluginsPath()) + }) + } +} + +func TestConfigLoader(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/configs/k9s.yaml", + }, + "toast": { + f: "testdata/configs/k9s_toast.yaml", + err: `k9s config file "testdata/configs/k9s_toast.yaml" load failed: +Additional property disablePodCounts is not allowed +Additional property shellPods is not allowed +Invalid type. Expected: boolean, given: string`, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cfg := config.NewConfig(nil) + if err := cfg.Load(u.f); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestConfigSetCurrentContext(t *testing.T) { + uu := map[string]struct { + cl, ct string + err string + }{ + "happy": { + ct: "ct-1-2", + cl: "cl-1", + }, + "toast": { + ct: "fred", + cl: "cl-1", + err: `set current context failed. no context found for: "fred"`, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cfg := mock.NewMockConfig() + ct, err := cfg.SetCurrentContext(u.ct) + if err != nil { + assert.Equal(t, u.err, err.Error()) + return + } + assert.NoError(t, err) + assert.Equal(t, u.cl, ct.ClusterName) + }) + } +} + +func TestConfigCurrentContext(t *testing.T) { + var ( + cfgFile = "testdata/kubes/test.yaml" + ct2 = "ct-1-2" + ) + + uu := map[string]struct { + flags *genericclioptions.ConfigFlags + err error + context string + cluster string + namespace string + }{ + "override-context": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Context: &ct2, + }, + cluster: "cl-1", + context: "ct-1-2", + namespace: "ns-2", + }, + "use-current-context": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + }, + cluster: "cl-1", + context: "ct-1-1", + namespace: client.DefaultNamespace, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cfg := mock.NewMockConfig() + + err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags)) + assert.NoError(t, err) + ct, err := cfg.CurrentContext() + assert.NoError(t, err) + assert.Equal(t, u.cluster, ct.ClusterName) + assert.Equal(t, u.namespace, ct.Namespace.Active) + }) + } +} + func TestConfigRefine(t *testing.T) { var ( - cfgFile = "testdata/kubeconfig-test.yaml" - ctx, cluster, ns = "ct-1-1", "cl-1", "ns-1" + cfgFile = "testdata/kubes/test.yaml" + cl1 = "cl-1" + ct2 = "ct-1-2" + ns1, ns2, nsx = "ns-1", "ns-2", "ns-x" + true = true ) uu := map[string]struct { - flags *genericclioptions.ConfigFlags - issue bool - context, cluster, namespace string + flags *genericclioptions.ConfigFlags + k9sFlags *config.Flags + err error + context string + cluster string + namespace string }{ - "overrideNS": { + "no-override": { + namespace: "default", + }, + "override-cluster": { flags: &genericclioptions.ConfigFlags{ KubeConfig: &cfgFile, - Context: &ctx, - ClusterName: &cluster, - Namespace: &ns, + ClusterName: &cl1, }, - issue: false, - context: ctx, - cluster: cluster, - namespace: ns, + cluster: "cl-1", + context: "ct-1-1", + namespace: client.DefaultNamespace, }, - "badContext": { + "override-cluster-context": { flags: &genericclioptions.ConfigFlags{ KubeConfig: &cfgFile, - Context: &ns, - ClusterName: &cluster, - Namespace: &ns, + ClusterName: &cl1, + Context: &ct2, + }, + cluster: "cl-1", + context: "ct-1-2", + namespace: "ns-2", + }, + "override-bad-cluster": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + ClusterName: &ns1, + }, + cluster: "cl-1", + context: "ct-1-1", + namespace: client.DefaultNamespace, + }, + "override-ns": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Namespace: &ns2, + }, + cluster: "cl-1", + context: "ct-1-1", + namespace: "ns-2", + }, + "all-ns": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Namespace: &ns2, + }, + k9sFlags: &config.Flags{ + AllNamespaces: &true, }, - issue: true, + cluster: "cl-1", + context: "ct-1-1", + namespace: client.NamespaceAll, + }, + + "override-bad-ns": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Namespace: &nsx, + }, + cluster: "cl-1", + context: "ct-1-1", + namespace: "ns-x", + }, + "override-context": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Context: &ct2, + }, + cluster: "cl-1", + context: "ct-1-2", + namespace: "ns-2", + }, + "override-bad-context": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + Context: &ns1, + }, + err: errors.New(`no context found for: "ns-1"`), + }, + "use-current-context": { + flags: &genericclioptions.ConfigFlags{ + KubeConfig: &cfgFile, + }, + cluster: "cl-1", + context: "ct-1-1", + namespace: client.DefaultNamespace, }, } @@ -60,9 +505,9 @@ func TestConfigRefine(t *testing.T) { t.Run(k, func(t *testing.T) { cfg := mock.NewMockConfig() - err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags)) - if u.issue { - assert.NotNil(t, err) + err := cfg.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags)) + if err != nil { + assert.Equal(t, u.err, err) } else { assert.Nil(t, err) assert.Equal(t, u.context, cfg.K9s.ActiveContextName()) @@ -76,35 +521,29 @@ func TestConfigValidate(t *testing.T) { cfg := mock.NewMockConfig() cfg.SetConnection(mock.NewMockConnection()) - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) cfg.Validate() } func TestConfigLoad(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) assert.Equal(t, 2, cfg.K9s.RefreshRate) - assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize) assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount) -} - -func TestConfigLoadOldCfg(t *testing.T) { - cfg := mock.NewMockConfig() - - assert.Nil(t, cfg.Load("testdata/k9s_old.yaml")) + assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize) } func TestConfigLoadCrap(t *testing.T) { cfg := mock.NewMockConfig() - assert.NotNil(t, cfg.Load("testdata/k9s_not_there.yaml")) + assert.NotNil(t, cfg.Load("testdata/configs/k9s_not_there.yaml")) } func TestConfigSaveFile(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) cfg.K9s.RefreshRate = 100 cfg.K9s.ReadOnly = true @@ -113,26 +552,28 @@ func TestConfigSaveFile(t *testing.T) { cfg.Validate() path := filepath.Join("/tmp", "k9s.yaml") - err := cfg.SaveFile(path) - assert.Nil(t, err) + assert.NoError(t, cfg.SaveFile(path)) raw, err := os.ReadFile(path) assert.Nil(t, err) - assert.Equal(t, expectedConfig, string(raw)) + ee, err := os.ReadFile("testdata/configs/expected.yaml") + assert.Nil(t, err) + assert.Equal(t, string(ee), string(raw)) } func TestConfigReset(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) cfg.Reset() cfg.Validate() path := filepath.Join("/tmp", "k9s.yaml") - err := cfg.SaveFile(path) - assert.Nil(t, err) + assert.NoError(t, cfg.SaveFile(path)) - raw, err := os.ReadFile(path) + bb, err := os.ReadFile(path) + assert.Nil(t, err) + ee, err := os.ReadFile("testdata/configs/k9s.yaml") assert.Nil(t, err) - assert.Equal(t, resetConfig, string(raw)) + assert.Equal(t, string(ee), string(bb)) } // Helpers... @@ -147,86 +588,86 @@ func TestSetup(t *testing.T) { // ---------------------------------------------------------------------------- // Test Data... -var expectedConfig = `k9s: - liveViewAutoRefresh: true - screenDumpDir: /tmp - refreshRate: 100 - maxConnRetry: 5 - readOnly: true - noExitOnCtrlC: false - ui: - enableMouse: false - headless: false - logoless: false - crumbsless: false - noIcons: false - skipLatestRevCheck: false - disablePodCounting: false - shellPod: - image: busybox:1.35.0 - namespace: default - limits: - cpu: 100m - memory: 100Mi - imageScans: - enable: false - exclusions: - namespaces: [] - labels: {} - logger: - tail: 500 - buffer: 800 - sinceSeconds: -1 - fullScreenLogs: false - textWrap: false - showTime: false - thresholds: - cpu: - critical: 90 - warn: 70 - memory: - critical: 90 - warn: 70 -` - -var resetConfig = `k9s: - liveViewAutoRefresh: true - screenDumpDir: /tmp - refreshRate: 2 - maxConnRetry: 5 - readOnly: false - noExitOnCtrlC: false - ui: - enableMouse: false - headless: false - logoless: false - crumbsless: false - noIcons: false - skipLatestRevCheck: false - disablePodCounting: false - shellPod: - image: busybox:1.35.0 - namespace: default - limits: - cpu: 100m - memory: 100Mi - imageScans: - enable: false - exclusions: - namespaces: [] - labels: {} - logger: - tail: 200 - buffer: 2000 - sinceSeconds: -1 - fullScreenLogs: false - textWrap: false - showTime: false - thresholds: - cpu: - critical: 90 - warn: 70 - memory: - critical: 90 - warn: 70 -` +// var expectedConfig = `k9s: +// liveViewAutoRefresh: true +// screenDumpDir: /tmp/screen-dumps +// refreshRate: 100 +// maxConnRetry: 5 +// readOnly: true +// noExitOnCtrlC: false +// ui: +// enableMouse: false +// headless: false +// logoless: false +// crumbsless: false +// noIcons: false +// skipLatestRevCheck: false +// disablePodCounting: false +// shellPod: +// image: busybox:1.35.0 +// namespace: default +// limits: +// cpu: 100m +// memory: 100Mi +// imageScans: +// enable: false +// exclusions: +// namespaces: [] +// labels: {} +// logger: +// tail: 500 +// buffer: 800 +// sinceSeconds: -1 +// fullScreen: false +// textWrap: false +// showTime: false +// thresholds: +// cpu: +// critical: 90 +// warn: 70 +// memory: +// critical: 90 +// warn: 70 +// ` + +// var resetConfig = `k9s: +// liveViewAutoRefresh: true +// screenDumpDir: /tmp/screen-dumps +// refreshRate: 2 +// maxConnRetry: 5 +// readOnly: false +// noExitOnCtrlC: false +// ui: +// enableMouse: false +// headless: false +// logoless: false +// crumbsless: false +// noIcons: false +// skipLatestRevCheck: false +// disablePodCounting: false +// shellPod: +// image: busybox:1.35.0 +// namespace: default +// limits: +// cpu: 100m +// memory: 100Mi +// imageScans: +// enable: false +// exclusions: +// namespaces: [] +// labels: {} +// logger: +// tail: 200 +// buffer: 2000 +// sinceSeconds: -1 +// fullScreen: false +// textWrap: false +// showTime: false +// thresholds: +// cpu: +// critical: 90 +// warn: 70 +// memory: +// critical: 90 +// warn: 70 +// ` diff --git a/internal/config/data/config.go b/internal/config/data/config.go index 3faf5fcc27..234adc31d9 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -18,27 +18,33 @@ type Config struct { Context *Context `yaml:"k9s"` } +// NewConfig returns a new config. func NewConfig(ct *api.Context) *Config { return &Config{ Context: NewContextFromConfig(ct), } } +// Validate ensures config is in norms. func (c *Config) Validate(conn client.Connection, ks KubeSettings) { + if c.Context == nil { + c.Context = NewContext() + } c.Context.Validate(conn, ks) } +// Dump used for debugging. func (c *Config) Dump(w io.Writer) { bb, _ := yaml.Marshal(&c) fmt.Fprintf(w, "%s\n", string(bb)) } +// Save saves the config to disk. func (c *Config) Save(path string) error { if err := EnsureDirPath(path, DefaultDirMod); err != nil { return err } - cfg, err := yaml.Marshal(c) if err != nil { return err diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 8f38676e54..32a777918d 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -4,6 +4,8 @@ package data import ( + "sync" + "github.com/derailed/k9s/internal/client" "k8s.io/client-go/tools/clientcmd/api" ) @@ -14,12 +16,13 @@ const DefaultPFAddress = "localhost" // Context tracks K9s context configuration. type Context struct { ClusterName string `yaml:"cluster,omitempty"` - ReadOnly bool `yaml:"readOnly"` + ReadOnly *bool `yaml:"readOnly,omitempty"` Skin string `yaml:"skin,omitempty"` Namespace *Namespace `yaml:"namespace"` View *View `yaml:"view"` FeatureGates FeatureGates `yaml:"featureGates"` PortForwardAddress string `yaml:"portForwardAddress"` + mx sync.RWMutex } // NewContext creates a new cluster configuration. @@ -32,6 +35,7 @@ func NewContext() *Context { } } +// NewContextFromConfig returns a config based on a kubecontext. func NewContextFromConfig(cfg *api.Context) *Context { return &Context{ Namespace: NewActiveNamespace(cfg.Namespace), @@ -42,12 +46,32 @@ func NewContextFromConfig(cfg *api.Context) *Context { } } -// Validate a context config. +// NewContextFromKubeConfig returns a new instance based on kubesettings or an error. +func NewContextFromKubeConfig(ks KubeSettings) (*Context, error) { + ct, err := ks.CurrentContext() + if err != nil { + return nil, err + } + + return NewContextFromConfig(ct), nil +} + +func (c *Context) GetClusterName() string { + c.mx.RLock() + defer c.mx.RUnlock() + + return c.ClusterName +} + +// Validate ensures a context config is tip top. func (c *Context) Validate(conn client.Connection, ks KubeSettings) { + c.mx.Lock() + defer c.mx.Unlock() + if c.PortForwardAddress == "" { c.PortForwardAddress = DefaultPFAddress } - if cl, err := ks.CurrentClusterName(); err != nil { + if cl, err := ks.CurrentClusterName(); err == nil { c.ClusterName = cl } diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index d20e545652..8e6c8b17b1 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -5,9 +5,11 @@ package data import ( "errors" + "fmt" "os" "path/filepath" + "github.com/derailed/k9s/internal/config/json" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/client-go/tools/clientcmd/api" @@ -60,6 +62,10 @@ func (d *Dir) loadConfig(path string) (*Config, error) { if err != nil { return nil, err } + if err := JSONValidator.Validate(json.ContextSchema, bb); err != nil { + return nil, fmt.Errorf("validation failed for %q: %w", path, err) + } + var cfg Config if err := yaml.Unmarshal(bb, &cfg); err != nil { return nil, err diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go index ed82e4584b..0b41d4328e 100644 --- a/internal/config/data/helpers_test.go +++ b/internal/config/data/helpers_test.go @@ -77,7 +77,7 @@ func TestEnsureDirPathNone(t *testing.T) { func TestEnsureDirPathNoOpt(t *testing.T) { var mod os.FileMode = 0744 dir := filepath.Join("/tmp", "k9s-test") - os.Remove(dir) + assert.NoError(t, os.RemoveAll(dir)) assert.NoError(t, os.Mkdir(dir, mod)) path := filepath.Join(dir, "duh.yaml") diff --git a/internal/config/data/types.go b/internal/config/data/types.go index d798f77b8d..5d7c021440 100644 --- a/internal/config/data/types.go +++ b/internal/config/data/types.go @@ -6,9 +6,13 @@ package data import ( "os" + "github.com/derailed/k9s/internal/config/json" "k8s.io/client-go/tools/clientcmd/api" ) +// JSONValidator validate yaml configurations. +var JSONValidator = json.NewValidator() + const ( // DefaultDirMod default unix perms for k9s directory. DefaultDirMod os.FileMode = 0744 diff --git a/internal/config/files.go b/internal/config/files.go index 73ee51b6e1..b4b1e02bdb 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -80,7 +80,7 @@ var ( AppHotKeysFile string ) -// InitLogsLoc initializes K9s logs location. +// InitLogLoc initializes K9s logs location. func InitLogLoc() error { var appLogDir string switch { @@ -273,5 +273,9 @@ func EnsureHotkeysCfgFile() (string, error) { // SkinFileFromName generate skin file path from spec. func SkinFileFromName(n string) string { + if n == "" { + n = "stock" + } + return filepath.Join(AppSkinsDir, n+".yaml") } diff --git a/internal/config/files_int_test.go b/internal/config/files_int_test.go new file mode 100644 index 0000000000..e08766f1f1 --- /dev/null +++ b/internal/config/files_int_test.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/adrg/xdg" + "github.com/derailed/k9s/internal/config/data" + "github.com/stretchr/testify/assert" +) + +func Test_initXDGLocs(t *testing.T) { + tmp, err := UserTmpDir() + assert.NoError(t, err) + + os.Unsetenv("XDG_CONFIG_HOME") + os.Unsetenv("XDG_CACHE_HOME") + os.Unsetenv("XDG_STATE_HOME") + os.Unsetenv("XDG_DATA_HOME") + + os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmp, "k9s-xdg", "config")) + os.Setenv("XDG_CACHE_HOME", filepath.Join(tmp, "k9s-xdg", "cache")) + os.Setenv("XDG_STATE_HOME", filepath.Join(tmp, "k9s-xdg", "state")) + os.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "k9s-xdg", "data")) + xdg.Reload() + + uu := map[string]struct { + configDir string + configFile string + benchmarksDir string + contextsDir string + contextHotkeysFile string + contextConfig string + dumpsDir string + benchDir string + hkFile string + }{ + "check-env": { + configDir: filepath.Join(tmp, "k9s-xdg", "config", "k9s"), + configFile: filepath.Join(tmp, "k9s-xdg", "config", "k9s", data.MainConfigFile), + benchmarksDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "benchmarks"), + contextsDir: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters"), + contextHotkeysFile: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters", "cl-1", "ct-1-1", "hotkeys.yaml"), + contextConfig: filepath.Join(tmp, "k9s-xdg", "data", "k9s", "clusters", "cl-1", "ct-1-1", data.MainConfigFile), + dumpsDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "screen-dumps", "cl-1", "ct-1-1"), + benchDir: filepath.Join(tmp, "k9s-xdg", "state", "k9s", "benchmarks", "cl-1", "ct-1-1"), + hkFile: filepath.Join(tmp, "k9s-xdg", "config", "k9s", "hotkeys.yaml"), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.NoError(t, initXDGLocs()) + assert.Equal(t, u.configDir, AppConfigDir) + assert.Equal(t, u.configFile, AppConfigFile) + assert.Equal(t, u.benchmarksDir, AppBenchmarksDir) + assert.Equal(t, u.contextsDir, AppContextsDir) + assert.Equal(t, u.contextHotkeysFile, AppContextHotkeysFile("cl-1", "ct-1-1")) + assert.Equal(t, u.contextConfig, AppContextConfig("cl-1", "ct-1-1")) + dir, err := DumpsDir("cl-1", "ct-1-1") + assert.NoError(t, err) + assert.Equal(t, u.dumpsDir, dir) + bdir, err := EnsureBenchmarksDir("cl-1", "ct-1-1") + assert.NoError(t, err) + assert.Equal(t, u.benchDir, bdir) + hk, err := EnsureHotkeysCfgFile() + assert.NoError(t, err) + assert.Equal(t, u.hkFile, hk) + }) + } +} diff --git a/internal/config/files_test.go b/internal/config/files_test.go index fb946ff2ff..02d53e4620 100644 --- a/internal/config/files_test.go +++ b/internal/config/files_test.go @@ -58,10 +58,11 @@ func TestInitLogLoc(t *testing.T) { }) } } + func TestEnsureBenchmarkCfg(t *testing.T) { os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") assert.NoError(t, config.InitLocs()) - defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) + defer assert.NoError(t, os.RemoveAll("/tmp/test-config")) assert.NoError(t, data.EnsureFullPath("/tmp/test-config/clusters/cl-1/ct-2", data.DefaultDirMod)) assert.NoError(t, os.WriteFile("/tmp/test-config/clusters/cl-1/ct-2/benchmarks.yaml", []byte{}, data.DefaultFileMod)) @@ -95,3 +96,28 @@ func TestEnsureBenchmarkCfg(t *testing.T) { }) } } + +func TestSkinFileFromName(t *testing.T) { + config.AppSkinsDir = "/tmp/k9s-test/skins" + defer assert.NoError(t, os.RemoveAll("/tmp/k9s-test/skins")) + + uu := map[string]struct { + n string + e string + }{ + "empty": { + e: "/tmp/k9s-test/skins/stock.yaml", + }, + "happy": { + n: "fred-blee", + e: "/tmp/k9s-test/skins/fred-blee.yaml", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, config.SkinFileFromName(u.n)) + }) + } +} diff --git a/internal/config/flags_test.go b/internal/config/flags_test.go new file mode 100644 index 0000000000..907c47955a --- /dev/null +++ b/internal/config/flags_test.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestNewFlags(t *testing.T) { + config.AppDumpsDir = "/tmp/k9s-test/screen-dumps" + config.AppLogFile = "/tmp/k9s-test/k9s.log" + + f := config.NewFlags() + assert.Equal(t, 2, *f.RefreshRate) + assert.Equal(t, "info", *f.LogLevel) + assert.Equal(t, "/tmp/k9s-test/k9s.log", *f.LogFile) + assert.Equal(t, config.AppDumpsDir, *f.ScreenDumpDir) + assert.Empty(t, *f.Command) + assert.False(t, *f.Headless) + assert.False(t, *f.Logoless) + assert.False(t, *f.AllNamespaces) + assert.False(t, *f.ReadOnly) + assert.False(t, *f.Write) + assert.False(t, *f.Crumbsless) +} diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 752644d7d3..af04c2fbe5 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -13,6 +13,19 @@ import ( v1 "k8s.io/api/core/v1" ) +func isBoolSet(b *bool) bool { + return b != nil && *b +} + +func isStringSet(s *string) bool { + return s != nil && len(*s) > 0 +} + +func isYamlFile(file string) bool { + ext := filepath.Ext(file) + return ext == ".yml" || ext == ".yaml" +} + // isEnvSet checks if env var is set. func isEnvSet(env string) bool { return os.Getenv(env) != "" diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index b98c7c436e..4aec92acdb 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -4,8 +4,11 @@ package config import ( + "fmt" "os" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" "gopkg.in/yaml.v2" ) @@ -30,19 +33,32 @@ func NewHotKeys() HotKeys { } // Load K9s plugins. -func (h HotKeys) Load() error { - return h.LoadHotKeys(AppHotKeysFile) +func (h HotKeys) Load(path string) error { + if err := h.LoadHotKeys(AppHotKeysFile); err != nil { + return err + } + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } + + return h.LoadHotKeys(path) } // LoadHotKeys loads plugins from a given file. func (h HotKeys) LoadHotKeys(path string) error { - f, err := os.ReadFile(path) + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } + bb, err := os.ReadFile(path) if err != nil { return err } + if err := data.JSONValidator.Validate(json.HotkeysSchema, bb); err != nil { + return fmt.Errorf("validation failed for %q: %w", path, err) + } var hh HotKeys - if err := yaml.Unmarshal(f, &hh); err != nil { + if err := yaml.Unmarshal(bb, &hh); err != nil { return err } for k, v := range hh.HotKey { diff --git a/internal/config/hotkey_test.go b/internal/config/hotkey_test.go index 28936d3490..66c986afc5 100644 --- a/internal/config/hotkey_test.go +++ b/internal/config/hotkey_test.go @@ -12,7 +12,7 @@ import ( func TestHotKeyLoad(t *testing.T) { h := config.NewHotKeys() - assert.Nil(t, h.LoadHotKeys("testdata/hotkeys.yaml")) + assert.NoError(t, h.LoadHotKeys("testdata/hotkeys/hotkeys.yaml")) assert.Equal(t, 1, len(h.HotKey)) diff --git a/internal/config/json/schemas/aliases.json b/internal/config/json/schemas/aliases.json new file mode 100644 index 0000000000..7ab1e6fcd8 --- /dev/null +++ b/internal/config/json/schemas/aliases.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s aliases schema", + "type": "object", + "additionalProperties": false, + "properties": { + "aliases": { + "type": "object", + "additionalProperties": { "type": "string" }, + "required": [] + } + }, + "required": ["aliases"] +} diff --git a/internal/config/json/schemas/context.json b/internal/config/json/schemas/context.json new file mode 100644 index 0000000000..e392dd7e8d --- /dev/null +++ b/internal/config/json/schemas/context.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s context config schema", + "type": "object", + "additionalProperties": false, + "properties": { + "k9s": { + "additionalProperties": false, + "properties": { + "cluster": { "type": "string" }, + "readOnly": {"type": "boolean"}, + "skin": { "type": "string" }, + "portForwardAddress": { "type": "string" }, + "namespace": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": {"type": "string"}, + "lockFavorites": {"type": "boolean"}, + "favorites": { + "type": "array", + "items": {"type": "string"} + } + } + }, + "view": { + "type": "object", + "additionalProperties": false, + "properties": { + "active": { "type": "string" } + } + }, + "featureGates": { + "type": "object", + "additionalProperties": false, + "properties": { + "nodeShell": { "type": "boolean" } + } + } + } + } + }, + "required": ["k9s"] +} \ No newline at end of file diff --git a/internal/config/json/schemas/hotkeys.json b/internal/config/json/schemas/hotkeys.json new file mode 100644 index 0000000000..b567d6c917 --- /dev/null +++ b/internal/config/json/schemas/hotkeys.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s hotkeys schema", + "type": "object", + "additionalProperties": false, + "properties": { + "hotKeys": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "shortCut": {"type": "string"}, + "description": {"type": "string"}, + "command": {"type": "string"} + } + } + } + }, + "required": ["hotKeys"] +} diff --git a/internal/config/json/schemas/k9s.json b/internal/config/json/schemas/k9s.json new file mode 100644 index 0000000000..42d2cb394e --- /dev/null +++ b/internal/config/json/schemas/k9s.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s config schema", + "type": "object", + "additionalProperties": false, + "properties": { + "k9s": { + "additionalProperties": false, + "properties": { + "liveViewAutoRefresh": { "type": "boolean" }, + "screenDumpDir": {"type": "string"}, + "refreshRate": { "type": "integer" }, + "maxConnRetry": { "type": "integer" }, + "readOnly": { "type": "boolean" }, + "noExitOnCtrlC": { "type": "boolean" }, + "skipLatestRevCheck": { "type": "boolean" }, + "disablePodCounting": { "type": "boolean" }, + "ui": { + "type": "object", + "additionalProperties": false, + "properties": { + "enableMouse": {"type": "boolean"}, + "headless": {"type": "boolean"}, + "logoless": {"type": "boolean"}, + "crumbsless": {"type": "boolean"}, + "noIcons": {"type": "boolean"}, + "reactive": {"type": "boolean"}, + "skin": {"type": "string"} + } + }, + "shellPod": { + "type": "object", + "additionalProperties": false, + "properties": { + "image": { "type": "string" }, + "namespace": { "type": "string" }, + "limits": { + "type": "object", + "properties": { + "cpu": { "type": "string" }, + "memory": { "type": "string" } + }, + "required": ["cpu", "memory"] + } + }, + "required": ["image", "namespace", "limits"] + }, + "imageScans": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { "type": "boolean" }, + "namespace": { "type": "string" }, + "exclusions": { + "type": "object", + "properties": { + "namespaces": { + "type": "array", + "items": { "type": "string" } + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { "type": "string" } + } + } + } + } + }, + "required": ["enable"] + }, + "logger": { + "type": "object", + "additionalProperties": false, + "properties": { + "tail": {"type": "integer"}, + "buffer": {"type": "integer"}, + "sinceSeconds": {"type": "integer"}, + "fullScreen": {"type": "boolean"}, + "textWrap": {"type": "boolean"}, + "showTime": {"type": "boolean"} + } + }, + "thresholds": { + "type": "object", + "additionalProperties": false, + "properties": { + "cpu": { + "type": "object", + "properties": { + "critical": {"type": "integer"}, + "warn": {"type": "integer"} + } + }, + "memory": { + "type": "object", + "properties": { + "critical": {"type": "integer"}, + "warn": {"type": "integer"} + } + } + } + } + } + } + }, + "required": ["k9s"] +} \ No newline at end of file diff --git a/internal/config/json/schemas/plugins.json b/internal/config/json/schemas/plugins.json new file mode 100644 index 0000000000..3445bd165b --- /dev/null +++ b/internal/config/json/schemas/plugins.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s plugins schema", + "type": "object", + "additionalProperties": false, + "properties": { + "plugins": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "shortCut": { "type": "string" }, + "description": { "type": "string" }, + "confirm": { "type": "boolean" }, + "scopes": { + "type": "array", + "items": { "type": "string" } + }, + "command": { "type": "string" }, + "background": { "type": "boolean" }, + "args": { + "type": "array", + "items": { "type": ["string", "number"] } + } + }, + "required": ["shortCut", "description", "scopes", "command"] + }, + "required": [] + } + }, + "required": ["plugins"] +} diff --git a/internal/config/json/schemas/skin.json b/internal/config/json/schemas/skin.json new file mode 100644 index 0000000000..0dd6c07f22 --- /dev/null +++ b/internal/config/json/schemas/skin.json @@ -0,0 +1,185 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s skin schema", + "type": "object", + "additionalProperties": true, + "properties": { + "k9s": { + "type": "object", + "additionalProperties": false, + "properties": { + "body": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "logoColor": {"type": "string"} + } + }, + "prompt": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "suggestColor": {"type": "string"} + } + }, + "info": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "sectionColor": {"type": "string"} + } + }, + "help": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "keyColor": {"type": "string"}, + "numKeyColor": {"type": "string"}, + "sectionColor": {"type": "string"} + } + }, + "dialog": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "buttonFgColor": {"type": "string"}, + "buttonBgColor": {"type": "string"}, + "buttonFocusFgColor": {"type": "string"}, + "buttonFocusBgColor": {"type": "string"}, + "labelFgColor": {"type": "string"}, + "fieldFgColor": {"type": "string"} + } + }, + "frame": { + "type": "object", + "properties": { + "border": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"} + } + }, + "menu": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "keyColor": {"type": "string"}, + "numKeyColor": {"type": "string"} + } + }, + "crumbs": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "keyColor": {"type": "string"}, + "activeColor": {"type": "string"} + } + }, + "status": { + "type": "object", + "properties": { + "newColor": {"type": "string"}, + "modifyColor": {"type": "string"}, + "addColor:": {"type": "string"}, + "errorColor": {"type": "string"}, + "highlightColor": {"type": "string"}, + "killColor": {"type": "string"}, + "completedColor": {"type": "string"} + } + }, + "title": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor":{"type": "string"}, + "highlightColor": {"type": "string"}, + "counterColor":{"type": "string"}, + "filterColor": {"type": "string"} + } + } + } + }, + "views": { + "type": "object", + "properties": { + "charts": { + "type": "object", + "properties": { + "bgColor": {"type": "string"}, + "defaultDialColors": { + "type": "array", + "items": {"type": "string"} + }, + "defaultChartColors": { + "type": "array", + "items": {"type": "string"} + } + }, + "table": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "cursorFgColor": {"type": "string"}, + "cursorBgColor": {"type": "string"}, + "header": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"} + } + } + } + } + }, + "xray": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "cursorFgColor": {"type": "string"}, + "graphicColor": {"type": "string"}, + "showIcons": {"type": "boolean"} + } + }, + "yaml": { + "type": "object", + "properties": { + "keyColor": {"type": "string"}, + "colonColor": {"type": "string"}, + "valueColor": {"type": "string"} + } + }, + "logs": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "indicator": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "fgColor": {"type": "string"}, + "bgColor": {"type": "string"}, + "toggleOnColor": {"type": "string"}, + "toggleOffColor": {"type": "string"} + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/internal/config/json/schemas/views.json b/internal/config/json/schemas/views.json new file mode 100644 index 0000000000..6b3971c501 --- /dev/null +++ b/internal/config/json/schemas/views.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "K9s views schema", + "type": "object", + "additionalProperties": false, + "properties": { + "views": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "sortColumn": { "type": "string" }, + "columns": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["columns"] + } + } + }, + "required": ["views"] +} diff --git a/internal/config/json/testdata/aliases/cool.yaml b/internal/config/json/testdata/aliases/cool.yaml new file mode 100644 index 0000000000..60f0867c70 --- /dev/null +++ b/internal/config/json/testdata/aliases/cool.yaml @@ -0,0 +1,3 @@ +aliases: + blee: duh + fred: zorg diff --git a/internal/config/json/testdata/aliases/toast.yaml b/internal/config/json/testdata/aliases/toast.yaml new file mode 100644 index 0000000000..3ba24ef386 --- /dev/null +++ b/internal/config/json/testdata/aliases/toast.yaml @@ -0,0 +1,3 @@ +alias: + blee: duh + fred: zorg diff --git a/internal/config/json/testdata/context/cool.yaml b/internal/config/json/testdata/context/cool.yaml new file mode 100644 index 0000000000..fd2c67718b --- /dev/null +++ b/internal/config/json/testdata/context/cool.yaml @@ -0,0 +1,15 @@ +k9s: + cluster: kind-dashb + readOnly: false + skin: nightfox + namespace: + active: default + lockFavorites: false + favorites: + - kube-system + - default + view: + active: pod + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/json/testdata/context/toast.yaml b/internal/config/json/testdata/context/toast.yaml new file mode 100644 index 0000000000..997914b4dc --- /dev/null +++ b/internal/config/json/testdata/context/toast.yaml @@ -0,0 +1,16 @@ +k9s: + cluster: kind-dashb + readOnly: false + skin: nightfox + namespaces: + active: default + lockFavorites: false + favorites: + - kube-system + - default + view: + active: pod + fred: blee + featureGates: + nodeShell: false + portForwardAddress: localhost diff --git a/internal/config/json/testdata/hotkeys/cool.yaml b/internal/config/json/testdata/hotkeys/cool.yaml new file mode 100644 index 0000000000..9e8e11d680 --- /dev/null +++ b/internal/config/json/testdata/hotkeys/cool.yaml @@ -0,0 +1,33 @@ +hotKey: + shift-0: + shortCut: Shift-0 + description: Popeye + command: popeye + shift-1: + shortCut: Shift-1 + description: View deployments + command: dp + shift-2: + shortCut: Shift-2 + description: View services + command: service + shift-3: + shortCut: Shift-3 + description: View statefulsets + command: sts + shift-4: + shortCut: Shift-4 + description: Xray Deployments + command: xray dp + shift-5: + shortCut: Shift-5 + description: Xray StatefulSets + command: xray sts + shift-6: + shortCut: Shift-6 + description: Xray DaemonSets + command: xray ds + shift-7: + shortCut: Shift-7 + description: Xray Services + command: xray svc diff --git a/internal/config/json/testdata/k9s/cool.yaml b/internal/config/json/testdata/k9s/cool.yaml new file mode 100644 index 0000000000..d09da47b80 --- /dev/null +++ b/internal/config/json/testdata/k9s/cool.yaml @@ -0,0 +1,40 @@ +k9s: + liveViewAutoRefresh: false + screenDumpDir: /Users/fernand/.local/state/k9s/screen-dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 100 + buffer: 5000 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/json/testdata/k9s/toast.yaml b/internal/config/json/testdata/k9s/toast.yaml new file mode 100644 index 0000000000..b380e0a311 --- /dev/null +++ b/internal/config/json/testdata/k9s/toast.yaml @@ -0,0 +1,34 @@ +k9s: + liveViewAutoRefresh: false + screenDumpDir: /Users/fernand/.local/state/k9s/screen-dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + skipLatestRevCheck: false + disablePodCounting: false + shellPods: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 100 + buffer: 5000 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/json/testdata/plugins/cool.yaml b/internal/config/json/testdata/plugins/cool.yaml new file mode 100644 index 0000000000..bfc98dcd3b --- /dev/null +++ b/internal/config/json/testdata/plugins/cool.yaml @@ -0,0 +1,23 @@ +plugins: + blee: + shortCut: g + confirm: false + description: blee + scopes: + - namespaces + command: sh + background: false + args: + - -c + - "blee bla" + duh: + shortCut: h + confirm: true + description: duh + scopes: + - all + command: sh + background: true + args: + - -c + - "duh fred" diff --git a/internal/config/json/testdata/plugins/toast.yaml b/internal/config/json/testdata/plugins/toast.yaml new file mode 100644 index 0000000000..43adeb7a47 --- /dev/null +++ b/internal/config/json/testdata/plugins/toast.yaml @@ -0,0 +1,21 @@ +plugins: + blee: + shortCuts: g + confirm: false + description: blee + scopes: + - namespaces + command: sh + background: false + args: + - -c + - "blee bla" + duh: + shortCut: h + confirm: true + description: duh + command: sh + background: true + args: + - -c + - "duh fred" diff --git a/internal/config/json/testdata/skins/cool.yaml b/internal/config/json/testdata/skins/cool.yaml new file mode 100644 index 0000000000..187d344b70 --- /dev/null +++ b/internal/config/json/testdata/skins/cool.yaml @@ -0,0 +1,109 @@ +# ----------------------------------------------------------------------------- +# K9s Nightfox Theme +# Based on the Nightfox.nvim color scheme: +# https://github.com/EdenEast/nightfox.nvim +# ----------------------------------------------------------------------------- + +# Styles... +foreground: &foreground "#cdcecf" +background: &background "#192330" +current_line: ¤t_line "#2b3b51" +selection: &selection "#2b3b51" +comment: &comment "#738091" +cyan: &cyan "#63cdcf" +green: &green "#81b29a" +orange: &orange "#f4a261" +magenta: &magenta "#9d79d6" +blue: &blue "#719cd6" +red: &red "#c94f6d" + +# Skin... +k9s: + body: + fgColor: *foreground + bgColor: *background + logoColor: *blue + prompt: + fgColor: *foreground + bgColor: *background + suggestColor: *orange + info: + fgColor: *magenta + sectionColor: *foreground + help: + fgColor: *foreground + bgColor: *background + keyColor: *magenta + numKeyColor: *magenta + sectionColor: *foreground + dialog: + fgColor: *foreground + bgColor: *background + buttonFgColor: *foreground + buttonBgColor: *magenta + buttonFocusFgColor: white + buttonFocusBgColor: *cyan + labelFgColor: *orange + fieldFgColor: *foreground + frame: + border: + fgColor: *selection + focusColor: *current_line + menu: + fgColor: *foreground + keyColor: *magenta + numKeyColor: *magenta + crumbs: + fgColor: *foreground + bgColor: *current_line + activeColor: *current_line + status: + newColor: *cyan + modifyColor: *blue + addColor: *green + errorColor: *red + highlightColor: *orange + killColor: *comment + completedColor: *comment + title: + fgColor: *foreground + bgColor: *current_line + highlightColor: *orange + counterColor: *blue + filterColor: *magenta + views: + charts: + bgColor: default + defaultDialColors: + - *blue + - *red + defaultChartColors: + - *blue + - *red + table: + fgColor: *foreground + bgColor: *background + cursorFgColor: *selection + cursorBgColor: *current_line + header: + fgColor: *foreground + bgColor: *background + sorterColor: *cyan + xray: + fgColor: *foreground + bgColor: *background + cursorColor: *current_line + graphicColor: *blue + showIcons: false + yaml: + keyColor: *magenta + colonColor: *blue + valueColor: *foreground + logs: + fgColor: *foreground + bgColor: *background + indicator: + fgColor: *foreground + bgColor: *selection + toggleOnColor: *magenta + toggleOffColor: *blue diff --git a/internal/config/json/testdata/skins/toast.yaml b/internal/config/json/testdata/skins/toast.yaml new file mode 100644 index 0000000000..271bf6a5a0 --- /dev/null +++ b/internal/config/json/testdata/skins/toast.yaml @@ -0,0 +1,103 @@ +# ----------------------------------------------------------------------------- +# K9s Nightfox Theme +# Based on the Nightfox.nvim color scheme: +# https://github.com/EdenEast/nightfox.nvim +# ----------------------------------------------------------------------------- + +# Styles... +foreground: &foreground "#cdcecf" +background: &background "#192330" +current_line: ¤t_line "#2b3b51" +selection: &selection "#2b3b51" +comment: &comment "#738091" +cyan: &cyan "#63cdcf" +green: &green "#81b29a" +orange: &orange "#f4a261" +magenta: &magenta "#9d79d6" +blue: &blue "#719cd6" +red: &red "#c94f6d" + +# Skin... +k9s: + bodys: + fgColor: *foreground + bgColor: *background + logoColor: *blue + prompt: + fgColor: *foreground + bgColor: *background + suggestColor: *orange + info: + fgColor: *magenta + sectionColor: *foreground + dialog: + fgColor: *foreground + bgColor: *background + buttonFgColor: *foreground + buttonBgColor: *magenta + buttonFocusFgColor: white + buttonFocusBgColor: *cyan + labelFgColor: *orange + fieldFgColor: *foreground + frame: + border: + fgColor: *selection + focusColor: *current_line + menu: + fgColor: *foreground + keyColor: *magenta + numKeyColor: *magenta + crumbs: + fgColor: *foreground + bgColor: *current_line + activeColor: *current_line + status: + newColor: *cyan + modifyColor: *blue + addColor: *green + errorColor: *red + highlightColor: *orange + killColor: *comment + completedColor: *comment + title: + fgColor: *foreground + bgColor: *current_line + highlightColor: *orange + counterColor: *blue + filterColor: *magenta + views: + charts: + bgColor: default + defaultDialColors: + - *blue + - *red + defaultChartColors: + - *blue + - *red + table: + fgColor: *foreground + bgColor: *background + cursorFgColor: *selection + cursorBgColor: *current_line + header: + fgColor: *foreground + bgColor: *background + sorterColor: *cyan + xray: + fgColor: *foreground + bgColor: *background + cursorColor: *current_line + graphicColor: *blue + showIcons: false + yaml: + keyColor: *magenta + colonColor: *blue + valueColor: *foreground + logs: + fgColor: *foreground + bgColor: *background + indicator: + fgColor: *foreground + bgColor: *selection + toggleOnColor: *magenta + toggleOffColor: *blue diff --git a/internal/config/json/testdata/views/cool.yaml b/internal/config/json/testdata/views/cool.yaml new file mode 100644 index 0000000000..ce89ceb8ba --- /dev/null +++ b/internal/config/json/testdata/views/cool.yaml @@ -0,0 +1,12 @@ +views: + v1/nodes: + columns: + - NAME + - IP + v1/endpoints: + sortColumn: AGE:asc + columns: + - NAME + - NAMESPACE + - ENDPOINTS + - AGE diff --git a/internal/config/json/testdata/views/toast.yaml b/internal/config/json/testdata/views/toast.yaml new file mode 100644 index 0000000000..42ec1ae470 --- /dev/null +++ b/internal/config/json/testdata/views/toast.yaml @@ -0,0 +1,9 @@ +views: + v1/nodes: + v1/endpoints: + sortCol: AGE:asc + cols: + - NAME + - NAMESPACE + - ENDPOINTS + - AGE diff --git a/internal/config/json/validator.go b/internal/config/json/validator.go new file mode 100644 index 0000000000..e9bb02119c --- /dev/null +++ b/internal/config/json/validator.go @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package json + +import ( + "cmp" + _ "embed" + "errors" + "fmt" + "slices" + + "github.com/rs/zerolog/log" + "github.com/xeipuuv/gojsonschema" + "gopkg.in/yaml.v3" +) + +const ( + // PluginsSchema describes plugins schema. + PluginsSchema = "plugins.json" + + // AliasesSchema describes aliases schema. + AliasesSchema = "aliases.json" + + // ViewsSchema describes views schema. + ViewsSchema = "views.json" + + // HotkeysSchema describes hotkeys schema. + HotkeysSchema = "hotkeys.json" + + // K9sSchema describes k9s config schema. + K9sSchema = "k9s.json" + + // ContextSchema describes context config schema. + ContextSchema = "context.json" + + // SkinSchema describes skin config schema. + SkinSchema = "skin.json" +) + +var ( + //go:embed schemas/plugins.json + pluginSchema string + + //go:embed schemas/aliases.json + aliasSchema string + + //go:embed schemas/views.json + viewsSchema string + + //go:embed schemas/k9s.json + k9sSchema string + + //go:embed schemas/context.json + contextSchema string + + //go:embed schemas/hotkeys.json + hotkeysSchema string + + //go:embed schemas/skin.json + skinSchema string +) + +// Validator tracks schemas validation. +type Validator struct { + schemas map[string]gojsonschema.JSONLoader + loader *gojsonschema.SchemaLoader +} + +// NewValidator returns a new instance. +func NewValidator() *Validator { + v := Validator{ + schemas: map[string]gojsonschema.JSONLoader{ + K9sSchema: gojsonschema.NewStringLoader(k9sSchema), + ContextSchema: gojsonschema.NewStringLoader(contextSchema), + AliasesSchema: gojsonschema.NewStringLoader(aliasSchema), + ViewsSchema: gojsonschema.NewStringLoader(viewsSchema), + PluginsSchema: gojsonschema.NewStringLoader(pluginSchema), + HotkeysSchema: gojsonschema.NewStringLoader(hotkeysSchema), + SkinSchema: gojsonschema.NewStringLoader(skinSchema), + }, + } + v.register() + + return &v +} + +// Init initializes the schemas. +func (v *Validator) register() { + v.loader = gojsonschema.NewSchemaLoader() + v.loader.Validate = true + for k, s := range v.schemas { + if err := v.loader.AddSchema(k, s); err != nil { + log.Error().Err(err).Msgf("schema initialization failed: %q", k) + } + } +} + +// Validate runs document thru given schema validation. +func (v *Validator) Validate(k string, bb []byte) error { + var m interface{} + err := yaml.Unmarshal(bb, &m) + if err != nil { + return err + } + + s, ok := v.schemas[k] + if !ok { + return fmt.Errorf("no schema found for: %q", k) + } + result, err := gojsonschema.Validate(s, gojsonschema.NewGoLoader(m)) + if err != nil { + return err + } + if result.Valid() { + return nil + } + + slices.SortFunc(result.Errors(), func(a, b gojsonschema.ResultError) int { + return cmp.Compare(a.Description(), b.Description()) + }) + var errs error + for _, re := range result.Errors() { + errs = errors.Join(errs, errors.New(re.Description())) + } + + return errs +} + +func (v *Validator) ValidateObj(k string, o any) error { + s, ok := v.schemas[k] + if !ok { + return fmt.Errorf("no schema found for: %q", k) + } + result, err := gojsonschema.Validate(s, gojsonschema.NewGoLoader(o)) + if err != nil { + return err + } + if result.Valid() { + return nil + } + + slices.SortFunc(result.Errors(), func(a, b gojsonschema.ResultError) int { + return cmp.Compare(a.Description(), b.Description()) + }) + var errs error + for _, re := range result.Errors() { + errs = errors.Join(errs, errors.New(re.Description())) + } + + return errs +} diff --git a/internal/config/json/validator_test.go b/internal/config/json/validator_test.go new file mode 100644 index 0000000000..9bab5e0776 --- /dev/null +++ b/internal/config/json/validator_test.go @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package json_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/derailed/k9s/internal/config/json" + "github.com/stretchr/testify/assert" +) + +func TestValidatePluginDir(t *testing.T) { + skinDir := "../../../plugins" + ee, err := os.ReadDir(skinDir) + assert.NoError(t, err) + p := json.NewValidator() + for _, e := range ee { + if e.IsDir() { + continue + } + ext := filepath.Ext(e.Name()) + if ext == ".md" { + continue + } + assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name())) + assert.True(t, !strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name())) + bb, err := os.ReadFile(filepath.Join(skinDir, e.Name())) + assert.NoError(t, err) + assert.NoError(t, p.Validate(json.PluginsSchema, bb), e.Name()) + } +} + +func TestValidateSkinDir(t *testing.T) { + skinDir := "../../../skins" + ee, err := os.ReadDir(skinDir) + assert.NoError(t, err) + p := json.NewValidator() + for _, e := range ee { + if e.IsDir() { + continue + } + ext := filepath.Ext(e.Name()) + assert.True(t, ext == ".yaml", fmt.Sprintf("expected yaml file: %q", e.Name())) + assert.True(t, !strings.Contains(e.Name(), "_"), fmt.Sprintf("underscore in: %q", e.Name())) + bb, err := os.ReadFile(filepath.Join(skinDir, e.Name())) + assert.NoError(t, err) + assert.NoError(t, p.Validate(json.SkinSchema, bb), e.Name()) + } +} + +func TestValidateSkin(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/skins/cool.yaml", + }, + "toast": { + f: "testdata/skins/toast.yaml", + err: `Additional property bodys is not allowed`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.SkinSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestValidateK9s(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/k9s/cool.yaml", + }, + "toast": { + f: "testdata/k9s/toast.yaml", + err: `Additional property shellPods is not allowed`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.K9sSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestValidateContext(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/context/cool.yaml", + }, + "toast": { + f: "testdata/context/toast.yaml", + err: `Additional property fred is not allowed +Additional property namespaces is not allowed`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.ContextSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestValidatePlugins(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/plugins/cool.yaml", + }, + "toast": { + f: "testdata/plugins/toast.yaml", + err: `Additional property shortCuts is not allowed +scopes is required +shortCut is required`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.PluginsSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestValidateAliases(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/aliases/cool.yaml", + }, + "toast": { + f: "testdata/aliases/toast.yaml", + err: `Additional property alias is not allowed +aliases is required`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.AliasesSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} + +func TestValidateViews(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "happy": { + f: "testdata/views/cool.yaml", + }, + "toast": { + f: "testdata/views/toast.yaml", + err: `Additional property cols is not allowed +Additional property sortCol is not allowed +Invalid type. Expected: object, given: null +columns is required`, + }, + } + + v := json.NewValidator() + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + bb, err := os.ReadFile(u.f) + assert.NoError(t, err) + if err := v.Validate(json.ViewsSchema, bb); err != nil { + assert.Equal(t, u.err, err.Error()) + } + }) + } +} diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 69e4ddd5a0..09cee75302 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -4,8 +4,9 @@ package config import ( - "errors" + "fmt" "path/filepath" + "sync" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" @@ -13,19 +14,19 @@ import ( // K9s tracks K9s configuration options. type K9s struct { - LiveViewAutoRefresh bool `yaml:"liveViewAutoRefresh"` - ScreenDumpDir string `yaml:"screenDumpDir,omitempty"` - RefreshRate int `yaml:"refreshRate"` - MaxConnRetry int `yaml:"maxConnRetry"` - ReadOnly bool `yaml:"readOnly"` - NoExitOnCtrlC bool `yaml:"noExitOnCtrlC"` - UI UI `yaml:"ui"` - SkipLatestRevCheck bool `yaml:"skipLatestRevCheck"` - DisablePodCounting bool `yaml:"disablePodCounting"` - ShellPod *ShellPod `yaml:"shellPod"` - ImageScans *ImageScans `yaml:"imageScans"` - Logger *Logger `yaml:"logger"` - Thresholds Threshold `yaml:"thresholds"` + LiveViewAutoRefresh bool `json:"liveViewAutoRefresh" yaml:"liveViewAutoRefresh"` + ScreenDumpDir string `json:"screenDumpDir" yaml:"screenDumpDir,omitempty"` + RefreshRate int `json:"refreshRate" yaml:"refreshRate"` + MaxConnRetry int `json:"maxConnRetry" yaml:"maxConnRetry"` + ReadOnly bool `json:"readOnly" yaml:"readOnly"` + NoExitOnCtrlC bool `json:"noExitOnCtrlC" yaml:"noExitOnCtrlC"` + UI UI `json:"ui" yaml:"ui"` + SkipLatestRevCheck bool `json:"skipLatestRevCheck" yaml:"skipLatestRevCheck"` + DisablePodCounting bool `json:"disablePodCounting" yaml:"disablePodCounting"` + ShellPod ShellPod `json:"shellPod" yaml:"shellPod"` + ImageScans ImageScans `json:"imageScans" yaml:"imageScans"` + Logger Logger `json:"logger" yaml:"logger"` + Thresholds Threshold `json:"thresholds" yaml:"thresholds"` manualRefreshRate int manualHeadless *bool manualLogoless *bool @@ -38,6 +39,7 @@ type K9s struct { activeConfig *data.Config conn client.Connection ks data.KubeSettings + mx sync.RWMutex } // NewK9s create a new K9s configuration. @@ -57,25 +59,32 @@ func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s { } func (k *K9s) resetConnection(conn client.Connection) { + k.mx.Lock() + defer k.mx.Unlock() + k.conn = conn } // Save saves the k9s config to dis. func (k *K9s) Save() error { - if k.activeConfig != nil { - path := filepath.Join( - AppContextsDir, - data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName), - data.MainConfigFile, - ) - return k.activeConfig.Save(path) + if k.activeConfig == nil { + return fmt.Errorf("save failed. no active config detected") } + path := filepath.Join( + AppContextsDir, + data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName), + data.MainConfigFile, + ) - return nil + return k.activeConfig.Save(path) } -// Refine merges k9s configs. -func (k *K9s) Refine(k1 *K9s) { +// Merge merges k9s configs. +func (k *K9s) Merge(k1 *K9s) { + if k1 == nil { + return + } + k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh k.ScreenDumpDir = k1.ScreenDumpDir k.RefreshRate = k1.RefreshRate @@ -86,56 +95,31 @@ func (k *K9s) Refine(k1 *K9s) { k.SkipLatestRevCheck = k1.SkipLatestRevCheck k.DisablePodCounting = k1.DisablePodCounting k.ShellPod = k1.ShellPod - k.ImageScans = k1.ImageScans k.Logger = k1.Logger + k.ImageScans = k1.ImageScans k.Thresholds = k1.Thresholds } -// Override overrides k9s config from cli args. -func (k *K9s) Override(k9sFlags *Flags) { - if *k9sFlags.RefreshRate != DefaultRefreshRate { - k.OverrideRefreshRate(*k9sFlags.RefreshRate) - } - - k.OverrideHeadless(*k9sFlags.Headless) - k.OverrideLogoless(*k9sFlags.Logoless) - k.OverrideCrumbsless(*k9sFlags.Crumbsless) - k.OverrideReadOnly(*k9sFlags.ReadOnly) - k.OverrideWrite(*k9sFlags.Write) - k.OverrideCommand(*k9sFlags.Command) - k.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir) -} - -// OverrideScreenDumpDir set the screen dump dir manually. -func (k *K9s) OverrideScreenDumpDir(dir string) { - k.manualScreenDumpDir = &dir -} - -// GetScreenDumpDir fetch screen dumps dir. -func (k *K9s) GetScreenDumpDir() string { - screenDumpDir := k.ScreenDumpDir - if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" { - screenDumpDir = *k.manualScreenDumpDir +// AppScreenDumpDir fetch screen dumps dir. +func (k *K9s) AppScreenDumpDir() string { + d := k.ScreenDumpDir + if isStringSet(k.manualScreenDumpDir) { + d = *k.manualScreenDumpDir + k.ScreenDumpDir = d } - if screenDumpDir == "" { - screenDumpDir = AppDumpsDir + if d == "" { + d = AppDumpsDir } - return screenDumpDir + return d } -// Reset resets configuration and context. -func (k *K9s) Reset() { - k.activeConfig, k.activeContextName = nil, "" -} - -// ActiveScreenDumpsDir fetch context specific screen dumps dir. -func (k *K9s) ActiveScreenDumpsDir() string { - return filepath.Join(k.GetScreenDumpDir(), k.ActiveContextDir()) +// ContextScreenDumpDir fetch context specific screen dumps dir. +func (k *K9s) ContextScreenDumpDir() string { + return filepath.Join(k.AppScreenDumpDir(), k.contextPath()) } -// ActiveContextDir fetch current cluster/context path. -func (k *K9s) ActiveContextDir() string { +func (k *K9s) contextPath() string { if k.activeConfig == nil { return "na" } @@ -146,33 +130,39 @@ func (k *K9s) ActiveContextDir() string { ) } +// Reset resets configuration and context. +func (k *K9s) Reset() { + k.activeConfig, k.activeContextName = nil, "" +} + // ActiveContextNamespace fetch the context active ns. func (k *K9s) ActiveContextNamespace() (string, error) { - if k.activeConfig != nil { - return k.activeConfig.Context.Namespace.Active, nil + act, err := k.ActiveContext() + if err != nil { + return "", err } - return "", errors.New("context config is not set") + return act.Namespace.Active, nil } // ActiveContextName returns the active context name. func (k *K9s) ActiveContextName() string { + k.mx.RLock() + defer k.mx.RUnlock() + return k.activeContextName } // ActiveContext returns the currently active context. func (k *K9s) ActiveContext() (*data.Context, error) { - if k.activeConfig != nil { - if k.activeConfig.Context == nil { - ct, err := k.ks.CurrentContext() - if err != nil { - return nil, err - } - k.activeConfig.Context = data.NewContextFromConfig(ct) - } - return k.activeConfig.Context, nil - } + var ac *data.Config + k.mx.RLock() + ac = k.activeConfig + k.mx.RUnlock() + if ac != nil && ac.Context != nil { + return ac.Context, nil + } ct, err := k.ActivateContext(k.activeContextName) if err != nil { return nil, err @@ -181,7 +171,7 @@ func (k *K9s) ActiveContext() (*data.Context, error) { return ct, nil } -// ActivateContext initializes the active context is not present. +// ActivateContext initializes the active context if not present. func (k *K9s) ActivateContext(n string) (*data.Context, error) { k.activeContextName = n ct, err := k.ks.GetContext(n) @@ -192,161 +182,128 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { if err != nil { return nil, err } - // If the context specifies a default namespace, use it! - if k.conn != nil { - k.Validate(k.conn, k.ks) - if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace { - k.activeConfig.Context.Namespace.Active = ns - } else { - k.activeConfig.Context.Namespace.Active = client.DefaultNamespace - } + + k.Validate(k.conn, k.ks) + // If the context specifies a namespace, use it! + if ns := ct.Namespace; ns != client.BlankNamespace { + k.activeConfig.Context.Namespace.Active = ns + } else { + k.activeConfig.Context.Namespace.Active = client.DefaultNamespace + } + if k.activeConfig.Context == nil { + return nil, fmt.Errorf("context activation failed for: %s", n) } return k.activeConfig.Context, nil } -// Reload reloads the active config from disk. +// Reload reloads the context config from disk. func (k *K9s) Reload() error { + k.mx.Lock() + defer k.mx.Unlock() + ct, err := k.ks.GetContext(k.activeContextName) if err != nil { return err } - k.activeConfig, err = k.dir.Load(k.activeContextName, ct) if err != nil { return err } + k.activeConfig.Validate(k.conn, k.ks) return nil } -// OverrideRefreshRate set the refresh rate manually. -func (k *K9s) OverrideRefreshRate(r int) { - k.manualRefreshRate = r -} - -// OverrideHeadless toggle the header manually. -func (k *K9s) OverrideHeadless(b bool) { - k.manualHeadless = &b -} - -// OverrideLogoless toggle the k9s logo manually. -func (k *K9s) OverrideLogoless(b bool) { - k.manualLogoless = &b -} - -// OverrideCrumbsless tooh the crumbslessness manually. -func (k *K9s) OverrideCrumbsless(b bool) { - k.manualCrumbsless = &b -} - -// OverrideReadOnly set the readonly mode manually. -func (k *K9s) OverrideReadOnly(b bool) { - if b { - k.manualReadOnly = &b +// Override overrides k9s config from cli args. +func (k *K9s) Override(k9sFlags *Flags) { + if k9sFlags.RefreshRate != nil && *k9sFlags.RefreshRate != DefaultRefreshRate { + k.manualRefreshRate = *k9sFlags.RefreshRate } -} -// OverrideWrite set the write mode manually. -func (k *K9s) OverrideWrite(b bool) { - if b { - var flag bool - k.manualReadOnly = &flag + k.manualHeadless = k9sFlags.Headless + k.manualLogoless = k9sFlags.Logoless + k.manualCrumbsless = k9sFlags.Crumbsless + if k9sFlags.ReadOnly != nil && *k9sFlags.ReadOnly { + k.manualReadOnly = k9sFlags.ReadOnly } -} - -// OverrideCommand set the command manually. -func (k *K9s) OverrideCommand(cmd string) { - k.manualCommand = &cmd + if k9sFlags.Write != nil && *k9sFlags.Write { + var false bool + k.manualReadOnly = &false + } + k.manualCommand = k9sFlags.Command + k.manualScreenDumpDir = k9sFlags.ScreenDumpDir } // IsHeadless returns headless setting. func (k *K9s) IsHeadless() bool { - h := k.UI.Headless - if k.manualHeadless != nil && *k.manualHeadless { - h = *k.manualHeadless + if isBoolSet(k.manualHeadless) { + return true } - return h + return k.UI.Headless } // IsLogoless returns logoless setting. func (k *K9s) IsLogoless() bool { - h := k.UI.Logoless - if k.manualLogoless != nil && *k.manualLogoless { - h = *k.manualLogoless + if isBoolSet(k.manualLogoless) { + return true } - return h + return k.UI.Logoless } // IsCrumbsless returns crumbsless setting. func (k *K9s) IsCrumbsless() bool { - h := k.UI.Crumbsless - if k.manualCrumbsless != nil && *k.manualCrumbsless { - h = *k.manualCrumbsless + if isBoolSet(k.manualCrumbsless) { + return true } - return h + return k.UI.Crumbsless } // GetRefreshRate returns the current refresh rate. func (k *K9s) GetRefreshRate() int { - rate := k.RefreshRate if k.manualRefreshRate != 0 { - rate = k.manualRefreshRate + return k.manualRefreshRate } - return rate + return k.RefreshRate } // IsReadOnly returns the readonly setting. func (k *K9s) IsReadOnly() bool { - readOnly := k.ReadOnly - if k.manualReadOnly != nil { - readOnly = *k.manualReadOnly + k.mx.RLock() + defer k.mx.RUnlock() + + ro := k.ReadOnly + if k.activeConfig != nil && k.activeConfig.Context.ReadOnly != nil { + ro = *k.activeConfig.Context.ReadOnly } - if k.activeConfig != nil && k.activeConfig.Context.ReadOnly { - readOnly = true + if k.manualReadOnly != nil { + ro = true } - return readOnly + return ro } -func (k *K9s) validateDefaults() { +// Validate the current configuration. +func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { if k.RefreshRate <= 0 { k.RefreshRate = defaultRefreshRate } if k.MaxConnRetry <= 0 { k.MaxConnRetry = defaultMaxConnRetry } -} -// Validate the current configuration. -func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { - k.validateDefaults() if k.activeConfig == nil { if n, err := ks.CurrentContextName(); err == nil { _, _ = k.ActivateContext(n) } } - if k.ImageScans == nil { - k.ImageScans = NewImageScans() - } - if k.ShellPod == nil { - k.ShellPod = NewShellPod() - } - k.ShellPod.Validate() - - if k.Logger == nil { - k.Logger = NewLogger() - } else { - k.Logger.Validate() - } - if k.Thresholds == nil { - k.Thresholds = NewThreshold() - } - k.Thresholds.Validate() + k.ShellPod = k.ShellPod.Validate() + k.Logger = k.Logger.Validate() + k.Thresholds = k.Thresholds.Validate() if k.activeConfig != nil { k.activeConfig.Validate(c, ks) diff --git a/internal/config/k9s_int_test.go b/internal/config/k9s_int_test.go new file mode 100644 index 0000000000..0bee7fcce3 --- /dev/null +++ b/internal/config/k9s_int_test.go @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_k9sOverrides(t *testing.T) { + var ( + true = true + cmd = "po" + dir = "/tmp/blee" + ) + + uu := map[string]struct { + k *K9s + rate int + ro, hl, cl, ll bool + }{ + "plain": { + k: &K9s{ + LiveViewAutoRefresh: false, + ScreenDumpDir: "", + RefreshRate: 10, + MaxConnRetry: 0, + ReadOnly: false, + NoExitOnCtrlC: false, + UI: UI{}, + SkipLatestRevCheck: false, + DisablePodCounting: false, + }, + rate: 10, + }, + "set": { + k: &K9s{ + LiveViewAutoRefresh: false, + ScreenDumpDir: "", + RefreshRate: 10, + MaxConnRetry: 0, + ReadOnly: true, + NoExitOnCtrlC: false, + UI: UI{ + Headless: true, + Logoless: true, + Crumbsless: true, + }, + SkipLatestRevCheck: false, + DisablePodCounting: false, + }, + rate: 10, + ro: true, + hl: true, + ll: true, + cl: true, + }, + "overrides": { + k: &K9s{ + LiveViewAutoRefresh: false, + ScreenDumpDir: "", + RefreshRate: 10, + MaxConnRetry: 0, + ReadOnly: false, + NoExitOnCtrlC: false, + UI: UI{ + Headless: false, + Logoless: false, + Crumbsless: false, + }, + SkipLatestRevCheck: false, + DisablePodCounting: false, + manualRefreshRate: 100, + manualReadOnly: &true, + manualHeadless: &true, + manualLogoless: &true, + manualCrumbsless: &true, + manualCommand: &cmd, + manualScreenDumpDir: &dir, + }, + rate: 100, + ro: true, + hl: true, + ll: true, + cl: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.rate, u.k.GetRefreshRate()) + assert.Equal(t, u.ro, u.k.IsReadOnly()) + assert.Equal(t, u.cl, u.k.IsCrumbsless()) + assert.Equal(t, u.hl, u.k.IsHeadless()) + assert.Equal(t, u.ll, u.k.IsLogoless()) + + }) + } +} + +func Test_screenDumpDirOverride(t *testing.T) { + uu := map[string]struct { + dir string + e string + }{ + "empty": { + e: "/tmp/k9s-test/screen-dumps", + }, + "override": { + dir: "/tmp/k9s-test/sd", + e: "/tmp/k9s-test/sd", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + cfg := NewConfig(nil) + assert.NoError(t, cfg.Load("testdata/configs/k9s.yaml")) + + cfg.K9s.manualScreenDumpDir = &u.dir + assert.Equal(t, u.e, cfg.K9s.AppScreenDumpDir()) + }) + } +} diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index 67c1c461b3..69e76189c8 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -4,40 +4,145 @@ package config_test import ( + "errors" "testing" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/mock" "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" ) -func TestGetScreenDumpDir(t *testing.T) { - cfg := mock.NewMockConfig() +func TestK9sReload(t *testing.T) { + config.AppConfigDir = "/tmp/k9s-test" + + cl, ct := "cl-1", "ct-1-1" + + uu := map[string]struct { + k *config.K9s + cl, ct string + err error + }{ + "no-context": { + k: config.NewK9s( + mock.NewMockConnection(), + mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }), + ), + err: errors.New(`no context found for: ""`), + }, + "set-context": { + k: config.NewK9s( + mock.NewMockConnection(), + mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }), + ), + ct: "ct-1-1", + cl: "cl-1", + }, + } - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) - assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir()) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + _, _ = u.k.ActivateContext(u.ct) + assert.Equal(t, u.err, u.k.Reload()) + ct, err := u.k.ActiveContext() + assert.Equal(t, u.err, err) + if err == nil { + assert.Equal(t, u.cl, ct.ClusterName) + } + }) + } } -func TestGetScreenDumpDirOverride(t *testing.T) { - cfg := mock.NewMockConfig() +func TestK9sMerge(t *testing.T) { + cl, ct := "cl-1", "ct-1-1" + + uu := map[string]struct { + k1, k2 *config.K9s + ek *config.K9s + }{ + "no-opt": { + k1: config.NewK9s( + mock.NewMockConnection(), + mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }), + ), + ek: config.NewK9s( + mock.NewMockConnection(), + mock.NewMockKubeSettings(&genericclioptions.ConfigFlags{ + ClusterName: &cl, + Context: &ct, + }), + ), + }, + "override": { + k1: &config.K9s{ + LiveViewAutoRefresh: false, + ScreenDumpDir: "", + RefreshRate: 0, + MaxConnRetry: 0, + ReadOnly: false, + NoExitOnCtrlC: false, + UI: config.UI{}, + SkipLatestRevCheck: false, + DisablePodCounting: false, + ShellPod: config.ShellPod{}, + ImageScans: config.ImageScans{}, + Logger: config.Logger{}, + Thresholds: nil, + }, + k2: &config.K9s{ + LiveViewAutoRefresh: true, + MaxConnRetry: 100, + ShellPod: config.NewShellPod(), + }, + ek: &config.K9s{ + LiveViewAutoRefresh: true, + ScreenDumpDir: "", + RefreshRate: 0, + MaxConnRetry: 100, + ReadOnly: false, + NoExitOnCtrlC: false, + UI: config.UI{}, + SkipLatestRevCheck: false, + DisablePodCounting: false, + ShellPod: config.NewShellPod(), + ImageScans: config.ImageScans{}, + Logger: config.Logger{}, + Thresholds: nil, + }, + }, + } - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) - cfg.K9s.OverrideScreenDumpDir("/override") - assert.Equal(t, "/override", cfg.K9s.GetScreenDumpDir()) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.k1.Merge(u.k2) + assert.Equal(t, u.ek, u.k1) + }) + } } -func TestGetScreenDumpDirOverrideEmpty(t *testing.T) { +func TestContextScreenDumpDir(t *testing.T) { cfg := mock.NewMockConfig() + _, err := cfg.K9s.ActivateContext("ct-1-1") - assert.Nil(t, cfg.Load("testdata/k9s.yaml")) - cfg.K9s.OverrideScreenDumpDir("") - assert.Equal(t, "/tmp", cfg.K9s.GetScreenDumpDir()) + assert.NoError(t, err) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Equal(t, "/tmp/k9s-test/screen-dumps/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir()) } -func TestGetScreenDumpDirEmpty(t *testing.T) { +func TestAppScreenDumpDir(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/k9s1.yaml")) - cfg.K9s.OverrideScreenDumpDir("") - assert.Equal(t, config.AppDumpsDir, cfg.K9s.GetScreenDumpDir()) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Equal(t, "/tmp/k9s-test/screen-dumps", cfg.K9s.AppScreenDumpDir()) } diff --git a/internal/config/logger.go b/internal/config/logger.go index 014d724c16..88c7653dd2 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -16,17 +16,17 @@ const ( // Logger tracks logger options. type Logger struct { - TailCount int64 `yaml:"tail"` - BufferSize int `yaml:"buffer"` - SinceSeconds int64 `yaml:"sinceSeconds"` - FullScreenLogs bool `yaml:"fullScreenLogs"` - TextWrap bool `yaml:"textWrap"` - ShowTime bool `yaml:"showTime"` + TailCount int64 `json:"tail" yaml:"tail"` + BufferSize int `json:"buffer" yaml:"buffer"` + SinceSeconds int64 `json:"sinceSeconds" yaml:"sinceSeconds"` + FullScreen bool `json:"fullScreen" yaml:"fullScreen"` + TextWrap bool `json:"textWrap" yaml:"textWrap"` + ShowTime bool `json:"showTime" yaml:"showTime"` } // NewLogger returns a new instance. -func NewLogger() *Logger { - return &Logger{ +func NewLogger() Logger { + return Logger{ TailCount: DefaultLoggerTailCount, BufferSize: MaxLogThreshold, SinceSeconds: DefaultSinceSeconds, @@ -34,7 +34,7 @@ func NewLogger() *Logger { } // Validate checks thresholds and make sure we're cool. If not use defaults. -func (l *Logger) Validate() { +func (l Logger) Validate() Logger { if l.TailCount <= 0 { l.TailCount = DefaultLoggerTailCount } @@ -47,4 +47,6 @@ func (l *Logger) Validate() { if l.SinceSeconds == 0 { l.SinceSeconds = DefaultSinceSeconds } + + return l } diff --git a/internal/config/logger_test.go b/internal/config/logger_test.go index 51625df65e..753466f459 100644 --- a/internal/config/logger_test.go +++ b/internal/config/logger_test.go @@ -12,7 +12,7 @@ import ( func TestNewLogger(t *testing.T) { l := config.NewLogger() - l.Validate() + l = l.Validate() assert.Equal(t, int64(100), l.TailCount) assert.Equal(t, 5000, l.BufferSize) @@ -20,7 +20,7 @@ func TestNewLogger(t *testing.T) { func TestLoggerValidate(t *testing.T) { var l config.Logger - l.Validate() + l = l.Validate() assert.Equal(t, int64(100), l.TailCount) assert.Equal(t, 5000, l.BufferSize) diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index 49db4a24c6..a20db334c5 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -33,7 +33,7 @@ func EnsureDir(d string) error { func NewMockConfig() *config.Config { config.AppContextsDir = "/tmp/test" - cl, ct := "cl-1", "ct-1" + cl, ct := "cl-1", "ct-1-1" flags := genericclioptions.ConfigFlags{ ClusterName: &cl, Context: &ct, @@ -63,7 +63,7 @@ func NewMockKubeSettings(f *genericclioptions.ConfigFlags) mockKubeSettings { }, ctId + "-2": { Cluster: *f.ClusterName, - Namespace: "ns-1", + Namespace: "ns-2", }, ctId + "-3": { Cluster: *f.ClusterName, diff --git a/internal/config/plugin.go b/internal/config/plugin.go index b947f8663e..7b111294ad 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -10,6 +10,9 @@ import ( "path/filepath" "strings" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" + "github.com/adrg/xdg" "gopkg.in/yaml.v2" ) @@ -47,6 +50,7 @@ func NewPlugins() Plugins { // Load K9s plugins. func (p Plugins) Load(path string) error { var errs error + if err := p.load(AppPluginsFile); err != nil { errs = errors.Join(errs, err) } @@ -74,12 +78,12 @@ func (p Plugins) loadPluginDir(dir string) error { if file.IsDir() || !isYamlFile(file.Name()) { continue } - pluginFile, err := os.ReadFile(filepath.Join(dir, file.Name())) + bb, err := os.ReadFile(filepath.Join(dir, file.Name())) if err != nil { errs = errors.Join(errs, err) } var plugin Plugin - if err = yaml.Unmarshal(pluginFile, &plugin); err != nil { + if err = yaml.Unmarshal(bb, &plugin); err != nil { return err } p.Plugins[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin @@ -92,13 +96,15 @@ func (p *Plugins) load(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { return nil } - f, err := os.ReadFile(path) + bb, err := os.ReadFile(path) if err != nil { return err } - + if err := data.JSONValidator.Validate(json.PluginsSchema, bb); err != nil { + return fmt.Errorf("validation failed for %q: %w", path, err) + } var pp Plugins - if err := yaml.Unmarshal(f, &pp); err != nil { + if err := yaml.Unmarshal(bb, &pp); err != nil { return err } for k, v := range pp.Plugins { diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go index f0870e0142..bd95fec5ec 100644 --- a/internal/config/plugin_test.go +++ b/internal/config/plugin_test.go @@ -4,8 +4,10 @@ package config import ( + "os" "testing" + "github.com/adrg/xdg" "github.com/stretchr/testify/assert" ) @@ -39,10 +41,24 @@ var test2YmlTestData = Plugin{ Background: true, } +func TestPluginLoad(t *testing.T) { + AppPluginsFile = "/tmp/k9s-test/fred.yaml" + os.Setenv("XDG_DATA_HOME", "/tmp/k9s-test") + xdg.Reload() + + p := NewPlugins() + assert.NoError(t, p.Load("testdata/plugins.yaml")) + + assert.Equal(t, 1, len(p.Plugins)) + k, ok := p.Plugins["blah"] + assert.True(t, ok) + assert.ObjectsAreEqual(pluginYmlTestData, k) +} + func TestSinglePluginFileLoad(t *testing.T) { p := NewPlugins() - assert.Nil(t, p.load("testdata/plugins.yaml")) - assert.Nil(t, p.loadPluginDir("/random/dir/not/exist")) + assert.NoError(t, p.load("testdata/plugins.yaml")) + assert.NoError(t, p.loadPluginDir("/random/dir/not/exist")) assert.Equal(t, 1, len(p.Plugins)) k, ok := p.Plugins["blah"] @@ -53,8 +69,8 @@ func TestSinglePluginFileLoad(t *testing.T) { func TestMultiplePluginFilesLoad(t *testing.T) { p := NewPlugins() - assert.Nil(t, p.load("testdata/plugins.yaml")) - assert.Nil(t, p.loadPluginDir("testdata/plugins")) + assert.NoError(t, p.load("testdata/plugins.yaml")) + assert.NoError(t, p.loadPluginDir("testdata/plugins")) testPlugins := map[string]Plugin{ "blah": pluginYmlTestData, diff --git a/internal/config/scans.go b/internal/config/scans.go index 5731c58eca..31aa8ef788 100644 --- a/internal/config/scans.go +++ b/internal/config/scans.go @@ -23,8 +23,8 @@ func (l Labels) exclude(k, val string) bool { // ScanExcludes tracks vul scan exclusions. type ScanExcludes struct { - Namespaces []string `yaml:"namespaces"` - Labels Labels `yaml:"labels"` + Namespaces []string `json:"namespaces" yaml:"namespaces"` + Labels Labels `json:"labels" yaml:"labels"` } func newScanExcludes() ScanExcludes { @@ -50,19 +50,19 @@ func (b ScanExcludes) exclude(ns string, ll map[string]string) bool { // ImageScans tracks vul scans options. type ImageScans struct { - Enable bool `yaml:"enable"` - Exclusions ScanExcludes `yaml:"exclusions"` + Enable bool `json:"enable" yaml:"enable"` + Exclusions ScanExcludes `json:"exclusions" yaml:"exclusions"` } // NewImageScans returns a new instance. -func NewImageScans() *ImageScans { - return &ImageScans{ +func NewImageScans() ImageScans { + return ImageScans{ Exclusions: newScanExcludes(), } } // ShouldExclude checks if scan should be excluder given ns/labels -func (i *ImageScans) ShouldExclude(ns string, ll map[string]string) bool { +func (i ImageScans) ShouldExclude(ns string, ll map[string]string) bool { if !i.Enable { return false } diff --git a/internal/config/scans_test.go b/internal/config/scans_test.go new file mode 100644 index 0000000000..d410393c10 --- /dev/null +++ b/internal/config/scans_test.go @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestScansShouldExclude(t *testing.T) { + uu := map[string]struct { + sc config.ImageScans + ns string + ll map[string]string + e bool + }{ + "empty": { + sc: config.NewImageScans(), + }, + "exclude-ns": { + sc: config.ImageScans{ + Enable: true, + Exclusions: config.ScanExcludes{ + Namespaces: []string{"ns-1", "ns-2", "ns-3"}, + Labels: config.Labels{ + "app": []string{"fred", "blee"}, + }, + }, + }, + ns: "ns-1", + ll: map[string]string{ + "app": "freddy", + }, + e: true, + }, + "include-ns": { + sc: config.ImageScans{ + Enable: true, + Exclusions: config.ScanExcludes{ + Namespaces: []string{"ns-1", "ns-2", "ns-3"}, + Labels: config.Labels{ + "app": []string{"fred", "blee"}, + }, + }, + }, + ns: "ns-4", + ll: map[string]string{ + "app": "bozo", + }, + }, + "exclude-labels": { + sc: config.ImageScans{ + Enable: true, + Exclusions: config.ScanExcludes{ + Namespaces: []string{"ns-1", "ns-2", "ns-3"}, + Labels: config.Labels{ + "app": []string{"fred", "blee"}, + }, + }, + }, + ns: "ns-4", + ll: map[string]string{ + "app": "fred", + }, + e: true, + }, + "include-labels": { + sc: config.ImageScans{ + Enable: true, + Exclusions: config.ScanExcludes{ + Namespaces: []string{"ns-1", "ns-2", "ns-3"}, + Labels: config.Labels{ + "app": []string{"fred", "blee"}, + }, + }, + }, + ns: "ns-4", + ll: map[string]string{ + "app": "freddy", + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.sc.ShouldExclude(u.ns, u.ll)) + }) + } +} diff --git a/internal/config/shell_pod.go b/internal/config/shell_pod.go index c431bc1b0f..08540f2c94 100644 --- a/internal/config/shell_pod.go +++ b/internal/config/shell_pod.go @@ -14,20 +14,20 @@ type Limits map[v1.ResourceName]string // ShellPod represents k9s shell configuration. type ShellPod struct { - Image string `yaml:"image"` - Command []string `yaml:"command,omitempty"` - Args []string `yaml:"args,omitempty"` - Namespace string `yaml:"namespace"` - Limits Limits `yaml:"limits,omitempty"` - Labels map[string]string `yaml:"labels,omitempty"` - ImagePullSecrets []v1.LocalObjectReference `yaml:"imagePullSecrets,omitempty"` - ImagePullPolicy v1.PullPolicy `yaml:"imagePullPolicy,omitempty"` - TTY bool `yaml:"tty,omitempty"` + Image string `json:"image" yaml:"image"` + Command []string `json:"command,omitempty" yaml:"command,omitempty"` + Args []string `json:"args,omitempty" yaml:"args,omitempty"` + Namespace string `json:"namespace" yaml:"namespace"` + Limits Limits `json:"limits,omitempty" yaml:"limits,omitempty"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"` + ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"` + TTY bool `json:"tty,omitempty" yaml:"tty,omitempty"` } // NewShellPod returns a new instance. -func NewShellPod() *ShellPod { - return &ShellPod{ +func NewShellPod() ShellPod { + return ShellPod{ Image: defaultDockerShellImage, Namespace: "default", Limits: defaultLimits(), @@ -35,13 +35,15 @@ func NewShellPod() *ShellPod { } // Validate validates the configuration. -func (s *ShellPod) Validate() { +func (s ShellPod) Validate() ShellPod { if s.Image == "" { s.Image = defaultDockerShellImage } if len(s.Limits) == 0 { s.Limits = defaultLimits() } + + return s } func defaultLimits() Limits { diff --git a/internal/config/styles.go b/internal/config/styles.go index 06690d7d9a..604cfee938 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -4,8 +4,11 @@ package config import ( + "fmt" "os" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" "github.com/derailed/tcell/v2" "github.com/derailed/tview" "gopkg.in/yaml.v2" @@ -18,204 +21,198 @@ type StyleListener interface { } type ( - // Color represents a color. - Color string - - // Colors tracks multiple colors. - Colors []Color - // Styles tracks K9s styling options. Styles struct { - K9s Style `yaml:"k9s"` + K9s Style `json:"k9s" yaml:"k9s"` listeners []StyleListener } // Style tracks K9s styles. Style struct { - Body Body `yaml:"body"` - Prompt Prompt `yaml:"prompt"` - Help Help `yaml:"help"` - Frame Frame `yaml:"frame"` - Info Info `yaml:"info"` - Views Views `yaml:"views"` - Dialog Dialog `yaml:"dialog"` + Body Body `json:"body" yaml:"body"` + Prompt Prompt `json:"prompt" yaml:"prompt"` + Help Help `json:"help" yaml:"help"` + Frame Frame `json:"frame" yaml:"frame"` + Info Info `json:"info" yaml:"info"` + Views Views `json:"views" yaml:"views"` + Dialog Dialog `json:"dialog" yaml:"dialog"` } // Prompt tracks command styles Prompt struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - SuggestColor Color `yaml:"suggestColor"` - Border PromptBorder `yaml:"border"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + SuggestColor Color `json:"" yaml:"suggestColor"` + Border PromptBorder `json:"" yaml:"border"` } // PromptBorder tracks the color of the prompt depending on its kind (e.g., command or filter) PromptBorder struct { - CommandColor Color `yaml:"command"` - DefaultColor Color `yaml:"default"` + CommandColor Color `json:"command" yaml:"command"` + DefaultColor Color `json:"default" yaml:"default"` } // Help tracks help styles. Help struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - SectionColor Color `yaml:"sectionColor"` - KeyColor Color `yaml:"keyColor"` - NumKeyColor Color `yaml:"numKeyColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + SectionColor Color `json:"sectionColor" yaml:"sectionColor"` + KeyColor Color `json:"keyColor" yaml:"keyColor"` + NumKeyColor Color `json:"numKeyColor" yaml:"numKeyColor"` } // Body tracks body styles. Body struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - LogoColor Color `yaml:"logoColor"` - LogoColorMsg Color `yaml:"logoColorMsg"` - LogoColorInfo Color `yaml:"logoColorInfo"` - LogoColorWarn Color `yaml:"logoColorWarn"` - LogoColorError Color `yaml:"logoColorError"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + LogoColor Color `json:"logoColor" yaml:"logoColor"` + LogoColorMsg Color `json:"logoColorMsg" yaml:"logoColorMsg"` + LogoColorInfo Color `json:"logoColorInfo" yaml:"logoColorInfo"` + LogoColorWarn Color `json:"logoColorWarn" yaml:"logoColorWarn"` + LogoColorError Color `json:"logoColorError" yaml:"logoColorError"` } // Dialog tracks dialog styles. Dialog struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - ButtonFgColor Color `yaml:"buttonFgColor"` - ButtonBgColor Color `yaml:"buttonBgColor"` - ButtonFocusFgColor Color `yaml:"buttonFocusFgColor"` - ButtonFocusBgColor Color `yaml:"buttonFocusBgColor"` - LabelFgColor Color `yaml:"labelFgColor"` - FieldFgColor Color `yaml:"fieldFgColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + ButtonFgColor Color `json:"buttonFgColor" yaml:"buttonFgColor"` + ButtonBgColor Color `json:"buttonBgColor" yaml:"buttonBgColor"` + ButtonFocusFgColor Color `json:"buttonFocusFgColor" yaml:"buttonFocusFgColor"` + ButtonFocusBgColor Color `json:"buttonFocusBgColor" yaml:"buttonFocusBgColor"` + LabelFgColor Color `json:"labelFgColor" yaml:"labelFgColor"` + FieldFgColor Color `json:"fieldFgColor" yaml:"fieldFgColor"` } // Frame tracks frame styles. Frame struct { - Title Title `yaml:"title"` - Border Border `yaml:"border"` - Menu Menu `yaml:"menu"` - Crumb Crumb `yaml:"crumbs"` - Status Status `yaml:"status"` + Title Title `json:"title" yaml:"title"` + Border Border `json:"border" yaml:"border"` + Menu Menu `json:"menu" yaml:"menu"` + Crumb Crumb `json:"crumbs" yaml:"crumbs"` + Status Status `json:"status" yaml:"status"` } // Views tracks individual view styles. Views struct { - Table Table `yaml:"table"` - Xray Xray `yaml:"xray"` - Charts Charts `yaml:"charts"` - Yaml Yaml `yaml:"yaml"` - Picker Picker `yaml:"picker"` - Log Log `yaml:"logs"` + Table Table `json:"table" yaml:"table"` + Xray Xray `json:"xray" yaml:"xray"` + Charts Charts `json:"charts" yaml:"charts"` + Yaml Yaml `json:"yaml" yaml:"yaml"` + Picker Picker `json:"picker" yaml:"picker"` + Log Log `json:"logs" yaml:"logs"` } // Status tracks resource status styles. Status struct { - NewColor Color `yaml:"newColor"` - ModifyColor Color `yaml:"modifyColor"` - AddColor Color `yaml:"addColor"` - PendingColor Color `yaml:"pendingColor"` - ErrorColor Color `yaml:"errorColor"` - HighlightColor Color `yaml:"highlightColor"` - KillColor Color `yaml:"killColor"` - CompletedColor Color `yaml:"completedColor"` + NewColor Color `json:"newColor" yaml:"newColor"` + ModifyColor Color `json:"modifyColor" yaml:"modifyColor"` + AddColor Color `json:"addColor" yaml:"addColor"` + PendingColor Color `json:"pendingColor" yaml:"pendingColor"` + ErrorColor Color `json:"errorColor" yaml:"errorColor"` + HighlightColor Color `json:"highlightColor" yaml:"highlightColor"` + KillColor Color `json:"killColor" yaml:"killColor"` + CompletedColor Color `json:"completedColor" yaml:"completedColor"` } // Log tracks Log styles. Log struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - Indicator LogIndicator `yaml:"indicator"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + Indicator LogIndicator `json:"indicator" yaml:"indicator"` } // Picker tracks color when selecting containers Picker struct { - MainColor Color `yaml:"mainColor"` - FocusColor Color `yaml:"focusColor"` - ShortcutColor Color `yaml:"shortcutColor"` + MainColor Color `json:"mainColor" yaml:"mainColor"` + FocusColor Color `json:"focusColor" yaml:"focusColor"` + ShortcutColor Color `json:"shortcutColor" yaml:"shortcutColor"` } // LogIndicator tracks log view indicator. LogIndicator struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - ToggleOnColor Color `yaml:"toggleOnColor"` - ToggleOffColor Color `yaml:"toggleOffColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + ToggleOnColor Color `json:"toggleOnColor" yaml:"toggleOnColor"` + ToggleOffColor Color `json:"toggleOffColor" yaml:"toggleOffColor"` } // Yaml tracks yaml styles. Yaml struct { - KeyColor Color `yaml:"keyColor"` - ValueColor Color `yaml:"valueColor"` - ColonColor Color `yaml:"colonColor"` + KeyColor Color `json:"keyColor" yaml:"keyColor"` + ValueColor Color `json:"valueColor" yaml:"valueColor"` + ColonColor Color `json:"colonColor" yaml:"colonColor"` } // Title tracks title styles. Title struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - HighlightColor Color `yaml:"highlightColor"` - CounterColor Color `yaml:"counterColor"` - FilterColor Color `yaml:"filterColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + HighlightColor Color `json:"highlightColor" yaml:"highlightColor"` + CounterColor Color `json:"counterColor" yaml:"counterColor"` + FilterColor Color `json:"filterColor" yaml:"filterColor"` } // Info tracks info styles. Info struct { - SectionColor Color `yaml:"sectionColor"` - FgColor Color `yaml:"fgColor"` + SectionColor Color `json:"sectionColor" yaml:"sectionColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` } // Border tracks border styles. Border struct { - FgColor Color `yaml:"fgColor"` - FocusColor Color `yaml:"focusColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + FocusColor Color `json:"focusColor" yaml:"focusColor"` } // Crumb tracks crumbs styles. Crumb struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - ActiveColor Color `yaml:"activeColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + ActiveColor Color `json:"activeColor" yaml:"activeColor"` } // Table tracks table styles. Table struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - CursorFgColor Color `yaml:"cursorFgColor"` - CursorBgColor Color `yaml:"cursorBgColor"` - MarkColor Color `yaml:"markColor"` - Header TableHeader `yaml:"header"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + CursorFgColor Color `json:"cursorFgColor" yaml:"cursorFgColor"` + CursorBgColor Color `json:"cursorBgColor" yaml:"cursorBgColor"` + MarkColor Color `json:"markColor" yaml:"markColor"` + Header TableHeader `json:"header" yaml:"header"` } // TableHeader tracks table header styles. TableHeader struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - SorterColor Color `yaml:"sorterColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + SorterColor Color `json:"sorterColor" yaml:"sorterColor"` } // Xray tracks xray styles. Xray struct { - FgColor Color `yaml:"fgColor"` - BgColor Color `yaml:"bgColor"` - CursorColor Color `yaml:"cursorColor"` - CursorTextColor Color `yaml:"cursorTextColor"` - GraphicColor Color `yaml:"graphicColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + CursorColor Color `json:"cursorColor" yaml:"cursorColor"` + CursorTextColor Color `json:"cursorTextColor" yaml:"cursorTextColor"` + GraphicColor Color `json:"graphicColor" yaml:"graphicColor"` } // Menu tracks menu styles. Menu struct { - FgColor Color `yaml:"fgColor"` - KeyColor Color `yaml:"keyColor"` - NumKeyColor Color `yaml:"numKeyColor"` + FgColor Color `json:"fgColor" yaml:"fgColor"` + KeyColor Color `json:"keyColor" yaml:"keyColor"` + NumKeyColor Color `json:"numKeyColor" yaml:"numKeyColor"` } // Charts tracks charts styles. Charts struct { - BgColor Color `yaml:"bgColor"` - DialBgColor Color `yaml:"dialBgColor"` - ChartBgColor Color `yaml:"chartBgColor"` - DefaultDialColors Colors `yaml:"defaultDialColors"` - DefaultChartColors Colors `yaml:"defaultChartColors"` - ResourceColors map[string]Colors `yaml:"resourceColors"` + BgColor Color `json:"bgColor" yaml:"bgColor"` + DialBgColor Color `json:"dialBgColor" yaml:"dialBgColor"` + ChartBgColor Color `json:"chartBgColor" yaml:"chartBgColor"` + DefaultDialColors Colors `json:"defaultDialColors" yaml:"defaultDialColors"` + DefaultChartColors Colors `json:"defaultChartColors" yaml:"defaultChartColors"` + ResourceColors map[string]Colors `json:"resourceColors" yaml:"resourceColors"` } ) @@ -442,7 +439,9 @@ func NewStyles() *Styles { // Reset resets styles. func (s *Styles) Reset() { - s.K9s = newStyle() + if err := yaml.Unmarshal(stockSkinTpl, s); err != nil { + s.K9s = newStyle() + } } // FgColor returns the foreground color. @@ -533,11 +532,14 @@ func (s *Styles) Views() Views { // Load K9s configuration from file. func (s *Styles) Load(path string) error { - f, err := os.ReadFile(path) + bb, err := os.ReadFile(path) if err != nil { return err } - if err := yaml.Unmarshal(f, s); err != nil { + if err := data.JSONValidator.Validate(json.SkinSchema, bb); err != nil { + return err + } + if err := yaml.Unmarshal(bb, s); err != nil { return err } @@ -561,3 +563,9 @@ func (s *Styles) Update() { s.fireStylesChanged() } + +// Dump for debug. +func (s *Styles) Dump() { + bb, _ := yaml.Marshal(s) + fmt.Println(string(bb)) +} diff --git a/internal/config/styles_int_test.go b/internal/config/styles_int_test.go new file mode 100644 index 0000000000..1845b1f4ca --- /dev/null +++ b/internal/config/styles_int_test.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_newStyle(t *testing.T) { + s := newStyle() + + assert.Equal(t, Color("black"), s.Body.BgColor) + assert.Equal(t, Color("cadetblue"), s.Body.FgColor) + assert.Equal(t, Color("lightskyblue"), s.Frame.Status.NewColor) +} diff --git a/internal/config/styles_test.go b/internal/config/styles_test.go index 572581c164..b2ada27848 100644 --- a/internal/config/styles_test.go +++ b/internal/config/styles_test.go @@ -12,6 +12,14 @@ import ( "github.com/stretchr/testify/assert" ) +func TestNewStyle(t *testing.T) { + s := config.NewStyles() + + assert.Equal(t, config.Color("black"), s.K9s.Body.BgColor) + assert.Equal(t, config.Color("cadetblue"), s.K9s.Body.FgColor) + assert.Equal(t, config.Color("lightskyblue"), s.K9s.Frame.Status.NewColor) +} + func TestColor(t *testing.T) { uu := map[string]tcell.Color{ "blah": tcell.ColorDefault, @@ -28,22 +36,9 @@ func TestColor(t *testing.T) { } } -func TestSkinNone(t *testing.T) { +func TestSkinHappy(t *testing.T) { s := config.NewStyles() - assert.Nil(t, s.Load("testdata/empty_skin.yaml")) - s.Update() - - assert.Equal(t, "#5f9ea0", s.Body().FgColor.String()) - assert.Equal(t, "#000000", s.Body().BgColor.String()) - assert.Equal(t, "#000000", s.Table().BgColor.String()) - assert.Equal(t, tcell.ColorCadetBlue.TrueColor(), s.FgColor()) - assert.Equal(t, tcell.ColorBlack.TrueColor(), s.BgColor()) - assert.Equal(t, tcell.ColorBlack.TrueColor(), tview.Styles.PrimitiveBackgroundColor) -} - -func TestSkin(t *testing.T) { - s := config.NewStyles() - assert.Nil(t, s.Load("testdata/black_and_wtf.yaml")) + assert.Nil(t, s.Load("../../skins/black-and-wtf.yaml")) s.Update() assert.Equal(t, "#ffffff", s.Body().FgColor.String()) @@ -54,12 +49,38 @@ func TestSkin(t *testing.T) { assert.Equal(t, tcell.ColorBlack.TrueColor(), tview.Styles.PrimitiveBackgroundColor) } -func TestSkinNotExits(t *testing.T) { - s := config.NewStyles() - assert.NotNil(t, s.Load("testdata/blee.yaml")) -} +func TestSkinLoad(t *testing.T) { + uu := map[string]struct { + f string + err string + }{ + "not-exist": { + f: "testdata/skins/blee.yaml", + err: "open testdata/skins/blee.yaml: no such file or directory", + }, + "toast": { + f: "testdata/skins/boarked.yaml", + err: `Additional property bgColor is not allowed +Additional property fgColor is not allowed +Additional property logoColor is not allowed +Invalid type. Expected: object, given: array`, + }, + } -func TestSkinBoarked(t *testing.T) { - s := config.NewStyles() - assert.NotNil(t, s.Load("testdata/skin_boarked.yaml")) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + s := config.NewStyles() + err := s.Load(u.f) + if err != nil { + assert.Equal(t, u.err, err.Error()) + } + assert.Equal(t, "#5f9ea0", s.Body().FgColor.String()) + assert.Equal(t, "#000000", s.Body().BgColor.String()) + assert.Equal(t, "#000000", s.Table().BgColor.String()) + assert.Equal(t, tcell.ColorCadetBlue.TrueColor(), s.FgColor()) + assert.Equal(t, tcell.ColorBlack.TrueColor(), s.BgColor()) + assert.Equal(t, tcell.ColorBlack.TrueColor(), tview.Styles.PrimitiveBackgroundColor) + }) + } } diff --git a/internal/config/templates/aliases.yaml b/internal/config/templates/aliases.yaml index 81c781f970..ee4d9ec014 100644 --- a/internal/config/templates/aliases.yaml +++ b/internal/config/templates/aliases.yaml @@ -1,9 +1,9 @@ aliases: - dp: apps/v1/deployments + dp: deployments sec: v1/secrets - jo: batch/v1/jobs - cr: rbac.authorization.k8s.io/v1/clusterroles - crb: rbac.authorization.k8s.io/v1/clusterrolebindings - ro: rbac.authorization.k8s.io/v1/roles - rb: rbac.authorization.k8s.io/v1/rolebindings - np: networking.k8s.io/v1/networkpolicies + jo: jobs + cr: clusterroles + crb: clusterrolebindings + ro: roles + rb: rolebindings + np: networkpolicies diff --git a/internal/config/testdata/aliases/aliases.yaml b/internal/config/testdata/aliases/aliases.yaml new file mode 100644 index 0000000000..81c781f970 --- /dev/null +++ b/internal/config/testdata/aliases/aliases.yaml @@ -0,0 +1,9 @@ +aliases: + dp: apps/v1/deployments + sec: v1/secrets + jo: batch/v1/jobs + cr: rbac.authorization.k8s.io/v1/clusterroles + crb: rbac.authorization.k8s.io/v1/clusterrolebindings + ro: rbac.authorization.k8s.io/v1/roles + rb: rbac.authorization.k8s.io/v1/rolebindings + np: networking.k8s.io/v1/networkpolicies diff --git a/internal/config/testdata/alias.yaml b/internal/config/testdata/aliases/plain.yaml similarity index 100% rename from internal/config/testdata/alias.yaml rename to internal/config/testdata/aliases/plain.yaml diff --git a/internal/config/testdata/b_containers.yaml b/internal/config/testdata/benchmarks/b_containers.yaml similarity index 100% rename from internal/config/testdata/b_containers.yaml rename to internal/config/testdata/benchmarks/b_containers.yaml diff --git a/internal/config/testdata/b_containers_1.yaml b/internal/config/testdata/benchmarks/b_containers_1.yaml similarity index 100% rename from internal/config/testdata/b_containers_1.yaml rename to internal/config/testdata/benchmarks/b_containers_1.yaml diff --git a/internal/config/testdata/b_good.yaml b/internal/config/testdata/benchmarks/b_good.yaml similarity index 100% rename from internal/config/testdata/b_good.yaml rename to internal/config/testdata/benchmarks/b_good.yaml diff --git a/internal/config/testdata/b_toast.yaml b/internal/config/testdata/benchmarks/b_toast.yaml similarity index 100% rename from internal/config/testdata/b_toast.yaml rename to internal/config/testdata/benchmarks/b_toast.yaml diff --git a/internal/config/testdata/bench-fred.yaml b/internal/config/testdata/benchmarks/bench-fred.yaml similarity index 100% rename from internal/config/testdata/bench-fred.yaml rename to internal/config/testdata/benchmarks/bench-fred.yaml diff --git a/internal/config/testdata/configs/default.yaml b/internal/config/testdata/configs/default.yaml new file mode 100644 index 0000000000..3cd4634890 --- /dev/null +++ b/internal/config/testdata/configs/default.yaml @@ -0,0 +1,41 @@ +k9s: + liveViewAutoRefresh: false + screenDumpDir: /tmp/k9s-test/screen-dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + reactive: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 100 + buffer: 5000 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/testdata/configs/expected.yaml b/internal/config/testdata/configs/expected.yaml new file mode 100644 index 0000000000..0038921199 --- /dev/null +++ b/internal/config/testdata/configs/expected.yaml @@ -0,0 +1,41 @@ +k9s: + liveViewAutoRefresh: true + screenDumpDir: /tmp/k9s-test/screen-dumps + refreshRate: 100 + maxConnRetry: 5 + readOnly: true + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + reactive: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 500 + buffer: 800 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/testdata/configs/k9s.yaml b/internal/config/testdata/configs/k9s.yaml new file mode 100644 index 0000000000..050392b6af --- /dev/null +++ b/internal/config/testdata/configs/k9s.yaml @@ -0,0 +1,41 @@ +k9s: + liveViewAutoRefresh: true + screenDumpDir: /tmp/k9s-test/screen-dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + reactive: false + noIcons: false + skipLatestRevCheck: false + disablePodCounting: false + shellPod: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 200 + buffer: 2000 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/testdata/configs/k9s_toast.yaml b/internal/config/testdata/configs/k9s_toast.yaml new file mode 100644 index 0000000000..00e56a062c --- /dev/null +++ b/internal/config/testdata/configs/k9s_toast.yaml @@ -0,0 +1,40 @@ +k9s: + liveViewAutoRefresh: true + screenDumpDir: /tmp/screen-dumps + refreshRate: 2 + maxConnRetry: 5 + readOnly: false + noExitOnCtrlC: false + ui: + enableMouse: false + headless: false + logoless: false + crumbsless: false + noIcons: false + skipLatestRevCheck: yes + disablePodCounts: false + shellPods: + image: busybox:1.35.0 + namespace: default + limits: + cpu: 100m + memory: 100Mi + imageScans: + enable: false + exclusions: + namespaces: [] + labels: {} + logger: + tail: 200 + buffer: 2000 + sinceSeconds: -1 + fullScreen: false + textWrap: false + showTime: false + thresholds: + cpu: + critical: 90 + warn: 70 + memory: + critical: 90 + warn: 70 diff --git a/internal/config/testdata/empty_skin.yaml b/internal/config/testdata/empty_skin.yaml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/internal/config/testdata/hotkeys.yaml b/internal/config/testdata/hotkeys/hotkeys.yaml similarity index 100% rename from internal/config/testdata/hotkeys.yaml rename to internal/config/testdata/hotkeys/hotkeys.yaml diff --git a/internal/config/testdata/k9s.yaml b/internal/config/testdata/k9s.yaml deleted file mode 100644 index 8bb5df2e1e..0000000000 --- a/internal/config/testdata/k9s.yaml +++ /dev/null @@ -1,35 +0,0 @@ -k9s: - liveViewAutoRefresh: true - refreshRate: 2 - readOnly: false - logger: - tail: 200 - buffer: 2000 - currentContext: minikube - contexts: - minikube: - cluster: minikube - namespace: - active: kube-system - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: ctx - fred: - cluster: fred - namespace: - active: default - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: po - screenDumpDir: /tmp - disablePodCounting: false diff --git a/internal/config/testdata/k9s1.yaml b/internal/config/testdata/k9s1.yaml deleted file mode 100644 index 99bb975f65..0000000000 --- a/internal/config/testdata/k9s1.yaml +++ /dev/null @@ -1,8 +0,0 @@ -k9s: - refreshRate: 10 - namespace: - active: fred - favorites: - - blee - - duh - - crap \ No newline at end of file diff --git a/internal/config/testdata/k9s_old.yaml b/internal/config/testdata/k9s_old.yaml deleted file mode 100644 index dade14d9a3..0000000000 --- a/internal/config/testdata/k9s_old.yaml +++ /dev/null @@ -1,13 +0,0 @@ -k9s: - refreshRate: 2 - logBufferSize: 200 - namespace: - active: kube-system - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: ctx \ No newline at end of file diff --git a/internal/config/testdata/k9s_readonly.yaml b/internal/config/testdata/k9s_readonly.yaml deleted file mode 100644 index e3edca141e..0000000000 --- a/internal/config/testdata/k9s_readonly.yaml +++ /dev/null @@ -1,30 +0,0 @@ -k9s: - refreshRate: 2 - readOnly: true - logger: - tail: 200 - buffer: 2000 - currentContext: minikube - contexts: - minikube: - namespace: - active: kube-system - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: ctx - fred: - namespace: - active: default - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: po diff --git a/internal/config/testdata/k9s_toast.yaml b/internal/config/testdata/k9s_toast.yaml deleted file mode 100644 index ac84a5893f..0000000000 --- a/internal/config/testdata/k9s_toast.yaml +++ /dev/null @@ -1,27 +0,0 @@ -k9s: - refreshRate: 2 - logBufferSize: 200 - currentContext: minikube - contexts: - minikube: - namespace: - active: kube-system - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: ctx - fred: - namespace: - active: default - favorites: - - default - - kube-public - - istio-system - - all - - kube-system - view: - active: po \ No newline at end of file diff --git a/internal/config/testdata/kubeconfig-test.yaml b/internal/config/testdata/kubes/test.yaml similarity index 68% rename from internal/config/testdata/kubeconfig-test.yaml rename to internal/config/testdata/kubes/test.yaml index 725d04bc7d..759598b048 100644 --- a/internal/config/testdata/kubeconfig-test.yaml +++ b/internal/config/testdata/kubes/test.yaml @@ -5,18 +5,27 @@ clusters: certificate-authority: /Users/test/ca.crt server: https://1.2.3.4:8443 name: cl-1 + - cluster: + certificate-authority: /Users/test/ca.crt + server: https://5.6.7.8:8443 + name: cl-2 contexts: - context: cluster: cl-1 user: user1 namespace: ns-1 - name: ct-1 + name: ct-1-1 - context: cluster: cl-1 user: user2 namespace: ns-2 - name: ct-2 -current-context: ct-1 + name: ct-1-2 + - context: + cluster: cl-2 + user: user2 + namespace: ns-2 + name: ct-2-1 +current-context: ct-1-1 preferences: {} users: - name: user1 diff --git a/internal/config/testdata/black_and_wtf.yaml b/internal/config/testdata/skins/black-and-wtf.yaml similarity index 82% rename from internal/config/testdata/black_and_wtf.yaml rename to internal/config/testdata/skins/black-and-wtf.yaml index 5cef5c954f..2ad58452a0 100644 --- a/internal/config/testdata/black_and_wtf.yaml +++ b/internal/config/testdata/skins/black-and-wtf.yaml @@ -31,11 +31,12 @@ k9s: highlightColor: navajowhite counterColor: navajowhite filterColor: slategray - table: - fgColor: white - bgColor: black - cursorColor: white - header: - fgColor: darkgray + views: + table: + fgColor: white bgColor: black - sorterColor: white + cursorColor: white + header: + fgColor: darkgray + bgColor: black + sorterColor: white diff --git a/internal/config/testdata/skin_boarked.yaml b/internal/config/testdata/skins/boarked.yaml similarity index 100% rename from internal/config/testdata/skin_boarked.yaml rename to internal/config/testdata/skins/boarked.yaml diff --git a/internal/config/testdata/skins/empty.yaml b/internal/config/testdata/skins/empty.yaml new file mode 100644 index 0000000000..27de2f65c0 --- /dev/null +++ b/internal/config/testdata/skins/empty.yaml @@ -0,0 +1,2 @@ +k9s: + body: \ No newline at end of file diff --git a/internal/config/testdata/view_settings.yaml b/internal/config/testdata/view_settings.yaml deleted file mode 100644 index 3ea3050c6e..0000000000 --- a/internal/config/testdata/view_settings.yaml +++ /dev/null @@ -1,8 +0,0 @@ -k9s: - views: - v1/pods: - columns: - - NAMESPACE - - NAME - - AGE - - IP diff --git a/internal/config/testdata/views/views.yaml b/internal/config/testdata/views/views.yaml new file mode 100644 index 0000000000..b6debac30a --- /dev/null +++ b/internal/config/testdata/views/views.yaml @@ -0,0 +1,7 @@ +views: + v1/pods: + columns: + - NAMESPACE + - NAME + - AGE + - IP diff --git a/internal/config/threshold.go b/internal/config/threshold.go index e746775580..de01250a67 100644 --- a/internal/config/threshold.go +++ b/internal/config/threshold.go @@ -61,7 +61,7 @@ func NewThreshold() Threshold { } // Validate a namespace is setup correctly. -func (t Threshold) Validate() { +func (t Threshold) Validate() Threshold { for _, k := range []string{"cpu", "memory"} { v, ok := t[k] if !ok { @@ -70,6 +70,8 @@ func (t Threshold) Validate() { v.Validate() } } + + return t } // LevelFor returns a defcon level for the current state. diff --git a/internal/config/types.go b/internal/config/types.go index 9e8fea59b6..3176ea9423 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -11,21 +11,24 @@ const ( // UI tracks ui specific configs. type UI struct { // EnableMouse toggles mouse support. - EnableMouse bool `yaml:"enableMouse"` + EnableMouse bool `json:"enableMouse" yaml:"enableMouse"` // Headless toggles top header display. - Headless bool `yaml:"headless"` + Headless bool `json:"headless" yaml:"headless"` // LogoLess toggles k9s logo. - Logoless bool `yaml:"logoless"` + Logoless bool `json:"logoless" yaml:"logoless"` // Crumbsless toggles nav crumb display. - Crumbsless bool `yaml:"crumbsless"` + Crumbsless bool `json:"crumbsless" yaml:"crumbsless"` + + // Reactive toggles reactive ui changes. + Reactive bool `json:"reactive" yaml:"reactive"` // NoIcons toggles icons display. - NoIcons bool `yaml:"noIcons"` + NoIcons bool `json:"noIcons" yaml:"noIcons"` // Skin reference the general k9s skin name. // Can be overridden per context. - Skin string `yaml:"skin,omitempty"` + Skin string `json:"skin" yaml:"skin,omitempty"` } diff --git a/internal/config/views.go b/internal/config/views.go index ae2ad4798e..876156463e 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -4,8 +4,12 @@ package config import ( + "fmt" "os" + "github.com/derailed/k9s/internal/config/data" + "github.com/derailed/k9s/internal/config/json" + "gopkg.in/yaml.v2" ) @@ -21,51 +25,41 @@ type ViewSetting struct { SortColumn string `yaml:"sortColumn"` } -// ViewSettings represent a collection of view configurations. -type ViewSettings struct { - Views map[string]ViewSetting `yaml:"views"` -} - -// NewViewSettings returns a new configuration. -func NewViewSettings() ViewSettings { - return ViewSettings{ - Views: make(map[string]ViewSetting), - } -} - // CustomView represents a collection of view customization. type CustomView struct { - K9s ViewSettings `yaml:"k9s"` + Views map[string]ViewSetting `yaml:"views"` listeners map[string]ViewConfigListener } // NewCustomView returns a views configuration. func NewCustomView() *CustomView { return &CustomView{ - K9s: NewViewSettings(), + Views: make(map[string]ViewSetting), listeners: make(map[string]ViewConfigListener), } } // Reset clears out configurations. func (v *CustomView) Reset() { - for k := range v.K9s.Views { - delete(v.K9s.Views, k) + for k := range v.Views { + delete(v.Views, k) } } // Load loads view configurations. func (v *CustomView) Load(path string) error { - raw, err := os.ReadFile(path) + bb, err := os.ReadFile(path) if err != nil { return err } - + if err := data.JSONValidator.Validate(json.ViewsSchema, bb); err != nil { + return fmt.Errorf("validation failed for %q: %w", path, err) + } var in CustomView - if err := yaml.Unmarshal(raw, &in); err != nil { + if err := yaml.Unmarshal(bb, &in); err != nil { return err } - v.K9s = in.K9s + v.Views = in.Views v.fireConfigChanged() return nil @@ -84,7 +78,7 @@ func (v *CustomView) RemoveListener(gvr string) { func (v *CustomView) fireConfigChanged() { for gvr, list := range v.listeners { - if v, ok := v.K9s.Views[gvr]; ok { + if v, ok := v.Views[gvr]; ok { list.ViewSettingsChanged(v) } else { list.ViewSettingsChanged(ViewSetting{}) diff --git a/internal/config/views_test.go b/internal/config/views_test.go index c883fed95e..2afeea2854 100644 --- a/internal/config/views_test.go +++ b/internal/config/views_test.go @@ -13,7 +13,7 @@ import ( func TestViewSettingsLoad(t *testing.T) { cfg := config.NewCustomView() - assert.Nil(t, cfg.Load("testdata/view_settings.yaml")) - assert.Equal(t, 1, len(cfg.K9s.Views)) - assert.Equal(t, 4, len(cfg.K9s.Views["v1/pods"].Columns)) + assert.Nil(t, cfg.Load("testdata/views/views.yaml")) + assert.Equal(t, 1, len(cfg.Views)) + assert.Equal(t, 4, len(cfg.Views["v1/pods"].Columns)) } diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 431065904d..0ec61fd0f8 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -68,9 +68,9 @@ func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) flags.Sections = §ions flags.ActiveNamespace = &ns } - spinach := cfg.YamlExtension(filepath.Join(cfg.K9sHome(), "spinach.yaml")) + spinach := filepath.Join(cfg.AppConfigDir, "spinach.yaml") if c, err := p.GetFactory().Client().Config().CurrentContextName(); err == nil { - spinach = cfg.YamlExtension(filepath.Join(cfg.K9sHome(), fmt.Sprintf("%s_spinach.yaml", c))) + spinach = filepath.Join(cfg.AppConfigDir, fmt.Sprintf("%s_spinach.yaml", c)) } if _, err := os.Stat(spinach); err == nil { flags.Spinach = &spinach diff --git a/internal/model/cluster_info.go b/internal/model/cluster_info.go index ac69ee85ff..5f1653be38 100644 --- a/internal/model/cluster_info.go +++ b/internal/model/cluster_info.go @@ -9,8 +9,11 @@ import ( "errors" "io" "net/http" + "sync" "time" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/rs/zerolog/log" @@ -72,24 +75,25 @@ func (c ClusterMeta) Deltas(n ClusterMeta) bool { // ClusterInfo models cluster metadata. type ClusterInfo struct { - cluster *Cluster - factory dao.Factory - data ClusterMeta - version string - skipLatestRevCheck bool - listeners []ClusterInfoListener - cache *cache.LRUExpireCache + cluster *Cluster + factory dao.Factory + data ClusterMeta + version string + cfg *config.K9s + listeners []ClusterInfoListener + cache *cache.LRUExpireCache + mx sync.RWMutex } // NewClusterInfo returns a new instance. -func NewClusterInfo(f dao.Factory, v string, skipLatestRevCheck bool) *ClusterInfo { +func NewClusterInfo(f dao.Factory, v string, cfg *config.K9s) *ClusterInfo { c := ClusterInfo{ - factory: f, - cluster: NewCluster(f), - data: NewClusterMeta(), - version: v, - skipLatestRevCheck: skipLatestRevCheck, - cache: cache.NewLRUExpireCache(cacheSize), + factory: f, + cluster: NewCluster(f), + data: NewClusterMeta(), + version: v, + cfg: cfg, + cache: cache.NewLRUExpireCache(cacheSize), } return &c @@ -113,7 +117,16 @@ func (c *ClusterInfo) fetchK9sLatestRev() string { // Reset resets context and reload. func (c *ClusterInfo) Reset(f dao.Factory) { - c.cluster, c.data = NewCluster(f), NewClusterMeta() + if f == nil { + return + } + + c.mx.Lock() + { + c.cluster, c.data = NewCluster(f), NewClusterMeta() + } + c.mx.Unlock() + c.Refresh() } @@ -138,7 +151,7 @@ func (c *ClusterInfo) Refresh() { v1 := NewSemVer(data.K9sVer) var latestRev string - if !c.skipLatestRevCheck { + if !c.cfg.SkipLatestRevCheck { latestRev = c.fetchK9sLatestRev() } v2 := NewSemVer(latestRev) @@ -153,7 +166,11 @@ func (c *ClusterInfo) Refresh() { } else { c.fireNoMetaChanged(data) } - c.data = data + c.mx.Lock() + { + c.data = data + } + c.mx.Unlock() } // AddListener adds a new model listener. diff --git a/internal/model/log.go b/internal/model/log.go index 09297b06ef..d194fcf017 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -108,7 +108,7 @@ func (l *Log) SetSinceSeconds(ctx context.Context, i int64) { } // Configure sets logger configuration. -func (l *Log) Configure(opts *config.Logger) { +func (l *Log) Configure(opts config.Logger) { l.logOptions.Lines = int64(opts.TailCount) l.logOptions.SinceSeconds = opts.SinceSeconds } diff --git a/internal/ui/action.go b/internal/ui/action.go index 66913ba308..3e4b8efd2a 100644 --- a/internal/ui/action.go +++ b/internal/ui/action.go @@ -15,12 +15,19 @@ type ( // ActionHandler handles a keyboard command. ActionHandler func(*tcell.EventKey) *tcell.EventKey + ActionOpts struct { + Visible bool + Shared bool + Plugin bool + HotKey bool + Dangerous bool + } + // KeyAction represents a keyboard action. KeyAction struct { Description string Action ActionHandler - Visible bool - Shared bool + Opts ActionOpts } // KeyActions tracks mappings between keystrokes and actions. @@ -28,13 +35,32 @@ type ( ) // NewKeyAction returns a new keyboard action. -func NewKeyAction(d string, a ActionHandler, display bool) KeyAction { - return KeyAction{Description: d, Action: a, Visible: display} +func NewKeyAction(d string, a ActionHandler, visible bool) KeyAction { + return NewKeyActionWithOpts(d, a, ActionOpts{ + Visible: visible, + }) } // NewSharedKeyAction returns a new shared keyboard action. -func NewSharedKeyAction(d string, a ActionHandler, display bool) KeyAction { - return KeyAction{Description: d, Action: a, Visible: display, Shared: true} +func NewSharedKeyAction(d string, a ActionHandler, visible bool) KeyAction { + return NewKeyActionWithOpts(d, a, ActionOpts{ + Visible: visible, + Shared: true, + }) +} + +// NewKeyActionWithOpts returns a new keyboard action. +func NewKeyActionWithOpts(d string, a ActionHandler, opts ActionOpts) KeyAction { + return KeyAction{ + Description: d, + Action: a, + Opts: opts, + } +} + +func (a KeyActions) Reset(aa KeyActions) { + a.Clear() + a.Add(aa) } // Add sets up keyboard action listener. @@ -51,6 +77,15 @@ func (a KeyActions) Clear() { } } +// ClearDanger remove all dangerous actions. +func (a KeyActions) ClearDanger() { + for k, v := range a { + if v.Opts.Dangerous { + delete(a, k) + } + } +} + // Set replace actions with new ones. func (a KeyActions) Set(aa KeyActions) { for k, v := range aa { @@ -69,7 +104,7 @@ func (a KeyActions) Delete(kk ...tcell.Key) { func (a KeyActions) Hints() model.MenuHints { kk := make([]int, 0, len(a)) for k := range a { - if !a[k].Shared { + if !a[k].Opts.Shared { kk = append(kk, int(k)) } } @@ -82,7 +117,7 @@ func (a KeyActions) Hints() model.MenuHints { model.MenuHint{ Mnemonic: name, Description: a[tcell.Key(k)].Description, - Visible: a[tcell.Key(k)].Visible, + Visible: a[tcell.Key(k)].Opts.Visible, }, ) } else { diff --git a/internal/ui/app.go b/internal/ui/app.go index 1b843f9136..4a3b3e274e 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -34,12 +34,11 @@ func NewApp(cfg *config.Config, context string) *App { a := App{ Application: tview.NewApplication(), actions: make(KeyActions), - Configurator: Configurator{Config: cfg}, + Configurator: Configurator{Config: cfg, Styles: config.NewStyles()}, Main: NewPages(), flash: model.NewFlash(model.DefaultFlashDelay), cmdBuff: model.NewFishBuff(':', model.CommandBuffer), } - a.ReloadStyles() a.views = map[string]tview.Primitive{ "menu": NewMenu(a.Styles), @@ -134,11 +133,6 @@ func (a *App) StylesChanged(s *config.Styles) { } } -// ReloadStyles reloads skin file. -func (a *App) ReloadStyles() { - a.RefreshStyles() -} - // Conn returns an api server connection. func (a *App) Conn() client.Connection { return a.Config.GetConnection() diff --git a/internal/ui/config.go b/internal/ui/config.go index 4509280c2a..27c5cf82ef 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -9,6 +9,8 @@ import ( "os" "path/filepath" + "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" "github.com/fsnotify/fsnotify" @@ -17,6 +19,8 @@ import ( // Synchronizer manages ui event queue. type synchronizer interface { + Flash() *model.Flash + UpdateClusterInfo() QueueUpdateDraw(func()) QueueUpdate(func()) } @@ -46,7 +50,7 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e for { select { case evt := <-w.Events: - if evt.Name == config.AppViewsFile { + if evt.Name == config.AppViewsFile && evt.Op != fsnotify.Chmod { s.QueueUpdateDraw(func() { if err := c.RefreshCustomViews(); err != nil { log.Warn().Err(err).Msgf("Custom views refresh failed") @@ -66,10 +70,11 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e } }() - if err := c.RefreshCustomViews(); err != nil { + if err := w.Add(config.AppViewsFile); err != nil { return err } - return w.Add(config.AppViewsFile) + + return c.RefreshCustomViews() } // RefreshCustomViews load view configuration changes. @@ -85,15 +90,13 @@ func (c *Configurator) RefreshCustomViews() error { // SkinsDirWatcher watches for skin directory file changes. func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) error { - if !c.HasSkin() { - return nil + if _, err := os.Stat(config.AppSkinsDir); os.IsNotExist(err) { + return err } - w, err := fsnotify.NewWatcher() if err != nil { return err } - go func() { for { select { @@ -101,7 +104,7 @@ func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) erro if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod { log.Debug().Msgf("Skin changed: %s", c.skinFile) s.QueueUpdateDraw(func() { - c.RefreshStyles() + c.RefreshStyles(s) }) } case err := <-w.Errors: @@ -133,18 +136,20 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error select { case evt := <-w.Events: if evt.Has(fsnotify.Create) || evt.Has(fsnotify.Write) { - log.Debug().Msgf("ConfigWatcher file changed: %s -- %#v", evt.Name, evt.Op.String()) + log.Debug().Msgf("ConfigWatcher file changed: %s", evt.Name) if evt.Name == config.AppConfigFile { if err := c.Config.Load(evt.Name); err != nil { - log.Error().Err(err).Msgf("Config reload failed") + log.Error().Err(err).Msgf("k9s config reload failed") + s.Flash().Warn("k9s config reload failed. Check k9s logs!") } } else { if err := c.Config.K9s.Reload(); err != nil { - log.Error().Err(err).Msgf("Context config reload failed") + log.Error().Err(err).Msgf("k9s context config reload failed") + s.Flash().Warn("Context config reload failed. Check k9s logs!") } } s.QueueUpdateDraw(func() { - c.RefreshStyles() + c.RefreshStyles(s) }) } case err := <-w.Errors: @@ -181,11 +186,18 @@ func (c *Configurator) activeSkin() (string, bool) { return skin, false } - if ct, err := c.Config.K9s.ActiveContext(); err == nil { - skin = ct.Skin + if ct, err := c.Config.K9s.ActiveContext(); err == nil && ct.Skin != "" { + if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); !os.IsNotExist(err) { + skin = ct.Skin + log.Debug().Msgf("[Skin] Loading context skin (%q) from %q", skin, c.Config.K9s.ActiveContextName()) + } } - if skin == "" { - skin = c.Config.K9s.UI.Skin + + if sk := c.Config.K9s.UI.Skin; skin == "" && sk != "" { + if _, err := os.Stat(config.SkinFileFromName(sk)); !os.IsNotExist(err) { + skin = sk + log.Debug().Msgf("[Skin] Loading global skin (%q)", skin) + } } return skin, skin != "" @@ -208,46 +220,53 @@ func (c *Configurator) activeConfig() (cluster string, context string, ok bool) } // RefreshStyles load for skin configuration changes. -func (c *Configurator) RefreshStyles() { +func (c *Configurator) RefreshStyles(s synchronizer) { + s.UpdateClusterInfo() if c.Styles == nil { c.Styles = config.NewStyles() } + defer c.loadSkinFile(s) cl, ct, ok := c.activeConfig() if !ok { - log.Debug().Msgf("No custom skin found. Using stock skin") - c.updateStyles("") return } - + // !!BOZO!! Lame move out! if bc, err := config.EnsureBenchmarksCfgFile(cl, ct); err != nil { log.Warn().Err(err).Msgf("No benchmark config file found: %q@%q", cl, ct) } else { c.BenchFile = bc } +} +func (c *Configurator) loadSkinFile(s synchronizer) { skin, ok := c.activeSkin() if !ok { log.Debug().Msgf("No custom skin found. Using stock skin") c.updateStyles("") return } + skinFile := config.SkinFileFromName(skin) + log.Debug().Msgf("Loading skin file: %q", skinFile) if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { - log.Warn().Msgf("Skin file %q not found in skins dir: %s", skinFile, config.AppSkinsDir) + s.Flash().Warnf("Skin file %q not found in skins dir: %s", filepath.Base(skinFile), config.AppSkinsDir) } else { - log.Error().Msgf("Failed to parse skin file -- %s: %s.", skinFile, err) + s.Flash().Errf("Failed to parse skin file -- %s: %s.", filepath.Base(skinFile), err) } c.updateStyles("") } else { - log.Debug().Msgf("Loading skin file: %q", skinFile) + s.Flash().Infof("Skin file loaded: %q", skinFile) c.updateStyles(skinFile) } } func (c *Configurator) updateStyles(f string) { c.skinFile = f + if f == "" { + c.Styles.Reset() + } c.Styles.Update() render.ModColor = c.Styles.Frame().Status.ModifyColor.Color() diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index b8a81c27c4..7117de9cc2 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -7,10 +7,12 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/mock" + "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" @@ -19,15 +21,14 @@ import ( ) func TestSkinnedContext(t *testing.T) { - os.Setenv(config.K9sEnvConfigDir, "/tmp/test-config") - + os.Setenv(config.K9sEnvConfigDir, "/tmp/k9s-test") assert.NoError(t, config.InitLocs()) defer assert.NoError(t, os.RemoveAll(config.K9sEnvConfigDir)) - sf := filepath.Join("..", "config", "testdata", "black_and_wtf.yaml") + sf := filepath.Join("..", "config", "testdata", "skins", "black-and-wtf.yaml") raw, err := os.ReadFile(sf) assert.NoError(t, err) - tf := filepath.Join(config.AppSkinsDir, "black_and_wtf.yaml") + tf := filepath.Join(config.AppSkinsDir, "black-and-wtf.yaml") assert.NoError(t, os.WriteFile(tf, raw, data.DefaultFileMod)) var cfg ui.Configurator @@ -43,9 +44,8 @@ func TestSkinnedContext(t *testing.T) { mock.NewMockKubeSettings(&flags)) _, err = cfg.Config.K9s.ActivateContext("ct-1-1") assert.NoError(t, err) - cfg.Config.K9s.UI = config.UI{Skin: "black_and_wtf"} - cfg.RefreshStyles() - + cfg.Config.K9s.UI = config.UI{Skin: "black-and-wtf"} + cfg.RefreshStyles(newMockSynchronizer()) assert.True(t, cfg.HasSkin()) assert.Equal(t, tcell.ColorGhostWhite.TrueColor(), render.StdColor) assert.Equal(t, tcell.ColorWhiteSmoke.TrueColor(), render.ErrColor) @@ -60,3 +60,18 @@ func TestBenchConfig(t *testing.T) { assert.NoError(t, error) assert.Equal(t, "/tmp/test-config/clusters/cl-1/ct-1/benchmarks.yaml", bc) } + +// Helpers... + +type synchronizer struct{} + +func newMockSynchronizer() synchronizer { + return synchronizer{} +} + +func (s synchronizer) Flash() *model.Flash { + return model.NewFlash(100 * time.Millisecond) +} +func (s synchronizer) UpdateClusterInfo() {} +func (s synchronizer) QueueUpdateDraw(func()) {} +func (s synchronizer) QueueUpdate(func()) {} diff --git a/internal/ui/logo.go b/internal/ui/logo.go index 1971410cfe..eb8f8501ad 100644 --- a/internal/ui/logo.go +++ b/internal/ui/logo.go @@ -6,6 +6,7 @@ package ui import ( "fmt" "strings" + "sync" "github.com/derailed/k9s/internal/config" "github.com/derailed/tview" @@ -17,6 +18,7 @@ type Logo struct { logo, status *tview.TextView styles *config.Styles + mx sync.Mutex } // NewLogo returns a new logo. @@ -89,6 +91,9 @@ func (l *Logo) update(msg string, c config.Color) { } func (l *Logo) refreshStatus(msg string, c config.Color) { + l.mx.Lock() + defer l.mx.Unlock() + l.status.SetBackgroundColor(c.Color()) l.status.SetText( fmt.Sprintf("[%s::b]%s", l.styles.Body().LogoColorMsg, msg), @@ -96,6 +101,8 @@ func (l *Logo) refreshStatus(msg string, c config.Color) { } func (l *Logo) refreshLogo(c config.Color) { + l.mx.Lock() + defer l.mx.Unlock() l.logo.Clear() for i, s := range LogoSmall { fmt.Fprintf(l.logo, "[%s::b]%s", c, s) diff --git a/internal/ui/table.go b/internal/ui/table.go index 4f51a2f97b..66b0450308 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -367,14 +367,14 @@ func (t *Table) Refresh() { t.Update(data, t.hasMetrics) } -// GetSelectedRow returns the entire selected row. -func (t *Table) GetSelectedRow(path string) (render.Row, bool) { +// GetSelectedRow returns the entire selected row or nil if nothing selected. +func (t *Table) GetSelectedRow(path string) *render.Row { data := t.model.Peek() i, ok := data.RowEvents.FindIndex(path) if !ok { - return render.Row{}, ok + return nil } - return data.RowEvents[i].Row, true + return &data.RowEvents[i].Row } // NameColIndex returns the index of the resource name column. diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 2962de9339..f6169abdef 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -46,10 +46,11 @@ func TestTableSelection(t *testing.T) { v.Update(m.Peek(), false) v.SelectRow(1, 0, true) - r, ok := v.GetSelectedRow("r1") - assert.True(t, ok) + r := v.GetSelectedRow("r1") + if r != nil { + assert.Equal(t, render.Row{ID: "r1", Fields: render.Fields{"blee", "duh", "fred"}}, *r) + } assert.Equal(t, "r1", v.GetSelectedItem()) - assert.Equal(t, render.Row{ID: "r1", Fields: render.Fields{"blee", "duh", "fred"}}, r) assert.Equal(t, "blee", v.GetSelectedCell(0)) assert.Equal(t, 1, v.GetSelectedRowIndex()) assert.Equal(t, []string{"r1"}, v.GetSelectedItems()) diff --git a/internal/view/actions.go b/internal/view/actions.go index b6c14afca2..60007ab7a8 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -57,21 +57,27 @@ func inScope(scopes, aliases []string) bool { return false } -func hotKeyActions(r Runner, aa ui.KeyActions) { +func hotKeyActions(r Runner, aa ui.KeyActions) error { hh := config.NewHotKeys() - if err := hh.Load(); err != nil { - return + for k, a := range aa { + if a.Opts.HotKey { + delete(aa, k) + } } + var errs error + if err := hh.Load(r.App().Config.ContextHotkeysPath()); err != nil { + errs = errors.Join(errs, err) + } for k, hk := range hh.HotKey { key, err := asKey(hk.ShortCut) if err != nil { - log.Warn().Err(err).Msg("HOT-KEY Unable to map hotkey shortcut to a key") + errs = errors.Join(errs, err) continue } _, ok := aa[key] if ok { - log.Warn().Err(fmt.Errorf("HOT-KEY Doh! you are trying to override an existing command `%s", k)).Msg("Invalid shortcut") + errs = errors.Join(errs, fmt.Errorf("duplicated hotkeys found for %q in %q", hk.ShortCut, k)) continue } @@ -81,11 +87,17 @@ func hotKeyActions(r Runner, aa ui.KeyActions) { continue } - aa[key] = ui.NewSharedKeyAction( + aa[key] = ui.NewKeyActionWithOpts( hk.Description, gotoCmd(r, command, "", !hk.KeepHistory), - false) + ui.ActionOpts{ + Shared: true, + HotKey: true, + }, + ) } + + return errs } func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler { @@ -95,31 +107,42 @@ func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler { } } -func pluginActions(r Runner, aa ui.KeyActions) { +func pluginActions(r Runner, aa ui.KeyActions) error { pp := config.NewPlugins() - if err := pp.Load(r.App().Config.ContextPluginsPath()); err != nil { - return + for k, a := range aa { + if a.Opts.Plugin { + delete(aa, k) + } } + var errs error + if err := pp.Load(r.App().Config.ContextPluginsPath()); err != nil { + errs = errors.Join(errs, err) + } for k, plugin := range pp.Plugins { if !inScope(plugin.Scopes, r.Aliases()) { continue } key, err := asKey(plugin.ShortCut) if err != nil { - log.Warn().Err(err).Msg("Unable to map plugin shortcut to a key") + errs = errors.Join(errs, err) continue } _, ok := aa[key] if ok { - log.Warn().Msgf("Invalid shortcut. You are trying to override an existing command `%s", k) + errs = errors.Join(errs, fmt.Errorf("duplicated plugin key found for %q in %q", plugin.ShortCut, k)) continue } - aa[key] = ui.NewKeyAction( + aa[key] = ui.NewKeyActionWithOpts( plugin.Description, pluginAction(r, plugin), - true) + ui.ActionOpts{ + Visible: true, + Plugin: true, + }) } + + return errs } func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { diff --git a/internal/view/app.go b/internal/view/app.go index b907dcedcc..4c4c1959de 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -66,6 +66,7 @@ func NewApp(cfg *config.Config) *App { filterHistory: model.NewHistory(model.MaxHistory), Content: NewPageStack(), } + a.ReloadStyles() a.Views()["statusIndicator"] = ui.NewStatusIndicator(a.App, a.Styles) a.Views()["clusterInfo"] = NewClusterInfo(&a) @@ -73,6 +74,18 @@ func NewApp(cfg *config.Config) *App { return &a } +// ReloadStyles reloads skin file. +func (a *App) ReloadStyles() { + a.RefreshStyles(a) +} + +// UpdateClusterInfo updates clusterInfo panel +func (a *App) UpdateClusterInfo() { + if a.factory != nil { + a.clusterModel.Reset(a.factory) + } +} + // ConOK checks the connection is cool, returns false otherwise. func (a *App) ConOK() bool { return atomic.LoadInt32(&a.conRetry) == 0 @@ -104,7 +117,7 @@ func (a *App) Init(version string, rate int) error { } a.initFactory(ns) - a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s.SkipLatestRevCheck) + a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s) a.clusterModel.AddListener(a.clusterInfo()) a.clusterModel.AddListener(a.statusIndicator()) if a.Conn().ConnectionOK() { @@ -124,6 +137,7 @@ func (a *App) Init(version string, rate int) error { if a.Config.K9s.ImageScans.Enable { a.initImgScanner(version) } + a.ReloadStyles() return nil } @@ -324,17 +338,17 @@ func (a *App) Resume() { ctx, a.cancelFn = context.WithCancel(context.Background()) go a.clusterUpdater(ctx) - if err := a.ConfigWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("ConfigWatcher failed") - } - if err := a.SkinsDirWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("SkinsWatcher failed") - } - if err := a.CustomViewsWatcher(ctx, a); err != nil { - log.Warn().Err(err).Msgf("CustomView watcher failed") - } else { - log.Debug().Msgf("CustomViews watching `%s", config.AppViewsFile) + if a.Config.K9s.UI.Reactive { + if err := a.ConfigWatcher(ctx, a); err != nil { + log.Warn().Err(err).Msgf("ConfigWatcher failed") + } + if err := a.SkinsDirWatcher(ctx, a); err != nil { + log.Warn().Err(err).Msgf("SkinsWatcher failed") + } + if err := a.CustomViewsWatcher(ctx, a); err != nil { + log.Warn().Err(err).Msgf("CustomView watcher failed") + } } } @@ -398,10 +412,12 @@ func (a *App) refreshCluster(context.Context) error { // Reload alias go func() { if err := a.command.Reset(a.Config.ContextAliasesPath(), false); err != nil { - log.Error().Err(err).Msgf("Command reset failed") + log.Warn().Err(err).Msgf("Command reset failed") + a.QueueUpdateDraw(func() { + a.Logo().Warn("Aliases load failed!") + }) } }() - // Update cluster info a.clusterModel.Refresh() diff --git a/internal/view/browser.go b/internal/view/browser.go index 21176d69ba..9589e99b36 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -249,6 +249,13 @@ func (b *Browser) TableDataChanged(data *render.TableData) { b.app.QueueUpdateDraw(func() { b.refreshActions() + if !b.app.Config.K9s.UI.Reactive { + if err := b.app.RefreshCustomViews(); err != nil { + log.Warn().Err(err).Msg("CustomViews load failed") + b.app.Logo().Warn("Views load failed!") + } + } + b.Update(data, b.app.Conn().HasMetrics()) }) } @@ -497,25 +504,41 @@ func (b *Browser) refreshActions() { b.namespaceActions(aa) if !b.app.Config.K9s.IsReadOnly() { if client.Can(b.meta.Verbs, "edit") { - aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true) + aa[ui.KeyE] = ui.NewKeyActionWithOpts("Edit", b.editCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }) } if client.Can(b.meta.Verbs, "delete") { - aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", b.deleteCmd, true) + aa[tcell.KeyCtrlD] = ui.NewKeyActionWithOpts("Delete", b.deleteCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }) } + } else { + b.Actions().ClearDanger() } } - if !dao.IsK9sMeta(b.meta) { aa[ui.KeyY] = ui.NewKeyAction(yamlAction, b.viewCmd, true) aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true) } - - pluginActions(b, aa) - hotKeyActions(b, aa) for _, f := range b.bindKeysFn { f(aa) } b.Actions().Add(aa) + + if err := pluginActions(b, b.Actions()); err != nil { + log.Warn().Msgf("Plugins load failed: %s", err) + b.app.Logo().Warn("Plugins load failed!") + } + if err := hotKeyActions(b, b.Actions()); err != nil { + log.Warn().Msgf("Hotkeys load failed: %s", err) + b.app.Logo().Warn("HotKeys load failed!") + } + b.app.Menu().HydrateMenu(b.Hints()) } diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index 8b91eff8a0..e6a24dea69 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -100,12 +100,25 @@ func (c *ClusterInfo) ClusterInfoUpdated(data model.ClusterMeta) { c.ClusterInfoChanged(data, data) } +func (c *ClusterInfo) warnCell(s string, w bool) string { + if w { + return fmt.Sprintf("[orangered::b]%s", s) + } + + return s +} + // ClusterInfoChanged notifies the cluster meta was changed. func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) { c.app.QueueUpdateDraw(func() { + var ic = " ✏️" + if c.app.Config.K9s.IsReadOnly() { + ic = " 🔒" + } + c.Clear() c.layout() - row := c.setCell(0, curr.Context) + row := c.setCell(0, curr.Context+ic) row = c.setCell(row, curr.Cluster) row = c.setCell(row, curr.User) if curr.K9sLatest != "" { @@ -119,8 +132,8 @@ func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) { _ = c.setCell(row, ui.AsPercDelta(prev.Mem, curr.Mem)) c.setDefCon(curr.Cpu, curr.Mem) } else { - row = c.setCell(row, "[orangered::b]n/a") - _ = c.setCell(row, "[orangered::b]n/a") + row = c.setCell(row, c.warnCell(render.NAValue, true)) + _ = c.setCell(row, c.warnCell(render.NAValue, true)) } c.updateStyle() }) diff --git a/internal/view/command.go b/internal/view/command.go index 30f2e3cb2f..97676176ef 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -96,6 +96,16 @@ func (c *Command) contextCmd(p *cmd.Interpreter) error { return c.exec(p, gvr, c.componentFor(gvr, ct, v), true) } +func (c *Command) aliasCmd(p *cmd.Interpreter) error { + filter, _ := p.FilterArg() + + gvr := client.NewGVR("aliases") + v := NewAlias(gvr) + v.SetFilter(filter) + + return c.exec(p, gvr, v, false) +} + func (c *Command) xrayCmd(p *cmd.Interpreter) error { arg, cns, ok := p.XrayArgs() if !ok { @@ -118,7 +128,6 @@ func (c *Command) xrayCmd(p *cmd.Interpreter) error { if err := c.app.switchNS(ns); err != nil { return err } - if err := c.app.Config.Save(); err != nil { return err } @@ -210,7 +219,9 @@ func (c *Command) specialCmd(p *cmd.Interpreter) bool { case p.IsHelpCmd(): _ = c.app.helpCmd(nil) case p.IsAliasCmd(): - _ = c.app.aliasCmd(nil) + if err := c.aliasCmd(p); err != nil { + c.app.Flash().Err(err) + } case p.IsXrayCmd(): if err := c.xrayCmd(p); err != nil { c.app.Flash().Err(err) diff --git a/internal/view/container.go b/internal/view/container.go index 7a2238a3fa..3d25a78eac 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -15,7 +15,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" ) @@ -58,8 +57,20 @@ func (c *Container) Name() string { return containerTitle } func (c *Container) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true), - ui.KeyA: ui.NewKeyAction("Attach", c.attachCmd, true), + ui.KeyS: ui.NewKeyActionWithOpts( + "Shell", + c.shellCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyA: ui.NewKeyActionWithOpts( + "Attach", + c.attachCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } @@ -80,10 +91,7 @@ func (c *Container) bindKeys(aa ui.KeyActions) { func (c *Container) k9sEnv() Env { path := c.GetTable().GetSelectedItem() - row, ok := c.GetTable().GetSelectedRow(path) - if !ok { - log.Error().Msgf("unable to locate selected row for %q", path) - } + row := c.GetTable().GetSelectedRow(path) env := defaultEnv(c.App().Conn().Config(), path, c.GetTable().GetModel().Peek().Header, row) env["NAMESPACE"], env["POD"] = client.Namespaced(c.GetTable().Path) diff --git a/internal/view/details.go b/internal/view/details.go index 53c9ee8db1..2a52e24da2 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -297,7 +297,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(d.app.Config.K9s.ActiveScreenDumpsDir(), d.title, d.text.GetText(true)); err != nil { + if path, err := saveYAML(d.app.Config.K9s.ContextScreenDumpDir(), d.title, d.text.GetText(true)); err != nil { d.app.Flash().Err(err) } else { d.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/dir.go b/internal/view/dir.go index a55220a0eb..dae5ca724e 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -62,13 +62,23 @@ func (d *Dir) dirContext(ctx context.Context) context.Context { func (d *Dir) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyA: ui.NewKeyAction("Apply", d.applyCmd, true), - ui.KeyD: ui.NewKeyAction("Delete", d.delCmd, true), - ui.KeyE: ui.NewKeyAction("Edit", d.editCmd, true), + ui.KeyA: ui.NewKeyActionWithOpts("Apply", d.applyCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyD: ui.NewKeyActionWithOpts("Delete", d.delCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyE: ui.NewKeyActionWithOpts("Edit", d.editCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } func (d *Dir) bindKeys(aa ui.KeyActions) { + // !!BOZO!! Lame! aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ) if !d.App().Config.K9s.IsReadOnly() { diff --git a/internal/view/exec.go b/internal/view/exec.go index 97d0dc539b..e0af76b92a 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -403,7 +403,7 @@ func k9sShellPodName() string { return fmt.Sprintf("%s-%d", k9sShell, os.Getpid()) } -func k9sShellPod(node string, cfg *config.ShellPod) *v1.Pod { +func k9sShellPod(node string, cfg config.ShellPod) *v1.Pod { var grace int64 var priv bool = true @@ -500,7 +500,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.W log.Debug().Msgf("Running Start") err := cmd.Run() - log.Debug().Msgf("Running Done: %s", err) + log.Debug().Msgf("Running Done: %v", err) if err == nil { statusChan <- fmt.Sprintf("Command completed successfully: %q", cmd.String()) } diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go index ce746ff682..0949d5dd5e 100644 --- a/internal/view/helm_history.go +++ b/internal/view/helm_history.go @@ -83,7 +83,14 @@ func (h *History) getValsCmd(app *App, _ ui.Tabular, _ client.GVR, path string) func (h *History) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyR: ui.NewKeyAction("RollBackTo...", h.rollbackCmd, true), + ui.KeyR: ui.NewKeyActionWithOpts( + "RollBackTo...", + h.rollbackCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + ), }) } diff --git a/internal/view/help.go b/internal/view/help.go index fd9da2ab13..06665e7be9 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -191,7 +191,7 @@ func (h *Help) showNav() model.MenuHints { func (h *Help) showHotKeys() (model.MenuHints, error) { hh := config.NewHotKeys() - if err := hh.Load(); err != nil { + if err := hh.Load(h.App().Config.ContextHotkeysPath()); err != nil { return nil, fmt.Errorf("no hotkey configuration found") } kk := make(sort.StringSlice, 0, len(hh.HotKey)) diff --git a/internal/view/help_test.go b/internal/view/help_test.go index 818002ae72..f45a2cf839 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -25,7 +25,7 @@ func TestHelp(t *testing.T) { assert.Nil(t, v.Init(ctx)) assert.Equal(t, 28, v.GetRowCount()) - assert.Equal(t, 6, v.GetColumnCount()) + assert.Equal(t, 8, v.GetColumnCount()) assert.Equal(t, "", strings.TrimSpace(v.GetCell(1, 0).Text)) assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text)) } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 6648b1daa0..90803dff31 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -87,9 +87,12 @@ func k8sEnv(c *client.Config) Env { } } -func defaultEnv(c *client.Config, path string, header render.Header, row render.Row) Env { +func defaultEnv(c *client.Config, path string, header render.Header, row *render.Row) Env { env := k8sEnv(c) env["NAMESPACE"], env["NAME"] = client.Namespaced(path) + if row == nil { + return env + } for _, col := range header.Columns(true) { i := header.IndexOf(col, true) if i >= 0 && i < len(row.Fields) { @@ -154,7 +157,7 @@ func asKey(key string) (tcell.Key, error) { } } - return 0, fmt.Errorf("no matching key found %s", key) + return 0, fmt.Errorf("invalid key specified: %q", key) } // FwFQN returns a fully qualified ns/name:container id. diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 0278afaa02..9ec347c8aa 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -108,7 +108,7 @@ func TestAsKey(t *testing.T) { e tcell.Key }{ "cool": {k: "Ctrl-A", e: tcell.KeyCtrlA}, - "miss": {k: "fred", e: 0, err: errors.New("no matching key found fred")}, + "miss": {k: "fred", e: 0, err: errors.New(`invalid key specified: "fred"`)}, } for k := range uu { @@ -157,7 +157,7 @@ func TestK9sEnv(t *testing.T) { r := render.Row{ Fields: []string{"a1", "b1", "c1"}, } - env := defaultEnv(c, "fred/blee", h, r) + env := defaultEnv(c, "fred/blee", h, &r) assert.Equal(t, 10, len(env)) assert.Equal(t, cl, env["CLUSTER"]) diff --git a/internal/view/live_view.go b/internal/view/live_view.go index d11a092f14..980d3742c2 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -357,7 +357,7 @@ func (v *LiveView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *LiveView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { name := fmt.Sprintf("%s--%s", strings.Replace(v.model.GetPath(), "/", "-", 1), strings.ToLower(v.title)) - if _, err := saveYAML(v.app.Config.K9s.ActiveScreenDumpsDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { + if _, err := saveYAML(v.app.Config.K9s.ContextScreenDumpDir(), name, sanitizeEsc(v.text.GetText(true))); err != nil { v.app.Flash().Err(err) } else { v.app.Flash().Infof("File %q saved successfully!", name) diff --git a/internal/view/log.go b/internal/view/log.go index 88b0cd8bfe..3fdc49ff2c 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -407,7 +407,7 @@ func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey { // SaveCmd dumps the logs to file. func (l *Log) SaveCmd(*tcell.EventKey) *tcell.EventKey { - path, err := saveData(l.app.Config.K9s.ActiveScreenDumpsDir(), l.model.GetPath(), l.logs.GetText(true)) + path, err := saveData(l.app.Config.K9s.ContextScreenDumpDir(), l.model.GetPath(), l.logs.GetText(true)) if err != nil { l.app.Flash().Err(err) return nil diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index 35962b92e0..6e3f65291b 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -34,7 +34,7 @@ func NewLogIndicator(cfg *config.Config, styles *config.Styles, allContainers bo TextView: tview.NewTextView(), indicator: make([]byte, 0, 100), scrollStatus: 1, - fullScreen: cfg.K9s.Logger.FullScreenLogs, + fullScreen: cfg.K9s.Logger.FullScreen, textWrap: cfg.K9s.Logger.TextWrap, showTime: cfg.K9s.Logger.ShowTime, shouldDisplayAllContainers: allContainers, diff --git a/internal/view/log_test.go b/internal/view/log_test.go index a55c50d9c7..6b466cf8ec 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -112,7 +112,7 @@ func TestLogViewSave(t *testing.T) { dd := "/tmp/test-dumps/na" assert.NoError(t, ensureDumpDir(dd)) app.Config.K9s.ScreenDumpDir = "/tmp/test-dumps" - dir := app.Config.K9s.ActiveScreenDumpsDir() + dir := app.Config.K9s.ContextScreenDumpDir() c1, err := os.ReadDir(dir) assert.NoError(t, err, fmt.Sprintf("Dir: %q", dir)) v.SaveCmd(nil) diff --git a/internal/view/logger.go b/internal/view/logger.go index 00446b0b17..7e0526cf36 100644 --- a/internal/view/logger.go +++ b/internal/view/logger.go @@ -154,7 +154,7 @@ func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(l.app.Config.K9s.ActiveScreenDumpsDir(), l.title, l.GetText(true)); err != nil { + if path, err := saveYAML(l.app.Config.K9s.ContextScreenDumpDir(), l.title, l.GetText(true)); err != nil { l.app.Flash().Err(err) } else { l.app.Flash().Infof("Log %s saved successfully!", path) diff --git a/internal/view/node.go b/internal/view/node.go index c5a14b932c..e39d4091ad 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -41,9 +41,30 @@ func (n *Node) nodeContext(ctx context.Context) context.Context { func (n *Node) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyC: ui.NewKeyAction("Cordon", n.toggleCordonCmd(true), true), - ui.KeyU: ui.NewKeyAction("Uncordon", n.toggleCordonCmd(false), true), - ui.KeyR: ui.NewKeyAction("Drain", n.drainCmd, true), + ui.KeyC: ui.NewKeyActionWithOpts( + "Cordon", + n.toggleCordonCmd(true), + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + ), + ui.KeyU: ui.NewKeyActionWithOpts( + "Uncordon", + n.toggleCordonCmd(false), + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + ), + ui.KeyR: ui.NewKeyActionWithOpts( + "Drain", + n.drainCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + ), }) ct, err := n.App().Config.K9s.ActiveContext() if err != nil { @@ -66,7 +87,7 @@ func (n *Node) bindKeys(aa ui.KeyActions) { ui.KeyY: ui.NewKeyAction(yamlAction, n.yamlCmd, true), ui.KeyShiftC: ui.NewKeyAction("Sort CPU", n.GetTable().SortColCmd(cpuCol, false), false), ui.KeyShiftM: ui.NewKeyAction("Sort MEM", n.GetTable().SortColCmd(memCol, false), false), - ui.KeyShift0: ui.NewKeyAction("Sort Pods", n.GetTable().SortColCmd("PODS", false), false), + ui.KeyShiftO: ui.NewKeyAction("Sort Pods", n.GetTable().SortColCmd("PODS", false), false), }) } diff --git a/internal/view/pod.go b/internal/view/pod.go index 1ef29bab80..6278691cea 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -72,11 +72,41 @@ func (p *Pod) portForwardIndicator(data *render.TableData) { func (p *Pod) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - tcell.KeyCtrlK: ui.NewKeyAction("Kill", p.killCmd, true), - ui.KeyS: ui.NewKeyAction("Shell", p.shellCmd, true), - ui.KeyA: ui.NewKeyAction("Attach", p.attachCmd, true), - ui.KeyT: ui.NewKeyAction("Transfer", p.transferCmd, true), - ui.KeyZ: ui.NewKeyAction("Sanitize", p.sanitizeCmd, true), + tcell.KeyCtrlK: ui.NewKeyActionWithOpts( + "Kill", + p.killCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyS: ui.NewKeyActionWithOpts( + "Shell", + p.shellCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyA: ui.NewKeyActionWithOpts( + "Attach", + p.attachCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyT: ui.NewKeyActionWithOpts( + "Transfer", + p.transferCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + ui.KeyZ: ui.NewKeyActionWithOpts( + "Sanitize", + p.sanitizeCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go index 629e68f029..1e83ced88c 100644 --- a/internal/view/restart_extender.go +++ b/internal/view/restart_extender.go @@ -34,7 +34,10 @@ func (r *RestartExtender) bindKeys(aa ui.KeyActions) { return } aa.Add(ui.KeyActions{ - ui.KeyR: ui.NewKeyAction("Restart", r.restartCmd, true), + ui.KeyR: ui.NewKeyActionWithOpts("Restart", r.restartCmd, ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index 86bc7809c9..14094dcfe4 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -34,7 +34,11 @@ func (s *ScaleExtender) bindKeys(aa ui.KeyActions) { return } aa.Add(ui.KeyActions{ - ui.KeyS: ui.NewKeyAction("Scale", s.scaleCmd, true), + ui.KeyS: ui.NewKeyActionWithOpts("Scale", s.scaleCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 4e4371c13e..83bbca09ac 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -35,7 +35,7 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { } func (s *ScreenDump) dirContext(ctx context.Context) context.Context { - dir := s.App().Config.K9s.ActiveScreenDumpsDir() + dir := s.App().Config.K9s.ContextScreenDumpDir() if err := data.EnsureFullPath(dir, data.DefaultDirMod); err != nil { s.App().Flash().Err(err) return ctx diff --git a/internal/view/table.go b/internal/view/table.go index 8a38693382..f1e28b901d 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -15,7 +15,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) // Table represents a table viewer. @@ -111,10 +110,7 @@ func (t *Table) EnvFn() EnvFunc { func (t *Table) defaultEnv() Env { path := t.GetSelectedItem() - row, ok := t.GetSelectedRow(path) - if !ok { - log.Error().Msgf("unable to locate selected row for %q", path) - } + row := t.GetSelectedRow(path) env := defaultEnv(t.app.Conn().Config(), path, t.GetModel().Peek().Header, row) env["FILTER"] = t.CmdBuff().GetText() if env["FILTER"] == "" { @@ -172,7 +168,7 @@ func (t *Table) BufferActive(state bool, k model.BufferKind) { } func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveTable(t.app.Config.K9s.ActiveScreenDumpsDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { + if path, err := saveTable(t.app.Config.K9s.ContextScreenDumpDir(), t.GVR().R(), t.Path, t.GetFilteredData()); err != nil { t.app.Flash().Err(err) } else { t.app.Flash().Infof("File saved successfully: %q", render.Truncate(filepath.Base(path), 50)) diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 13b564fc79..d87f9e90c3 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -29,7 +29,7 @@ func TestTableSave(t *testing.T) { v.SetTitle("k9s-test") assert.NoError(t, ensureDumpDir("/tmp/test-dumps")) - dir := v.app.Config.K9s.ActiveScreenDumpsDir() + dir := v.app.Config.K9s.ContextScreenDumpDir() c1, _ := os.ReadDir(dir) v.saveCmd(nil) diff --git a/internal/view/workload.go b/internal/view/workload.go index dd4d43b66d..0f66508e0f 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -38,8 +38,20 @@ func NewWorkload(gvr client.GVR) ResourceViewer { func (w *Workload) bindDangerousKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ - ui.KeyE: ui.NewKeyAction("Edit", w.editCmd, true), - tcell.KeyCtrlD: ui.NewKeyAction("Delete", w.deleteCmd, true), + ui.KeyE: ui.NewKeyActionWithOpts( + "Edit", + w.editCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), + tcell.KeyCtrlD: ui.NewKeyActionWithOpts( + "Delete", + w.deleteCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }), }) } diff --git a/internal/view/xray.go b/internal/view/xray.go index 564ed188f5..694ddd84f1 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -133,8 +133,12 @@ func (x *Xray) refreshActions() { aa := make(ui.KeyActions) defer func() { - pluginActions(x, aa) - hotKeyActions(x, aa) + if err := pluginActions(x, aa); err != nil { + log.Warn().Err(err).Msg("Plugins load failed") + } + if err := hotKeyActions(x, aa); err != nil { + log.Warn().Err(err).Msg("HotKeys load failed") + } x.Actions().Add(aa) x.app.Menu().HydrateMenu(x.Hints()) diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index c22567036b..01f1f15424 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -27,6 +27,7 @@ import ( "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" "github.com/anchore/grype/grype/vex" + "github.com/anchore/syft/syft/pkg/cataloger" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -40,11 +41,11 @@ type imageScanner struct { scans Scans mx sync.RWMutex initialized bool - config *config.ImageScans + config config.ImageScans } // NewImageScanner returns a new instance. -func NewImageScanner(cfg *config.ImageScans) *imageScanner { +func NewImageScanner(cfg config.ImageScans) *imageScanner { return &imageScanner{ scans: make(Scans), config: cfg, @@ -183,7 +184,7 @@ func getProviderConfig(opts *options.Grype) pkg.ProviderConfig { SyftProviderConfig: pkg.SyftProviderConfig{ RegistryOptions: opts.Registry.ToOptions(), Exclusions: opts.Exclusions, - CatalogingOptions: opts.Search.ToConfig(), + CatalogingOptions: cataloger.DefaultConfig(), Platform: opts.Platform, Name: opts.Name, DefaultImagePullSource: opts.DefaultImagePullSource, diff --git a/plugins/blame.yml b/plugins/blame.yaml similarity index 100% rename from plugins/blame.yml rename to plugins/blame.yaml diff --git a/plugins/helm_values.yaml b/plugins/helm-values.yaml similarity index 100% rename from plugins/helm_values.yaml rename to plugins/helm-values.yaml diff --git a/plugins/job_suspend.yaml b/plugins/job-suspend.yaml similarity index 100% rename from plugins/job_suspend.yaml rename to plugins/job-suspend.yaml diff --git a/plugins/k3d_root_shell.yaml b/plugins/k3d-root-shell.yaml similarity index 100% rename from plugins/k3d_root_shell.yaml rename to plugins/k3d-root-shell.yaml diff --git a/plugins/log_full.yaml b/plugins/log-full.yaml similarity index 100% rename from plugins/log_full.yaml rename to plugins/log-full.yaml diff --git a/plugins/log_jq.yaml b/plugins/log-jq.yaml similarity index 100% rename from plugins/log_jq.yaml rename to plugins/log-jq.yaml diff --git a/plugins/log_stern.yaml b/plugins/log-stern.yaml similarity index 100% rename from plugins/log_stern.yaml rename to plugins/log-stern.yaml diff --git a/plugins/remove_finalizers.yml b/plugins/remove-finalizers.yaml similarity index 100% rename from plugins/remove_finalizers.yml rename to plugins/remove-finalizers.yaml diff --git a/plugins/resource-recommendations.yml b/plugins/resource-recommendations.yaml similarity index 100% rename from plugins/resource-recommendations.yml rename to plugins/resource-recommendations.yaml diff --git a/plugins/schema.json b/plugins/schema.json deleted file mode 100644 index 3dd50016e9..0000000000 --- a/plugins/schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Schema for k9s CLI plugin.yml file : https://k9scli.io/topics/plugins", - "type": "object", - "additionalProperties": false, - "properties": { - "plugin": { - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": false, - "properties": { - "shortCut": { - "description": "Define a mnemonic to invoke the plugin", - "type": "string" - }, - "description": { - "description": "What will be shown on the K9s menu", - "type": "string" - }, - "confirm": { - "description": "See the command that is going to be executed and gives you an option to confirm", - "type": "boolean" - }, - "scopes": { - "type": "array", - "description": "Collections of views that support this shortcut. (You can use `all`)", - "items": { - "type": "string" - } - }, - "command": { - "description": "The command to run upon invocation. Can use Krew plugins here too!", - "type": "string" - }, - "background": { - "description": "Whether or not to run the command in background mode", - "type": "boolean" - }, - "args": { - "description": "Defines the command arguments", - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["shortCut", "description", "scopes", "command"] - }, - "required": [] - } - }, - "required": ["plugin"] -} diff --git a/plugins/watch_events.yaml b/plugins/watch-events.yaml similarity index 99% rename from plugins/watch_events.yaml rename to plugins/watch-events.yaml index db96c6eb3f..24ef63e669 100644 --- a/plugins/watch_events.yaml +++ b/plugins/watch-events.yaml @@ -1,7 +1,6 @@ # watch events on selected resources # requires linux "watch" command # change '-n' to adjust refresh time in seconds - plugins: watch-events: shortCut: Shift-E diff --git a/skins/black-and-wtf.yaml b/skins/black-and-wtf.yaml index 2b903ad78a..69fe02ef4a 100644 --- a/skins/black-and-wtf.yaml +++ b/skins/black-and-wtf.yaml @@ -24,7 +24,7 @@ k9s: prompt: fgColor: *fg bgColor: *bg - suggestColor: &gray + suggestColor: *gray info: fgColor: *text sectionColor: *fg diff --git a/skins/nightfox.yaml b/skins/nightfox.yaml index 6d144c69b6..61ec7dbd16 100644 --- a/skins/nightfox.yaml +++ b/skins/nightfox.yaml @@ -99,5 +99,5 @@ k9s: indicator: fgColor: *foreground bgColor: *selection - toggleOnColor: *margenta + toggleOnColor: *magenta toggleOffColor: *blue diff --git a/skins/transparent.yaml b/skins/transparent.yaml index be0a72e081..9a103a87fc 100644 --- a/skins/transparent.yaml +++ b/skins/transparent.yaml @@ -7,7 +7,7 @@ k9s: body: bgColor: default - promt: + prompt: bgColor: default info: sectionColor: default diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2b3acfe19d..2e727e153c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.30.8' +version: 'v0.31.0' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 98a7f3f1d6349c166b5a5c35d4d46cca040ed45b Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Mon, 8 Jan 2024 23:38:18 -0700 Subject: [PATCH 071/169] K9s/release v0.31.1 (#2443) * [Bug] Fix #2428 * [Bug] Ensures primordials configs are gen/saved if not present * v0.31.1 release notes --- Makefile | 2 +- change_logs/release_v0.31.0.md | 2 +- change_logs/release_v0.31.1.md | 157 ++++++++++++++++++++++ internal/config/config.go | 6 +- internal/config/json/schemas/hotkeys.json | 3 +- internal/config/views.go | 3 + snap/snapcraft.yaml | 2 +- 7 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 change_logs/release_v0.31.1.md diff --git a/Makefile b/Makefile index 7e77251a8c..143ff1238c 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.0 +VERSION ?= v0.31.1 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.0.md b/change_logs/release_v0.31.0.md index d4b92246bc..794089ac63 100644 --- a/change_logs/release_v0.31.0.md +++ b/change_logs/release_v0.31.0.md @@ -150,4 +150,4 @@ Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine cont * [#2420](https://github.com/derailed/k9s/pull/2420) supports referencing envs in hotkeys * [#2419](https://github.com/derailed/k9s/pull/2419) fix typo - © 2023 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/change_logs/release_v0.31.1.md b/change_logs/release_v0.31.1.md new file mode 100644 index 0000000000..8f9f730db2 --- /dev/null +++ b/change_logs/release_v0.31.1.md @@ -0,0 +1,157 @@ + + +# Release v0.31.1 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +## ♫ Sounds Behind The Release ♭ + +* [Border Crossing - Eek A Mouse](https://www.youtube.com/watch?v=KaAC9dBPcOM) +* [The Weight - The Band](https://www.youtube.com/watch?v=FFqb1I-hiHE) +* [Wonderin' - Neil Young](https://www.youtube.com/watch?v=h0PlwVPbM5k) +* [When Your Lover Has Gone - Louis Armstrong](https://www.youtube.com/watch?v=1tdfIj0fvlA) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Jacky Nguyen](https://github.com/nktpro) +* [Eckl, Máté](https://github.com/ecklm) +* [Jörgen](https://github.com/wthrbtn) +* [kmath313](https://github.com/kmath313) +* [a-thomas-22](https://github.com/a-thomas-22) +* [wpbeckwith](https://github.com/wpbeckwith) +* [Dima Altukhov](https://github.com/alt-dima) +* [Shoshin Nikita](https://github.com/ShoshinNikita) +* [Tu Hoang](https://github.com/rebyn) +* [Andreas Frangopoulos](https://github.com/qubeio) + +> Sponsorship cancellations since the last release: **7!** 🥹 + +## Feature Release! + +😳 Found a few issues in the neutrino drive... +This is another fairly heavy drop so bracing for impact 😱 +Be sure to dial in the v0.31.0 SneakPeek video below for the gory details! + +😵 Hopefully we've move the needle in the right direction on this drop... 🤞 + +Thank you all for your kindness, feedback and assistance in flushing out issues!! + +> ☢️ Repeating v0.31.0 release notes here as we tweaked the initial drop ☢️ + +### Hold My Hand... + +In this drop, we've added schema validation to ensure various configs are setup as expected. +K9s will now run validation checks on the following configurations: + +1. K9s main configuration (config.yaml) +2. Context specific configs (clusterX/contextY/config.yaml) +3. Skins +4. Aliases +5. HotKeys +6. Plugins +7. Views + +K9s behavior changed in this release if the main configuration does not match schema expectations. +In the past, the configuration will be validated, updated and saved should validation checks failed. Now the app will stop and report validation issues. + +The schemas are set to be a bit loose for the time being. Once we/ve vetted they are cool, we could publish them out (with additional TLC!) so k9s users can leverage them in their favorite editors. + +In the meantime, you'll need to keep k9s logs handy, to check for validation errors. The validation messages can be somewhat cryptic at times and so please be sure to include your debug logs and config settings when reporting issues which might be plenty ;(. + +### Breaking Bad! + +With this release, k9s may not start correctly if the config.yaml configurations are incorrect! + +Configuration changes: + +1. DRY fullScreenLogs -> fullScreens (k9s root config.yaml) + + ```yaml + # $XDG_CONFIG_HOME/k9s/config.yaml + k9s: + liveViewAutoRefresh: false + logger: + sinceSeconds: -1 + fullScreen: false # => Was fullScreenLogs + ... + ``` + +2. Views Configuration. + To match other configurations the root is now `views:` vs `k9s: views:` + + ```yaml + # $XDG_CONFIG_HOME/k9s/views.yaml + views: # => Was k9s:\n views: + v1/pods: + columns: + - AGE + - NAMESPACE + ... + ``` + +### Serenity Now! + + You can now opt in/out of the `reactive ui` feature. This feature enable users to make change to some configurations and see changes reflected live in the ui. This feature is now disabled by default and one must opt-in to enable via `k9s.UI.reactive` + Reactive UI provides for monitoring various config files on disk and update the UI when changes to those files occur. This is handy while tuning skins, plugins, aliases, hotkeys and benchmarks parameters. + + ```yaml + # $XDG_CONFIG_HOME/k9s/config.yaml + k9s: + liveViewAutoRefresh: false + UI: + ... + reactive: true # => enable/disable reactive UI + ... + ``` + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2434](https://github.com/derailed/k9s/issues/2434) readOnly: true in config.yaml doesnt get overriden by readOnly: false in cluster config +* [#2430](https://github.com/derailed/k9s/issues/2430) Referencing a namespace with the name of an alias inside an alias causes infinite loop +* [#2428](https://github.com/derailed/k9s/issues/2428) Boom!! runtime error: invalid memory address or nil pointer dereference - v0.30.8 +* [#2421](https://github.com/derailed/k9s/issues/2421) k9s/config.yaml configuration file is overwritten on launch + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2433](https://github.com/derailed/k9s/pull/2433) switch contexts only when needed +* [#2429](https://github.com/derailed/k9s/pull/2429) Reference correct configuration ENV var in README +* [#2426](https://github.com/derailed/k9s/pull/2426) Update carvel plugin kick to shift K +* [#2420](https://github.com/derailed/k9s/pull/2420) supports referencing envs in hotkeys +* [#2419](https://github.com/derailed/k9s/pull/2419) fix typo + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/config.go b/internal/config/config.go index d1ccc302a8..89adde3266 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -205,6 +205,11 @@ func (c *Config) Merge(c1 *Config) { // Load loads K9s configuration from file. func (c *Config) Load(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := c.Save(); err != nil { + return err + } + } bb, err := os.ReadFile(path) if err != nil { return err @@ -254,7 +259,6 @@ func (c *Config) Validate() { if c.K9s == nil { c.K9s = NewK9s(c.conn, c.settings) } - c.K9s.Validate(c.conn, c.settings) } diff --git a/internal/config/json/schemas/hotkeys.json b/internal/config/json/schemas/hotkeys.json index b567d6c917..627730ffd7 100644 --- a/internal/config/json/schemas/hotkeys.json +++ b/internal/config/json/schemas/hotkeys.json @@ -11,7 +11,8 @@ "properties": { "shortCut": {"type": "string"}, "description": {"type": "string"}, - "command": {"type": "string"} + "command": {"type": "string"}, + "keepHistory": {"type": "boolean"} } } } diff --git a/internal/config/views.go b/internal/config/views.go index 876156463e..1bab44c80d 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -48,6 +48,9 @@ func (v *CustomView) Reset() { // Load loads view configurations. func (v *CustomView) Load(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } bb, err := os.ReadFile(path) if err != nil { return err diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2e727e153c..0c9f329f69 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.0' +version: 'v0.31.1' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 65100b05d98d290a34359b1573723baa620c71bd Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 9 Jan 2024 11:34:46 -0700 Subject: [PATCH 072/169] K9s/release v0.31.2 (#2452) * [Bug] Fix #2428 * fix #2446 * fix #2449 * schemas updates * Add debug info * v0.31.2 rel notes --- Makefile | 2 +- change_logs/release_v0.31.2.md | 51 +++++++++++++++++++++++++++ internal/client/config.go | 2 +- internal/config/config.go | 8 ++--- internal/config/config_test.go | 7 ++-- internal/config/data/dir.go | 2 +- internal/config/json/schemas/k9s.json | 18 ++++++++-- internal/config/k9s.go | 13 +++++-- internal/view/app.go | 6 ++-- internal/view/browser.go | 7 ---- internal/view/command.go | 2 +- internal/view/context.go | 2 +- internal/view/table.go | 8 +++++ snap/snapcraft.yaml | 2 +- 14 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 change_logs/release_v0.31.2.md diff --git a/Makefile b/Makefile index 143ff1238c..74fffb2683 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.1 +VERSION ?= v0.31.2 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.2.md b/change_logs/release_v0.31.2.md new file mode 100644 index 0000000000..68734f5298 --- /dev/null +++ b/change_logs/release_v0.31.2.md @@ -0,0 +1,51 @@ + + +# Release v0.31.2 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Yikes! The aftermath... + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` doesn't really help us zero in. Everyone has slightly different settings/platforms so every little bits of info helps with the resolves. +Thank you!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2449](https://github.com/derailed/k9s/issues/2449) [Bug]: views.yaml columns not respected on startup +* [#2448](https://github.com/derailed/k9s/issues/2448) Missing '.thresholds' in config.yaml result in 'assignment to entry in nil map' +* [#2446](https://github.com/derailed/k9s/issues/2446) Context Switch unreliable/not working + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/client/config.go b/internal/client/config.go index 66990488c9..0a3253e5fa 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -118,7 +118,7 @@ func (c *Config) CurrentContextName() (string, error) { } cfg, err := c.RawConfig() if err != nil { - return "", err + return "", fmt.Errorf("fail to load rawConfig: %w", err) } return cfg.CurrentContext, nil diff --git a/internal/config/config.go b/internal/config/config.go index 89adde3266..ea78be80df 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -68,16 +68,16 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } if isStringSet(flags.Context) { if _, err := c.K9s.ActivateContext(*flags.Context); err != nil { - return err + return fmt.Errorf("k8sflags. unable to activate context %q: %w", *flags.Context, err) } } else { n, err := cfg.CurrentContextName() if err != nil { - return err + return fmt.Errorf("unable to retrieve kubeconfig current context %q: %w", n, err) } _, err = c.K9s.ActivateContext(n) if err != nil { - return err + return fmt.Errorf("unable to activate context %q: %w", *flags.Context, err) } } log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) @@ -220,7 +220,7 @@ func (c *Config) Load(path string) error { var cfg Config if err := yaml.Unmarshal(bb, &cfg); err != nil { - return err + return fmt.Errorf("main config yaml load failed: %w", err) } c.Merge(&cfg) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 21bdd56400..509bd5af5f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,7 +4,6 @@ package config_test import ( - "errors" "fmt" "os" "path/filepath" @@ -407,7 +406,7 @@ func TestConfigRefine(t *testing.T) { uu := map[string]struct { flags *genericclioptions.ConfigFlags k9sFlags *config.Flags - err error + err string context string cluster string namespace string @@ -488,7 +487,7 @@ func TestConfigRefine(t *testing.T) { KubeConfig: &cfgFile, Context: &ns1, }, - err: errors.New(`no context found for: "ns-1"`), + err: `k8sflags. unable to activate context "ns-1": no context found for: "ns-1"`, }, "use-current-context": { flags: &genericclioptions.ConfigFlags{ @@ -507,7 +506,7 @@ func TestConfigRefine(t *testing.T) { err := cfg.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags)) if err != nil { - assert.Equal(t, u.err, err) + assert.Equal(t, u.err, err.Error()) } else { assert.Nil(t, err) assert.Equal(t, u.context, cfg.K9s.ActiveContextName()) diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index 8e6c8b17b1..0eb3f5f407 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -68,7 +68,7 @@ func (d *Dir) loadConfig(path string) (*Config, error) { var cfg Config if err := yaml.Unmarshal(bb, &cfg); err != nil { - return nil, err + return nil, fmt.Errorf("context-config yaml load failed: %w\n%s", err, string(bb)) } return &cfg, nil diff --git a/internal/config/json/schemas/k9s.json b/internal/config/json/schemas/k9s.json index 42d2cb394e..6b17e24f72 100644 --- a/internal/config/json/schemas/k9s.json +++ b/internal/config/json/schemas/k9s.json @@ -30,9 +30,17 @@ }, "shellPod": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, "properties": { "image": { "type": "string" }, + "command": { + "type": "array", + "items": { "type": "string"} + }, + "args": { + "type": "array", + "items": { "type": "string"} + }, "namespace": { "type": "string" }, "limits": { "type": "object", @@ -41,7 +49,13 @@ "memory": { "type": "string" } }, "required": ["cpu", "memory"] - } + }, + "labels": { + "type": "object", + "additionalProperties": { "type": "string" }, + "required": [] + }, + "tty": { "type": "boolean" } }, "required": ["image", "namespace", "limits"] }, diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 09cee75302..95f52e1bdf 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" + "github.com/rs/zerolog/log" ) // K9s tracks K9s configuration options. @@ -65,10 +66,11 @@ func (k *K9s) resetConnection(conn client.Connection) { k.conn = conn } -// Save saves the k9s config to dis. +// Save saves the k9s config to disk. func (k *K9s) Save() error { if k.activeConfig == nil { - return fmt.Errorf("save failed. no active config detected") + log.Warn().Msgf("Save failed. no active config detected") + return nil } path := filepath.Join( AppContextsDir, @@ -97,7 +99,9 @@ func (k *K9s) Merge(k1 *K9s) { k.ShellPod = k1.ShellPod k.Logger = k1.Logger k.ImageScans = k1.ImageScans - k.Thresholds = k1.Thresholds + if k1.Thresholds != nil { + k.Thresholds = k1.Thresholds + } } // AppScreenDumpDir fetch screen dumps dir. @@ -193,6 +197,9 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { if k.activeConfig.Context == nil { return nil, fmt.Errorf("context activation failed for: %s", n) } + if k.activeConfig.Context == nil { + return nil, fmt.Errorf("context activation failed for: %s", n) + } return k.activeConfig.Context, nil } diff --git a/internal/view/app.go b/internal/view/app.go index 4c4c1959de..52e5bee70d 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -453,10 +453,12 @@ func (a *App) isValidNS(ns string) (bool, error) { return true, nil } -func (a *App) switchContext(ci *cmd.Interpreter) error { +func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { name, ok := ci.HasContext() if !ok || a.Config.ActiveContextName() == name { - return nil + if !force { + return nil + } } a.Halt() diff --git a/internal/view/browser.go b/internal/view/browser.go index 9589e99b36..36503bee51 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -249,13 +249,6 @@ func (b *Browser) TableDataChanged(data *render.TableData) { b.app.QueueUpdateDraw(func() { b.refreshActions() - if !b.app.Config.K9s.UI.Reactive { - if err := b.app.RefreshCustomViews(); err != nil { - log.Warn().Err(err).Msg("CustomViews load failed") - b.app.Logo().Warn("Views load failed!") - } - } - b.Update(data, b.app.Conn().HasMetrics()) }) } diff --git a/internal/view/command.go b/internal/view/command.go index 97676176ef..92ea9428d3 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -167,7 +167,7 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { return err } - if err := c.app.switchContext(p); err != nil { + if err := c.app.switchContext(p, false); err != nil { return err } } diff --git a/internal/view/context.go b/internal/view/context.go index 66d4c45e36..22266194f8 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -129,5 +129,5 @@ func useContext(app *App, name string) error { return err } - return app.switchContext(cmd.NewInterpreter("ctx " + name)) + return app.switchContext(cmd.NewInterpreter("ctx "+name), true) } diff --git a/internal/view/table.go b/internal/view/table.go index f1e28b901d..f0297d80ef 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -15,6 +15,7 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" + "github.com/rs/zerolog/log" ) // Table represents a table viewer. @@ -46,6 +47,13 @@ func (t *Table) Init(ctx context.Context) (err error) { ctx = context.WithValue(ctx, internal.KeyHasMetrics, t.app.Conn().HasMetrics()) } ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles) + if !t.app.Config.K9s.UI.Reactive { + if err := t.app.RefreshCustomViews(); err != nil { + log.Warn().Err(err).Msg("CustomViews load failed") + t.app.Logo().Warn("Views load failed!") + } + } + ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView) t.Table.Init(ctx) t.SetInputCapture(t.keyboard) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0c9f329f69..98655392e4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.1' +version: 'v0.31.2' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 268def283495d8aeb3a5bf8bc2e9b2d21fe230e5 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 10 Jan 2024 09:38:26 -0700 Subject: [PATCH 073/169] K9s/release v0.31.3 (#2460) * allow k9s to run with errors * fixes #2459 #2458 #2454 #2435 * v0.31.3 rel notes --- Makefile | 2 +- change_logs/release_v0.31.3.md | 52 ++++++++++++++++++++++++++++++++++ cmd/root.go | 2 +- internal/config/alias.go | 3 +- internal/config/config.go | 8 ++++-- internal/config/data/config.go | 8 ++++++ internal/config/k9s.go | 5 +--- snap/snapcraft.yaml | 2 +- 8 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 change_logs/release_v0.31.3.md diff --git a/Makefile b/Makefile index 74fffb2683..15447574fa 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.2 +VERSION ?= v0.31.3 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.3.md b/change_logs/release_v0.31.3.md new file mode 100644 index 0000000000..00a37b6751 --- /dev/null +++ b/change_logs/release_v0.31.3.md @@ -0,0 +1,52 @@ + + +# Release v0.31.3 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +The aftermath... + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` doesn't really help us zero in. Everyone has slightly different settings/platforms so every little bits of info helps with the resolves. +Thank you!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2459](https://github.com/derailed/k9s/issues/2459) No permission to see deployments/statefulsets even though I have them +* [#2458](https://github.com/derailed/k9s/issues/2458) panic on run without current context +* [#2454](https://github.com/derailed/k9s/issues/2454) Invoking K9s ends in panic question +* [#2435](https://github.com/derailed/k9s/issues/2435) "yaml: line 15: could not find expected ':'" error bug question (May be??) + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index c7412fa549..e0c208b71e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,7 +93,7 @@ func run(cmd *cobra.Command, args []string) error { cfg, err := loadConfiguration() if err != nil { - return err + log.Error().Err(err).Msgf("Fail to load global/context configuration") } app := view.NewApp(cfg) if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { diff --git a/internal/config/alias.go b/internal/config/alias.go index 798a0576c7..21cf26ba18 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -190,5 +190,6 @@ func (a *Aliases) SaveAliases(path string) error { if err != nil { return err } - return os.WriteFile(path, cfg, 0644) + + return os.WriteFile(path, cfg, data.DefaultFileMod) } diff --git a/internal/config/config.go b/internal/config/config.go index ea78be80df..c4d5109b02 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -77,7 +77,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c } _, err = c.K9s.ActivateContext(n) if err != nil { - return fmt.Errorf("unable to activate context %q: %w", *flags.Context, err) + return fmt.Errorf("unable to activate context %q: %w", n, err) } } log.Debug().Msgf("Active Context %q", c.K9s.ActiveContextName()) @@ -149,6 +149,10 @@ func (c *Config) FavNamespaces() []string { // SetActiveNamespace set the active namespace in the current context. func (c *Config) SetActiveNamespace(ns string) error { + if ns == client.NotNamespaced { + log.Debug().Msgf("[SetNS] No namespace given. skipping!") + return nil + } ct, err := c.K9s.ActiveContext() if err != nil { return err @@ -251,7 +255,7 @@ func (c *Config) SaveFile(path string) error { return err } - return os.WriteFile(path, cfg, 0644) + return os.WriteFile(path, cfg, data.DefaultFileMod) } // Validate the configuration. diff --git a/internal/config/data/config.go b/internal/config/data/config.go index 234adc31d9..6b821bb103 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "sync" "github.com/derailed/k9s/internal/client" "gopkg.in/yaml.v2" @@ -16,6 +17,7 @@ import ( // Config tracks a context configuration. type Config struct { Context *Context `yaml:"k9s"` + mx sync.RWMutex } // NewConfig returns a new config. @@ -27,6 +29,9 @@ func NewConfig(ct *api.Context) *Config { // Validate ensures config is in norms. func (c *Config) Validate(conn client.Connection, ks KubeSettings) { + c.mx.Lock() + defer c.mx.Unlock() + if c.Context == nil { c.Context = NewContext() } @@ -42,6 +47,9 @@ func (c *Config) Dump(w io.Writer) { // Save saves the config to disk. func (c *Config) Save(path string) error { + c.mx.RLock() + defer c.mx.RUnlock() + if err := EnsureDirPath(path, DefaultDirMod); err != nil { return err } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 95f52e1bdf..21453ce412 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -191,15 +191,12 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) { // If the context specifies a namespace, use it! if ns := ct.Namespace; ns != client.BlankNamespace { k.activeConfig.Context.Namespace.Active = ns - } else { + } else if k.activeConfig.Context.Namespace.Active == "" { k.activeConfig.Context.Namespace.Active = client.DefaultNamespace } if k.activeConfig.Context == nil { return nil, fmt.Errorf("context activation failed for: %s", n) } - if k.activeConfig.Context == nil { - return nil, fmt.Errorf("context activation failed for: %s", n) - } return k.activeConfig.Context, nil } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 98655392e4..84565fe60b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.2' +version: 'v0.31.3' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 942e61a90fb0ab6411fc77063658670d014fd974 Mon Sep 17 00:00:00 2001 From: Zadkiel Aharonian Date: Wed, 10 Jan 2024 21:18:56 +0100 Subject: [PATCH 074/169] fix: add missing shellpod config in schema (#2451) Signed-off-by: GitHub --- internal/config/json/schemas/k9s.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/config/json/schemas/k9s.json b/internal/config/json/schemas/k9s.json index 6b17e24f72..7830fdfd1e 100644 --- a/internal/config/json/schemas/k9s.json +++ b/internal/config/json/schemas/k9s.json @@ -55,7 +55,17 @@ "additionalProperties": { "type": "string" }, "required": [] }, - "tty": { "type": "boolean" } + "tty": { "type": "boolean" }, + "imagePullPolicy": { "type": "string" }, + "imagePullSecrets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" } + } + } + } }, "required": ["image", "namespace", "limits"] }, @@ -120,4 +130,4 @@ } }, "required": ["k9s"] -} \ No newline at end of file +} From 356c56138e10af4e2ba5f297401dc6089811ce4c Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 10 Jan 2024 16:57:37 -0700 Subject: [PATCH 075/169] K9s/release v0.31.4 (#2464) * [Bug] Fix scale dialog ui * [bug] Fix #2463 * v0.31.4 release notes --- Makefile | 2 +- change_logs/release_v0.31.4.md | 50 +++++++++++++++++++++++++++++++++ cmd/root.go | 10 +++---- go.mod | 2 +- internal/ui/dialog/confirm.go | 8 ++---- internal/view/command.go | 1 - internal/view/scale_extender.go | 22 ++++++++++----- snap/snapcraft.yaml | 2 +- 8 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 change_logs/release_v0.31.4.md diff --git a/Makefile b/Makefile index 15447574fa..cd6a6b6921 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.3 +VERSION ?= v0.31.4 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.4.md b/change_logs/release_v0.31.4.md new file mode 100644 index 0000000000..6265524911 --- /dev/null +++ b/change_logs/release_v0.31.4.md @@ -0,0 +1,50 @@ + + +# Release v0.31.4 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +More aftermath... + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` or `me to!` doesn't really help us zero in. +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves. +Thank you!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2463](https://github.com/derailed/k9s/issues/2463) v0.31.3 (Linux_amd64) gives runtime error on startup + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/root.go b/cmd/root.go index e0c208b71e..883f5de628 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -115,12 +115,12 @@ func loadConfiguration() (*config.Config, error) { k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) if err := k9sCfg.Load(config.AppConfigFile); err != nil { - return nil, err + return k9sCfg, err } k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("config refine failed") - return nil, err + return k9sCfg, err } conn, err := client.InitConnection(k8sCfg) k9sCfg.SetConnection(conn) @@ -129,16 +129,16 @@ func loadConfiguration() (*config.Config, error) { } // Try to access server version if that fail. Connectivity issue? if !conn.CheckConnectivity() { - return nil, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) + return k9sCfg, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) } if !conn.ConnectionOK() { - return nil, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) + return k9sCfg, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) } log.Info().Msg("✅ Kubernetes connectivity") if err := k9sCfg.Save(); err != nil { log.Error().Err(err).Msg("Config save") - return nil, err + return k9sCfg, err } return k9sCfg, nil diff --git a/go.mod b/go.mod index 4ca83ec743..759b0632b3 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/adrg/xdg v0.4.0 github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc github.com/anchore/grype v0.74.0 + github.com/anchore/syft v0.100.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.2 @@ -71,7 +72,6 @@ require ( github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 // indirect - github.com/anchore/syft v0.100.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect diff --git a/internal/ui/dialog/confirm.go b/internal/ui/dialog/confirm.go index 6ddcc9d565..461456cb4e 100644 --- a/internal/ui/dialog/confirm.go +++ b/internal/ui/dialog/confirm.go @@ -85,12 +85,10 @@ func ShowConfirm(styles config.Dialog, pages *ui.Pages, title, msg string, ack c cancel() }) for i := 0; i < 2; i++ { - b := f.GetButton(i) - if b == nil { - continue + if b := f.GetButton(i); b != nil { + b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()) + b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color()) } - b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()) - b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color()) } f.SetFocus(0) modal := tview.NewModalForm("<"+title+">", f) diff --git a/internal/view/command.go b/internal/view/command.go index 92ea9428d3..cdc311880b 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -166,7 +166,6 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { log.Error().Err(err).Msgf("Context switch failed") return err } - if err := c.app.switchContext(p, false); err != nil { return err } diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index 14094dcfe4..c30e289c3e 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -9,6 +9,8 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" @@ -83,7 +85,8 @@ func (s *ScaleExtender) valueOf(col string) (string, error) { } func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) { - f := s.makeStyledForm() + styles := s.App().Styles.Dialog() + f := s.makeStyledForm(styles) factor := "0" if len(sels) == 1 { @@ -126,10 +129,15 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) { s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), sels[0]) } }) - f.AddButton("Cancel", func() { s.dismissDialog() }) + for i := 0; i < 2; i++ { + if b := f.GetButton(i); b != nil { + b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()) + b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color()) + } + } return f, nil } @@ -138,14 +146,14 @@ func (s *ScaleExtender) dismissDialog() { s.App().Content.RemovePage(scaleDialogKey) } -func (s *ScaleExtender) makeStyledForm() *tview.Form { +func (s *ScaleExtender) makeStyledForm(styles config.Dialog) *tview.Form { f := tview.NewForm() f.SetItemPadding(0) f.SetButtonsAlign(tview.AlignCenter). - SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). - SetButtonTextColor(tview.Styles.PrimaryTextColor). - SetLabelColor(tcell.ColorAqua). - SetFieldTextColor(tcell.ColorOrange) + SetButtonBackgroundColor(styles.ButtonBgColor.Color()). + SetButtonTextColor(styles.ButtonBgColor.Color()). + SetLabelColor(styles.LabelFgColor.Color()). + SetFieldTextColor(styles.FieldFgColor.Color()) return f } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 84565fe60b..00a04f5675 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.3' +version: 'v0.31.4' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From ff17fff2861c02d325fc95d536d3787b83cb3782 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Fri, 12 Jan 2024 14:59:38 -0700 Subject: [PATCH 076/169] K9s/release v0.31.5 (#2469) * fix #2466 * fix #2465 * rel v0.31.5 --- Makefile | 2 +- change_logs/release_v0.31.5.md | 51 ++++++++++++++++++++++++++++++++++ cmd/root.go | 11 ++++---- go.mod | 2 +- go.sum | 4 +-- internal/config/config.go | 2 +- internal/config/data/types.go | 4 +-- internal/dao/types.go | 8 +++--- snap/snapcraft.yaml | 2 +- 9 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 change_logs/release_v0.31.5.md diff --git a/Makefile b/Makefile index cd6a6b6921..e9e7f73a7b 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.4 +VERSION ?= v0.31.5 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.5.md b/change_logs/release_v0.31.5.md new file mode 100644 index 0000000000..f73584c8d0 --- /dev/null +++ b/change_logs/release_v0.31.5.md @@ -0,0 +1,51 @@ + + +# Release v0.31.5 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +😱 More aftermath... 😱 + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;( +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant. +Thank you!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2466](https://github.com/derailed/k9s/issues/2466) Panic: index out of range [0] with length 0 +* [#2465](https://github.com/derailed/k9s/issues/2465) v0.31.4 - panic; no client connection detected - with feelings!! + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 883f5de628..2beadb4f79 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -114,6 +114,12 @@ func loadConfiguration() (*config.Config, error) { k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) + conn, err := client.InitConnection(k8sCfg) + k9sCfg.SetConnection(conn) + if err != nil { + return k9sCfg, err + } + if err := k9sCfg.Load(config.AppConfigFile); err != nil { return k9sCfg, err } @@ -122,11 +128,6 @@ func loadConfiguration() (*config.Config, error) { log.Error().Err(err).Msgf("config refine failed") return k9sCfg, err } - conn, err := client.InitConnection(k8sCfg) - k9sCfg.SetConnection(conn) - if err != nil { - return k9sCfg, err - } // Try to access server version if that fail. Connectivity issue? if !conn.CheckConnectivity() { return k9sCfg, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) diff --git a/go.mod b/go.mod index 759b0632b3..747bf6ee74 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/derailed/popeye v0.11.2 github.com/derailed/tcell/v2 v2.3.1-rc.3 - github.com/derailed/tview v0.8.2 + github.com/derailed/tview v0.8.3 github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/fvbommel/sortorder v1.1.0 diff --git a/go.sum b/go.sum index 311b078894..c1b9add36f 100644 --- a/go.sum +++ b/go.sum @@ -389,8 +389,8 @@ github.com/derailed/popeye v0.11.2 h1:8MKMjYBJdYNktTKeh98TeT127jZY6CFAsurrENoTZC github.com/derailed/popeye v0.11.2/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= -github.com/derailed/tview v0.8.2 h1:8b+QwVECV1lZ6VV7Vf1tergpJxJ+ReA/JhIBYyUVSFI= -github.com/derailed/tview v0.8.2/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw= +github.com/derailed/tview v0.8.3 h1:jhN7LW7pfCWf7Z6VC5Dpi/1usavOBZxz2mY90//TMsU= +github.com/derailed/tview v0.8.3/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9shgP+Cw= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= diff --git a/internal/config/config.go b/internal/config/config.go index c4d5109b02..a3fa3bef28 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,7 +30,7 @@ func NewConfig(ks data.KubeSettings) *Config { } } -// ContextHotKeysPath returns a context specific hotkeys file spec. +// ContextHotkeysPath returns a context specific hotkeys file spec. func (c *Config) ContextHotkeysPath() string { ct, err := c.K9s.ActiveContext() if err != nil { diff --git a/internal/config/data/types.go b/internal/config/data/types.go index 5d7c021440..4981be381e 100644 --- a/internal/config/data/types.go +++ b/internal/config/data/types.go @@ -32,10 +32,10 @@ type KubeSettings interface { // CurrentClusterName returns the name of the current cluster. CurrentClusterName() (string, error) - // CurrentNamespace returns the name of the current namespace. + // CurrentNamespaceName returns the name of the current namespace. CurrentNamespaceName() (string, error) - // ContextNames() returns all available context names. + // ContextNames returns all available context names. ContextNames() (map[string]struct{}, error) // CurrentContext returns the current context configuration. diff --git a/internal/dao/types.go b/internal/dao/types.go index d51d25e81c..da6fc7ff53 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -47,7 +47,7 @@ type Factory interface { // DeleteForwarder deletes a pod forwarder. DeleteForwarder(path string) - // Forwards returns all portforwards. + // Forwarders returns all portforwards. Forwarders() watch.Forwarders } @@ -101,7 +101,7 @@ type NodeMaintainer interface { // Loggable represents resources with logs. type Loggable interface { - // TaiLogs streams resource logs. + // TailLogs streams resource logs. TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) } @@ -158,10 +158,10 @@ type Logger interface { // ContainsPodSpec represents a resource with a pod template. type ContainsPodSpec interface { - // Get PodSpec of a resource + // GetPodSpec returns a podspec for the resource. GetPodSpec(path string) (*v1.PodSpec, error) - // Set Images for a resource + // SetImages sets container image. SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 00a04f5675..e9caf609e9 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.4' +version: 'v0.31.5' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 22db95e6e49e5311c4357b1cb6cc3ccadc3826b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:17:31 -0700 Subject: [PATCH 077/169] Bump k8s.io/klog/v2 from 2.110.1 to 2.120.0 (#2474) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.110.1 to 2.120.0. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.110.1...v2.120.0) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 747bf6ee74..67cccb8701 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( k8s.io/apimachinery v0.29.0 k8s.io/cli-runtime v0.29.0 k8s.io/client-go v0.29.0 - k8s.io/klog/v2 v2.110.1 + k8s.io/klog/v2 v2.120.0 k8s.io/kubectl v0.29.0 k8s.io/metrics v0.29.0 sigs.k8s.io/yaml v1.4.0 @@ -129,7 +129,7 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.11.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect diff --git a/go.sum b/go.sum index c1b9add36f..14fd97e547 100644 --- a/go.sum +++ b/go.sum @@ -510,8 +510,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -1862,8 +1862,8 @@ k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= +k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= From 66cb682aab9630f6e2e7b7e96993c550da1fcb3e Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Wed, 17 Jan 2024 08:26:00 +0800 Subject: [PATCH 078/169] shell autocomplete for k8s flags (#2477) --- cmd/root.go | 65 ++++++++++++++++++++++++++++++++++++++- internal/client/client.go | 2 +- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2beadb4f79..ea4d15cc6c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,11 +4,14 @@ package cmd import ( + "errors" "fmt" "os" "runtime/debug" + "strings" "github.com/derailed/k9s/internal/config/data" + "k8s.io/client-go/tools/clientcmd/api" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" @@ -44,11 +47,19 @@ var ( out = colorable.NewColorableStdout() ) +type FlagError struct{ err error } + +func (e *FlagError) Error() string { return e.err.Error() } + func init() { if err := config.InitLogLoc(); err != nil { fmt.Printf("Fail to init k9s logs location %s\n", err) } + rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error { + return &FlagError{err: err} + }) + rootCmd.AddCommand(versionCmd(), infoCmd()) initK9sFlags() initK8sFlags() @@ -57,7 +68,10 @@ func init() { // Execute root command. func Execute() { if err := rootCmd.Execute(); err != nil { - panic(err) + var flagError *FlagError + if !errors.As(err, &flagError) { + panic(err) + } } } @@ -281,6 +295,7 @@ func initK8sFlags() { initAsFlags() initCertFlags() + initK8sFlagCompletion() } func initAsFlags() { @@ -335,3 +350,51 @@ func initCertFlags() { "Bearer token for authentication to the API server", ) } + +func initK8sFlagCompletion() { + _ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.Context { + return cfg.Contexts + })) + + _ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.Cluster { + return cfg.Clusters + })) + + _ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.AuthInfo { + return cfg.AuthInfos + })) + + _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + conn, err := client.InitConnection(client.NewConfig(k8sFlags)) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + nss, err := conn.ValidNamespaceNames() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + return filterFlagCompletions(nss, toComplete) + }) +} + +func k8sFlagCompletionFunc[T any](picker func(cfg *api.Config) map[string]T) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + k8sCfg, err := client.NewConfig(k8sFlags).RawConfig() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + return filterFlagCompletions(picker(&k8sCfg), toComplete) + } +} + +func filterFlagCompletions[T any](m map[string]T, toComplete string) ([]string, cobra.ShellCompDirective) { + var completions []string + for name := range m { + if strings.HasPrefix(name, toComplete) { + completions = append(completions, name) + } + } + return completions, cobra.ShellCompDirectiveNoFileComp +} diff --git a/internal/client/client.go b/internal/client/client.go index 02b1e6aa56..76dace3118 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -71,7 +71,7 @@ func InitConnection(config *Config) (*APIClient, error) { if err != nil { log.Error().Err(err).Msgf("Fail to locate metrics-server") } - if errors.Is(err, noMetricServerErr) || errors.Is(err, metricsUnsupportedErr) { + if err == nil || errors.Is(err, noMetricServerErr) || errors.Is(err, metricsUnsupportedErr) { return &a, nil } a.connOK = false From 5445ff4da12a1da16a8fcce868db34947ed1c0f4 Mon Sep 17 00:00:00 2001 From: Guanchzhou Date: Wed, 17 Jan 2024 16:36:25 +0200 Subject: [PATCH 079/169] Adding system arch to nodes view (#2480) * adding arch to nodes view * hiding arch under wide view and fix test * fixing tests --------- Co-authored-by: Andrei Maltsev --- internal/render/node.go | 2 ++ internal/render/node_test.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/render/node.go b/internal/render/node.go index 17c80d947f..96b935811b 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -35,6 +35,7 @@ func (Node) Header(_ string) Header { HeaderColumn{Name: "NAME"}, HeaderColumn{Name: "STATUS"}, HeaderColumn{Name: "ROLE"}, + HeaderColumn{Name: "ARCH", Wide: true}, HeaderColumn{Name: "TAINTS"}, HeaderColumn{Name: "VERSION"}, HeaderColumn{Name: "KERNEL", Wide: true}, @@ -90,6 +91,7 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { no.Name, join(statuses, ","), join(roles, ","), + no.Status.NodeInfo.Architecture, strconv.Itoa(len(no.Spec.Taints)), no.Status.NodeInfo.KubeletVersion, no.Status.NodeInfo.KernelVersion, diff --git a/internal/render/node_test.go b/internal/render/node_test.go index 1b54ba919f..a276f06b72 100644 --- a/internal/render/node_test.go +++ b/internal/render/node_test.go @@ -24,8 +24,8 @@ func TestNodeRender(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "minikube", r.ID) - e := render.Fields{"minikube", "Ready", "master", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} - assert.Equal(t, e, r.Fields[:15]) + e := render.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} + assert.Equal(t, e, r.Fields[:16]) } func BenchmarkNodeRender(b *testing.B) { From a543f47319c0bdff16200a7ac40336b95ddfc823 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 17 Jan 2024 23:52:48 -0700 Subject: [PATCH 080/169] K9s/release v0.31.6 (#2487) * [Bug] Fix #2476 * rel v0.31.6 --- Makefile | 2 +- change_logs/release_v0.31.6.md | 75 +++++++++++++ cmd/root.go | 76 +++++++------ internal/client/client.go | 195 +++++++++++++++++++++++---------- internal/config/config.go | 10 +- internal/config/config_test.go | 87 --------------- internal/config/data/config.go | 2 - internal/config/data/ns.go | 20 +++- internal/config/k9s.go | 94 +++++++++------- internal/dao/container.go | 2 +- internal/dao/context.go | 4 +- internal/dao/crd.go | 2 +- internal/dao/cronjob.go | 8 +- internal/dao/dp.go | 6 +- internal/dao/ds.go | 8 +- internal/dao/generic.go | 2 +- internal/dao/job.go | 8 +- internal/dao/node.go | 8 +- internal/dao/non_resource.go | 11 +- internal/dao/pod.go | 8 +- internal/dao/popeye.go | 2 +- internal/dao/port_forward.go | 4 +- internal/dao/rbac.go | 14 +-- internal/dao/rbac_policy.go | 4 +- internal/dao/resource.go | 4 +- internal/dao/sts.go | 6 +- internal/dao/svc.go | 2 +- internal/model/stack.go | 2 + internal/model/table.go | 18 ++- internal/ui/config.go | 8 +- internal/ui/config_test.go | 1 + internal/ui/indicator.go | 2 +- internal/ui/logo.go | 10 +- internal/ui/prompt.go | 24 +++- internal/view/ns.go | 3 + snap/snapcraft.yaml | 2 +- 36 files changed, 442 insertions(+), 292 deletions(-) create mode 100644 change_logs/release_v0.31.6.md diff --git a/Makefile b/Makefile index e9e7f73a7b..d64fd545fc 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.5 +VERSION ?= v0.31.6 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.6.md b/change_logs/release_v0.31.6.md new file mode 100644 index 0000000000..890e6aca5a --- /dev/null +++ b/change_logs/release_v0.31.6.md @@ -0,0 +1,75 @@ + + +# Release v0.31.6 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +😱 More aftermath... 😱 + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;( +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant. + +--- + +## NOTE + +In this drop, we've made k9s a bit more resilient (hopefully!) to configuration issues and in most cases k9s will come up but may exhibit `limp mode` behaviors. +Please double check your k9s logs if things don't work as expected and file an issue with the `gory` details! + +☢️ This drop may cause `some disturbance in the farce!` ☢️ + +Please proceed with caution with this one as we did our best to attempt to address potential context config file corruption by eliminating race conditions. +It's late and I am operating on minimal sleep so I may have hosed some behaviors 🫣 +If you experience k9s locking up or misbehaving, as per the above👆 you know what to do now and as customary +we will do our best to address them quickly to get you back up and running! + +Thank you for your support, kindness and patience! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2476](https://github.com/derailed/k9s/issues/2476) Pod's are not displayed for the selected namespace. Hopefully! +* [#2471](https://github.com/derailed/k9s/issues/2471) Shell autocomplete functions do not work correctly + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2480](https://github.com/derailed/k9s/pull/2480) Adding system arch to nodes view +* [#2477](https://github.com/derailed/k9s/pull/2477) Shell autocomplete for k8s flags + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index ea4d15cc6c..db029ab92b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -47,9 +47,9 @@ var ( out = colorable.NewColorableStdout() ) -type FlagError struct{ err error } +type flagError struct{ err error } -func (e *FlagError) Error() string { return e.err.Error() } +func (e flagError) Error() string { return e.err.Error() } func init() { if err := config.InitLogLoc(); err != nil { @@ -57,7 +57,7 @@ func init() { } rootCmd.SetFlagErrorFunc(func(command *cobra.Command, err error) error { - return &FlagError{err: err} + return flagError{err: err} }) rootCmd.AddCommand(versionCmd(), infoCmd()) @@ -68,8 +68,7 @@ func init() { // Execute root command. func Execute() { if err := rootCmd.Execute(); err != nil { - var flagError *FlagError - if !errors.As(err, &flagError) { + if !errors.As(err, &flagError{}) { panic(err) } } @@ -128,35 +127,36 @@ func loadConfiguration() (*config.Config, error) { k8sCfg := client.NewConfig(k8sFlags) k9sCfg := config.NewConfig(k8sCfg) + var errs error conn, err := client.InitConnection(k8sCfg) k9sCfg.SetConnection(conn) if err != nil { - return k9sCfg, err + errs = errors.Join(errs, err) } if err := k9sCfg.Load(config.AppConfigFile); err != nil { - return k9sCfg, err + errs = errors.Join(errs, err) } k9sCfg.K9s.Override(k9sFlags) if err := k9sCfg.Refine(k8sFlags, k9sFlags, k8sCfg); err != nil { log.Error().Err(err).Msgf("config refine failed") - return k9sCfg, err + errs = errors.Join(errs, err) } // Try to access server version if that fail. Connectivity issue? if !conn.CheckConnectivity() { - return k9sCfg, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()) + errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName())) } if !conn.ConnectionOK() { - return k9sCfg, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()) + errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName())) } log.Info().Msg("✅ Kubernetes connectivity") if err := k9sCfg.Save(); err != nil { log.Error().Err(err).Msg("Config save") - return k9sCfg, err + errs = errors.Join(errs, err) } - return k9sCfg, nil + return k9sCfg, errs } func parseLevel(level string) zerolog.Level { @@ -351,50 +351,58 @@ func initCertFlags() { ) } +type ( + k8sPickerFn[T any] func(cfg *api.Config) map[string]T + completeFn func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) +) + func initK8sFlagCompletion() { - _ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.Context { + conn := client.NewConfig(k8sFlags) + cfg, err := conn.RawConfig() + if err != nil { + log.Error().Err(err).Msgf("k8s config getter failed") + } + + _ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.Context { return cfg.Contexts })) - _ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.Cluster { + _ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.Cluster { return cfg.Clusters })) - _ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletionFunc(func(cfg *api.Config) map[string]*api.AuthInfo { + _ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.AuthInfo { return cfg.AuthInfos })) - _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - conn, err := client.InitConnection(client.NewConfig(k8sFlags)) - if err != nil { - return nil, cobra.ShellCompDirectiveError + _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) { + if c, err := client.InitConnection(conn); err == nil { + if nss, err := c.ValidNamespaceNames(); err == nil { + return filterFlagCompletions(nss, s) + } } - nss, err := conn.ValidNamespaceNames() - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - return filterFlagCompletions(nss, toComplete) + return nil, cobra.ShellCompDirectiveError }) } -func k8sFlagCompletionFunc[T any](picker func(cfg *api.Config) map[string]T) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { +func k8sFlagCompletion[T any](cfg *api.Config, picker k8sPickerFn[T]) completeFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - k8sCfg, err := client.NewConfig(k8sFlags).RawConfig() - if err != nil { + if cfg == nil { return nil, cobra.ShellCompDirectiveError } - return filterFlagCompletions(picker(&k8sCfg), toComplete) + + return filterFlagCompletions(picker(cfg), toComplete) } } -func filterFlagCompletions[T any](m map[string]T, toComplete string) ([]string, cobra.ShellCompDirective) { - var completions []string +func filterFlagCompletions[T any](m map[string]T, s string) ([]string, cobra.ShellCompDirective) { + cc := make([]string, 0, len(m)) for name := range m { - if strings.HasPrefix(name, toComplete) { - completions = append(completions, name) + if strings.HasPrefix(name, s) { + cc = append(cc, name) } } - return completions, cobra.ShellCompDirectiveNoFileComp + + return cc, cobra.ShellCompDirectiveNoFileComp } diff --git a/internal/client/client.go b/internal/client/client.go index 76dace3118..981e5db9f6 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -46,7 +46,7 @@ type APIClient struct { mxsClient *versioned.Clientset cachedClient *disk.CachedDiscoveryClient config *Config - mx sync.Mutex + mx sync.RWMutex cache *cache.LRUExpireCache connOK bool } @@ -143,10 +143,7 @@ func (a *APIClient) clearCache() { // CanI checks if user has access to a certain resource. func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) { - a.mx.Lock() - defer a.mx.Unlock() - - if !a.connOK { + if !a.getConnOK() { return false, errors.New("ACCESS -- No API server connection") } if IsClusterWide(ns) { @@ -305,14 +302,11 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { // CheckConnectivity return true if api server is cool or false otherwise. func (a *APIClient) CheckConnectivity() bool { - a.mx.Lock() - defer a.mx.Unlock() - defer func() { if err := recover(); err != nil { - a.connOK = false + a.setConnOK(false) } - if !a.connOK { + if !a.getConnOK() { a.clearCache() } }() @@ -328,21 +322,21 @@ func (a *APIClient) CheckConnectivity() bool { client, err := kubernetes.NewForConfig(cfg) if err != nil { log.Error().Err(err).Msgf("Unable to connect to api server") - a.connOK = false - return a.connOK + a.setConnOK(false) + return a.getConnOK() } // Check connection if _, err := client.ServerVersion(); err == nil { - if !a.connOK { + if !a.getConnOK() { a.reset() } } else { log.Error().Err(err).Msgf("can't connect to cluster") - a.connOK = false + a.setConnOK(false) } - return a.connOK + return a.getConnOK() } // Config return a kubernetes configuration. @@ -355,13 +349,97 @@ func (a *APIClient) HasMetrics() bool { return a.supportsMetricsResources() == nil } +func (a *APIClient) getMxsClient() *versioned.Clientset { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.mxsClient +} + +func (a *APIClient) setMxsClient(c *versioned.Clientset) { + a.mx.Lock() + defer a.mx.Unlock() + + a.mxsClient = c +} + +func (a *APIClient) getCachedClient() *disk.CachedDiscoveryClient { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.cachedClient +} + +func (a *APIClient) setCachedClient(c *disk.CachedDiscoveryClient) { + a.mx.Lock() + defer a.mx.Unlock() + + a.cachedClient = c +} + +func (a *APIClient) getDClient() dynamic.Interface { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.dClient +} + +func (a *APIClient) setDClient(c dynamic.Interface) { + a.mx.Lock() + defer a.mx.Unlock() + + a.dClient = c +} + +func (a *APIClient) getConnOK() bool { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.connOK +} + +func (a *APIClient) setConnOK(b bool) { + a.mx.Lock() + defer a.mx.Unlock() + + a.connOK = b +} + +func (a *APIClient) setLogClient(k kubernetes.Interface) { + a.mx.Lock() + defer a.mx.Unlock() + + a.logClient = k +} + +func (a *APIClient) getLogClient() kubernetes.Interface { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.logClient +} + +func (a *APIClient) setClient(k kubernetes.Interface) { + a.mx.Lock() + defer a.mx.Unlock() + + a.client = k +} + +func (a *APIClient) getClient() kubernetes.Interface { + a.mx.RLock() + defer a.mx.RUnlock() + + return a.client +} + // DialLogs returns a handle to api server for logs. func (a *APIClient) DialLogs() (kubernetes.Interface, error) { - if !a.connOK { - return nil, errors.New("no connection to dial") + if !a.getConnOK() { + return nil, errors.New("dialLogs - no connection to dial") } - if a.logClient != nil { - return a.logClient, nil + if clt := a.getLogClient(); clt != nil { + return clt, nil } cfg, err := a.RestConfig() @@ -369,31 +447,35 @@ func (a *APIClient) DialLogs() (kubernetes.Interface, error) { return nil, err } cfg.Timeout = 0 - if a.logClient, err = kubernetes.NewForConfig(cfg); err != nil { + c, err := kubernetes.NewForConfig(cfg) + if err != nil { return nil, err } + a.setLogClient(c) - return a.logClient, nil + return a.getLogClient(), nil } // Dial returns a handle to api server or die. func (a *APIClient) Dial() (kubernetes.Interface, error) { - if !a.connOK { + if !a.getConnOK() { return nil, errors.New("no connection to dial") } - if a.client != nil { - return a.client, nil + if c := a.getClient(); c != nil { + return c, nil } cfg, err := a.RestConfig() if err != nil { return nil, err } - if a.client, err = kubernetes.NewForConfig(cfg); err != nil { + if c, err := kubernetes.NewForConfig(cfg); err != nil { return nil, err + } else { + a.setClient(c) } - return a.client, nil + return a.getClient(), nil } // RestConfig returns a rest api client. @@ -403,15 +485,12 @@ func (a *APIClient) RestConfig() (*restclient.Config, error) { // CachedDiscovery returns a cached discovery client. func (a *APIClient) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { - a.mx.Lock() - defer a.mx.Unlock() - - if !a.connOK { + if !a.getConnOK() { return nil, errors.New("no connection to cached dial") } - if a.cachedClient != nil { - return a.cachedClient, nil + if c := a.getCachedClient(); c != nil { + return c, nil } cfg, err := a.RestConfig() @@ -422,37 +501,38 @@ func (a *APIClient) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { httpCacheDir := filepath.Join(mustHomeDir(), ".kube", "http-cache") discCacheDir := filepath.Join(mustHomeDir(), ".kube", "cache", "discovery", toHostDir(cfg.Host)) - a.cachedClient, err = disk.NewCachedDiscoveryClientForConfig(cfg, discCacheDir, httpCacheDir, cacheExpiry) + c, err := disk.NewCachedDiscoveryClientForConfig(cfg, discCacheDir, httpCacheDir, cacheExpiry) if err != nil { return nil, err } - return a.cachedClient, nil + a.setCachedClient(c) + + return a.getCachedClient(), nil } // DynDial returns a handle to a dynamic interface. func (a *APIClient) DynDial() (dynamic.Interface, error) { - if a.dClient != nil { - return a.dClient, nil + if c := a.getDClient(); c != nil { + return c, nil } cfg, err := a.RestConfig() if err != nil { return nil, err } - if a.dClient, err = dynamic.NewForConfig(cfg); err != nil { - log.Panic().Err(err) + c, err := dynamic.NewForConfig(cfg) + if err != nil { + return nil, err } + a.setDClient(c) - return a.dClient, nil + return a.getDClient(), nil } // MXDial returns a handle to the metrics server. func (a *APIClient) MXDial() (*versioned.Clientset, error) { - a.mx.Lock() - defer a.mx.Unlock() - - if a.mxsClient != nil { - return a.mxsClient, nil + if c := a.getMxsClient(); c != nil { + return c, nil } cfg, err := a.RestConfig() @@ -460,11 +540,13 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) { return nil, err } - if a.mxsClient, err = versioned.NewForConfig(cfg); err != nil { - log.Error().Err(err) + c, err := versioned.NewForConfig(cfg) + if err != nil { + return nil, err } + a.setMxsClient(c) - return a.mxsClient, err + return a.getMxsClient(), err } // SwitchContext handles kubeconfig context switches. @@ -473,12 +555,8 @@ func (a *APIClient) SwitchContext(name string) error { if err := a.config.SwitchContext(name); err != nil { return err } - a.mx.Lock() - { - a.reset() - ResetMetrics() - } - a.mx.Unlock() + a.reset() + ResetMetrics() if !a.CheckConnectivity() { return fmt.Errorf("unable to connect to context %q", name) @@ -490,9 +568,14 @@ func (a *APIClient) SwitchContext(name string) error { func (a *APIClient) reset() { a.config.reset() a.cache = cache.NewLRUExpireCache(cacheSize) - a.client, a.dClient, a.nsClient, a.mxsClient = nil, nil, nil, nil - a.cachedClient, a.logClient = nil, nil - a.connOK = true + a.nsClient = nil + + a.setDClient(nil) + a.setMxsClient(nil) + a.setCachedClient(nil) + a.setClient(nil) + a.setLogClient(nil) + a.setConnOK(true) } func (a *APIClient) checkCacheBool(key string) (state bool, ok bool) { diff --git a/internal/config/config.go b/internal/config/config.go index a3fa3bef28..0390bf5e6a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ package config import ( + "errors" "fmt" "os" @@ -58,7 +59,7 @@ func (c *Config) ContextPluginsPath() string { return "" } - return AppContextPluginsFile(ct.ClusterName, c.K9s.activeContextName) + return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName) } // Refine the configuration based on cli args. @@ -218,17 +219,18 @@ func (c *Config) Load(path string) error { if err != nil { return err } + var errs error if err := data.JSONValidator.Validate(json.K9sSchema, bb); err != nil { - return fmt.Errorf("k9s config file %q load failed:\n%w", path, err) + errs = errors.Join(errs, fmt.Errorf("k9s config file %q load failed:\n%w", path, err)) } var cfg Config if err := yaml.Unmarshal(bb, &cfg); err != nil { - return fmt.Errorf("main config yaml load failed: %w", err) + errs = errors.Join(errs, fmt.Errorf("main config.yaml load failed: %w", err)) } c.Merge(&cfg) - return nil + return errs } // Save configuration to disk. diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 509bd5af5f..10c66cb670 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -583,90 +583,3 @@ func TestSetup(t *testing.T) { fmt.Println("Boom!", m, i) }) } - -// ---------------------------------------------------------------------------- -// Test Data... - -// var expectedConfig = `k9s: -// liveViewAutoRefresh: true -// screenDumpDir: /tmp/screen-dumps -// refreshRate: 100 -// maxConnRetry: 5 -// readOnly: true -// noExitOnCtrlC: false -// ui: -// enableMouse: false -// headless: false -// logoless: false -// crumbsless: false -// noIcons: false -// skipLatestRevCheck: false -// disablePodCounting: false -// shellPod: -// image: busybox:1.35.0 -// namespace: default -// limits: -// cpu: 100m -// memory: 100Mi -// imageScans: -// enable: false -// exclusions: -// namespaces: [] -// labels: {} -// logger: -// tail: 500 -// buffer: 800 -// sinceSeconds: -1 -// fullScreen: false -// textWrap: false -// showTime: false -// thresholds: -// cpu: -// critical: 90 -// warn: 70 -// memory: -// critical: 90 -// warn: 70 -// ` - -// var resetConfig = `k9s: -// liveViewAutoRefresh: true -// screenDumpDir: /tmp/screen-dumps -// refreshRate: 2 -// maxConnRetry: 5 -// readOnly: false -// noExitOnCtrlC: false -// ui: -// enableMouse: false -// headless: false -// logoless: false -// crumbsless: false -// noIcons: false -// skipLatestRevCheck: false -// disablePodCounting: false -// shellPod: -// image: busybox:1.35.0 -// namespace: default -// limits: -// cpu: 100m -// memory: 100Mi -// imageScans: -// enable: false -// exclusions: -// namespaces: [] -// labels: {} -// logger: -// tail: 200 -// buffer: 2000 -// sinceSeconds: -1 -// fullScreen: false -// textWrap: false -// showTime: false -// thresholds: -// cpu: -// critical: 90 -// warn: 70 -// memory: -// critical: 90 -// warn: 70 -// ` diff --git a/internal/config/data/config.go b/internal/config/data/config.go index 6b821bb103..f005571ad2 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -29,8 +29,6 @@ func NewConfig(ct *api.Context) *Config { // Validate ensures config is in norms. func (c *Config) Validate(conn client.Connection, ks KubeSettings) { - c.mx.Lock() - defer c.mx.Unlock() if c.Context == nil { c.Context = NewContext() diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index ba8aacae27..1926289460 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -4,6 +4,8 @@ package data import ( + "sync" + "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" ) @@ -18,6 +20,7 @@ type Namespace struct { Active string `yaml:"active"` LockFavorites bool `yaml:"lockFavorites"` Favorites []string `yaml:"favorites"` + mx sync.RWMutex } // NewNamespace create a new namespace configuration. @@ -37,6 +40,9 @@ func NewActiveNamespace(n string) *Namespace { // Validate validates a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection) { + n.mx.RLock() + defer n.mx.RUnlock() + if c == nil || !c.IsValidNamespace(n.Active) { return } @@ -50,14 +56,18 @@ func (n *Namespace) Validate(c client.Connection) { // SetActive set the active namespace. func (n *Namespace) SetActive(ns string, ks KubeSettings) error { - if ns == client.BlankNamespace { - ns = client.NamespaceAll - } if n == nil { n = NewActiveNamespace(ns) - } else { - n.Active = ns } + + n.mx.Lock() + defer n.mx.Unlock() + + if ns == client.BlankNamespace { + ns = client.NamespaceAll + } + n.Active = ns + if ns != "" && !n.LockFavorites { n.addFavNS(ns) } diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 21453ce412..f4eb69fe8d 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -68,17 +68,17 @@ func (k *K9s) resetConnection(conn client.Connection) { // Save saves the k9s config to disk. func (k *K9s) Save() error { - if k.activeConfig == nil { + if k.getActiveConfig() == nil { log.Warn().Msgf("Save failed. no active config detected") return nil } path := filepath.Join( AppContextsDir, - data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName), + data.SanitizeContextSubpath(k.activeConfig.Context.GetClusterName(), k.getActiveContextName()), data.MainConfigFile, ) - return k.activeConfig.Save(path) + return k.getActiveConfig().Save(path) } // Merge merges k9s configs. @@ -124,19 +124,20 @@ func (k *K9s) ContextScreenDumpDir() string { } func (k *K9s) contextPath() string { - if k.activeConfig == nil { + if k.getActiveConfig() == nil { return "na" } return data.SanitizeContextSubpath( - k.activeConfig.Context.ClusterName, + k.getActiveConfig().Context.GetClusterName(), k.ActiveContextName(), ) } // Reset resets configuration and context. func (k *K9s) Reset() { - k.activeConfig, k.activeContextName = nil, "" + k.setActiveConfig(nil) + k.setActiveContextName("") } // ActiveContextNamespace fetch the context active ns. @@ -151,23 +152,16 @@ func (k *K9s) ActiveContextNamespace() (string, error) { // ActiveContextName returns the active context name. func (k *K9s) ActiveContextName() string { - k.mx.RLock() - defer k.mx.RUnlock() - - return k.activeContextName + return k.getActiveContextName() } // ActiveContext returns the currently active context. func (k *K9s) ActiveContext() (*data.Context, error) { - var ac *data.Config - k.mx.RLock() - ac = k.activeConfig - k.mx.RUnlock() - if ac != nil && ac.Context != nil { - return ac.Context, nil + if cfg := k.getActiveConfig(); cfg != nil && cfg.Context != nil { + return cfg.Context, nil } - ct, err := k.ActivateContext(k.activeContextName) + ct, err := k.ActivateContext(k.getActiveContextName()) if err != nil { return nil, err } @@ -175,46 +169,75 @@ func (k *K9s) ActiveContext() (*data.Context, error) { return ct, nil } +func (k *K9s) setActiveConfig(c *data.Config) { + k.mx.Lock() + defer k.mx.Unlock() + + k.activeConfig = c +} + +func (k *K9s) getActiveConfig() *data.Config { + k.mx.RLock() + defer k.mx.RUnlock() + + return k.activeConfig +} + +func (k *K9s) setActiveContextName(n string) { + k.mx.Lock() + defer k.mx.Unlock() + + k.activeContextName = n +} + +func (k *K9s) getActiveContextName() string { + k.mx.RLock() + defer k.mx.RUnlock() + + return k.activeContextName +} + // ActivateContext initializes the active context if not present. func (k *K9s) ActivateContext(n string) (*data.Context, error) { - k.activeContextName = n + k.setActiveContextName(n) ct, err := k.ks.GetContext(n) if err != nil { return nil, err } - k.activeConfig, err = k.dir.Load(n, ct) + + cfg, err := k.dir.Load(n, ct) if err != nil { return nil, err } + k.setActiveConfig(cfg) k.Validate(k.conn, k.ks) // If the context specifies a namespace, use it! if ns := ct.Namespace; ns != client.BlankNamespace { - k.activeConfig.Context.Namespace.Active = ns + k.getActiveConfig().Context.Namespace.Active = ns } else if k.activeConfig.Context.Namespace.Active == "" { - k.activeConfig.Context.Namespace.Active = client.DefaultNamespace + k.getActiveConfig().Context.Namespace.Active = client.DefaultNamespace } - if k.activeConfig.Context == nil { + if k.getActiveConfig().Context == nil { return nil, fmt.Errorf("context activation failed for: %s", n) } - return k.activeConfig.Context, nil + return k.getActiveConfig().Context, nil } // Reload reloads the context config from disk. func (k *K9s) Reload() error { - k.mx.Lock() - defer k.mx.Unlock() - - ct, err := k.ks.GetContext(k.activeContextName) + ct, err := k.ks.GetContext(k.getActiveContextName()) if err != nil { return err } - k.activeConfig, err = k.dir.Load(k.activeContextName, ct) + + cfg, err := k.dir.Load(k.getActiveContextName(), ct) if err != nil { return err } - k.activeConfig.Validate(k.conn, k.ks) + k.setActiveConfig(cfg) + k.getActiveConfig().Validate(k.conn, k.ks) return nil } @@ -277,12 +300,9 @@ func (k *K9s) GetRefreshRate() int { // IsReadOnly returns the readonly setting. func (k *K9s) IsReadOnly() bool { - k.mx.RLock() - defer k.mx.RUnlock() - ro := k.ReadOnly - if k.activeConfig != nil && k.activeConfig.Context.ReadOnly != nil { - ro = *k.activeConfig.Context.ReadOnly + if cfg := k.getActiveConfig(); cfg != nil && cfg.Context.ReadOnly != nil { + ro = *cfg.Context.ReadOnly } if k.manualReadOnly != nil { ro = true @@ -300,7 +320,7 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.MaxConnRetry = defaultMaxConnRetry } - if k.activeConfig == nil { + if k.getActiveConfig() == nil { if n, err := ks.CurrentContextName(); err == nil { _, _ = k.ActivateContext(n) } @@ -309,7 +329,7 @@ func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) { k.Logger = k.Logger.Validate() k.Thresholds = k.Thresholds.Validate() - if k.activeConfig != nil { - k.activeConfig.Validate(c, ks) + if cfg := k.getActiveConfig(); cfg != nil { + cfg.Validate(c, ks) } } diff --git a/internal/dao/container.go b/internal/dao/container.go index f90d74c908..2074607f26 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -94,7 +94,7 @@ func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { } func (c *Container) fetchPod(fqn string) (*v1.Pod, error) { - o, err := c.GetFactory().Get("v1/pods", fqn, true, labels.Everything()) + o, err := c.getFactory().Get("v1/pods", fqn, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/context.go b/internal/dao/context.go index 222dbc6c32..671ed34f02 100644 --- a/internal/dao/context.go +++ b/internal/dao/context.go @@ -23,7 +23,7 @@ type Context struct { } func (c *Context) config() *client.Config { - return c.GetFactory().Client().Config() + return c.getFactory().Client().Config() } // Get a Context. @@ -60,5 +60,5 @@ func (c *Context) MustCurrentContextName() string { // Switch to another context. func (c *Context) Switch(ctx string) error { - return c.GetFactory().Client().SwitchContext(ctx) + return c.getFactory().Client().SwitchContext(ctx) } diff --git a/internal/dao/crd.go b/internal/dao/crd.go index fcf7f53b65..16f06076d7 100644 --- a/internal/dao/crd.go +++ b/internal/dao/crd.go @@ -44,5 +44,5 @@ func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtim } const gvr = "apiextensions.k8s.io/v1/customresourcedefinitions" - return c.GetFactory().List(gvr, "-", false, labelSel) + return c.getFactory().List(gvr, "-", false, labelSel) } diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 99ae897e2e..6feee0ca11 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -56,7 +56,7 @@ func (c *CronJob) Run(path string) error { return fmt.Errorf("user is not authorized to run jobs") } - o, err := c.GetFactory().Get(c.GVR(), path, true, labels.Everything()) + o, err := c.getFactory().Get(c.GVR(), path, true, labels.Everything()) if err != nil { return err } @@ -102,7 +102,7 @@ func (c *CronJob) Run(path string) error { // ScanSA scans for serviceaccount refs. func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := c.GetFactory().List(c.GVR(), ns, wait, labels.Everything()) + oo, err := c.getFactory().List(c.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -127,7 +127,7 @@ func (c *CronJob) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, erro // GetInstance fetch a matching cronjob. func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) { - o, err := c.GetFactory().Get(c.GVR(), fqn, true, labels.Everything()) + o, err := c.getFactory().Get(c.GVR(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -175,7 +175,7 @@ func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error { // Scan scans for cluster resource refs. func (c *CronJob) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := c.GetFactory().List(c.GVR(), ns, wait, labels.Everything()) + oo, err := c.getFactory().List(c.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 2eb5e56bbf..4ab1a4badb 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -81,7 +81,7 @@ func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) err // Restart a Deployment rollout. func (d *Deployment) Restart(ctx context.Context, path string) error { - o, err := d.GetFactory().Get("apps/v1/deployments", path, true, labels.Everything()) + o, err := d.getFactory().Get("apps/v1/deployments", path, true, labels.Everything()) if err != nil { return err } @@ -170,7 +170,7 @@ func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) { // ScanSA scans for serviceaccount refs. func (d *Deployment) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) + oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -196,7 +196,7 @@ func (d *Deployment) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, e // Scan scans for resource references. func (d *Deployment) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) + oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 0850a8ab3e..1488960762 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -58,7 +58,7 @@ func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool { // Restart a DaemonSet rollout. func (d *DaemonSet) Restart(ctx context.Context, path string) error { - o, err := d.GetFactory().Get("apps/v1/daemonsets", path, true, labels.Everything()) + o, err := d.getFactory().Get("apps/v1/daemonsets", path, true, labels.Everything()) if err != nil { return err } @@ -173,7 +173,7 @@ func (d *DaemonSet) Pod(fqn string) (string, error) { // GetInstance returns a daemonset instance. func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) { - o, err := d.GetFactory().Get(d.gvr.String(), fqn, true, labels.Everything()) + o, err := d.getFactory().Get(d.gvrStr(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -190,7 +190,7 @@ func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) { // ScanSA scans for serviceaccount refs. func (d *DaemonSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) + oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -216,7 +216,7 @@ func (d *DaemonSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, er // Scan scans for cluster refs. func (d *DaemonSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := d.GetFactory().List(d.GVR(), ns, wait, labels.Everything()) + oo, err := d.getFactory().List(d.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/generic.go b/internal/dao/generic.go index 489c3f835c..cf579cf7e8 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -106,7 +106,7 @@ func (g *Generic) ToYAML(path string, showManaged bool) (string, error) { // Delete deletes a resource. func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error { ns, n := client.Namespaced(path) - auth, err := g.Client().CanI(ns, g.gvr.String(), []string{client.DeleteVerb}) + auth, err := g.Client().CanI(ns, g.gvrStr(), []string{client.DeleteVerb}) if err != nil { return err } diff --git a/internal/dao/job.go b/internal/dao/job.go index 1444929a34..286cde59be 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -73,7 +73,7 @@ func (j *Job) List(ctx context.Context, ns string) ([]runtime.Object, error) { // TailLogs tail logs for all pods represented by this Job. func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) { - o, err := j.GetFactory().Get(j.gvr.String(), opts.Path, true, labels.Everything()) + o, err := j.getFactory().Get(j.gvrStr(), opts.Path, true, labels.Everything()) if err != nil { return nil, err } @@ -92,7 +92,7 @@ func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) } func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) { - o, err := j.GetFactory().Get(j.gvr.String(), fqn, true, labels.Everything()) + o, err := j.getFactory().Get(j.gvrStr(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -109,7 +109,7 @@ func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) { // ScanSA scans for serviceaccount refs. func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := j.GetFactory().List(j.GVR(), ns, wait, labels.Everything()) + oo, err := j.getFactory().List(j.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -135,7 +135,7 @@ func (j *Job) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { // Scan scans for resource references. func (j *Job) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := j.GetFactory().List(j.GVR(), ns, wait, labels.Everything()) + oo, err := j.getFactory().List(j.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/node.go b/internal/dao/node.go index db0c8fffef..da5c8ddfba 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -56,7 +56,7 @@ func (n *Node) ToggleCordon(path string, cordon bool) error { } return fmt.Errorf("node is already uncordoned") } - dial, err := n.GetFactory().Client().Dial() + dial, err := n.getFactory().Client().Dial() if err != nil { return err } @@ -98,7 +98,7 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error { } } - dial, err := n.GetFactory().Client().Dial() + dial, err := n.getFactory().Client().Dial() if err != nil { return err } @@ -189,7 +189,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) { // CountPods counts the pods scheduled on a given node. func (n *Node) CountPods(nodeName string) (int, error) { var count int - oo, err := n.GetFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) + oo, err := n.getFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) if err != nil { return 0, err } @@ -213,7 +213,7 @@ func (n *Node) CountPods(nodeName string) (int, error) { // GetPods returns all pods running on given node. func (n *Node) GetPods(nodeName string) ([]*v1.Pod, error) { - oo, err := n.GetFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) + oo, err := n.getFactory().List("v1/pods", client.BlankNamespace, false, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/non_resource.go b/internal/dao/non_resource.go index c32728d3da..9840532ec4 100644 --- a/internal/dao/non_resource.go +++ b/internal/dao/non_resource.go @@ -29,7 +29,14 @@ func (n *NonResource) Init(f Factory, gvr client.GVR) { n.mx.Unlock() } -func (n *NonResource) GetFactory() Factory { +func (n *NonResource) gvrStr() string { + n.mx.RLock() + defer n.mx.RUnlock() + + return n.gvr.String() +} + +func (n *NonResource) getFactory() Factory { n.mx.RLock() defer n.mx.RUnlock() @@ -41,7 +48,7 @@ func (n *NonResource) GVR() string { n.mx.RLock() defer n.mx.RUnlock() - return n.gvr.String() + return n.gvrStr() } // Get returns the given resource. diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 850454499c..1f34b60cef 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -177,7 +177,7 @@ func (p *Pod) Pod(fqn string) (string, error) { // GetInstance returns a pod instance. func (p *Pod) GetInstance(fqn string) (*v1.Pod, error) { - o, err := p.GetFactory().Get(p.gvr.String(), fqn, true, labels.Everything()) + o, err := p.getFactory().Get(p.gvrStr(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -197,7 +197,7 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) if !ok { return nil, errors.New("no factory in context") } - o, err := fac.Get(p.gvr.String(), opts.Path, true, labels.Everything()) + o, err := fac.Get(p.gvrStr(), opts.Path, true, labels.Everything()) if err != nil { return nil, err } @@ -240,7 +240,7 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) // ScanSA scans for ServiceAccount refs. func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := p.GetFactory().List(p.GVR(), ns, wait, labels.Everything()) + oo, err := p.getFactory().List(p.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -270,7 +270,7 @@ func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { // Scan scans for cluster resource refs. func (p *Pod) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := p.GetFactory().List(p.GVR(), ns, wait, labels.Everything()) + oo, err := p.getFactory().List(p.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index 0ec61fd0f8..e940f1b561 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -69,7 +69,7 @@ func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) flags.ActiveNamespace = &ns } spinach := filepath.Join(cfg.AppConfigDir, "spinach.yaml") - if c, err := p.GetFactory().Client().Config().CurrentContextName(); err == nil { + if c, err := p.getFactory().Client().Config().CurrentContextName(); err == nil { spinach = filepath.Join(cfg.AppConfigDir, fmt.Sprintf("%s_spinach.yaml", c)) } if _, err := os.Stat(spinach); err == nil { diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 4efa7509ef..3a0b6280fe 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -30,7 +30,7 @@ type PortForward struct { // Delete deletes a portforward. func (p *PortForward) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error { - p.GetFactory().DeleteForwarder(path) + p.getFactory().DeleteForwarder(path) return nil } @@ -48,7 +48,7 @@ func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, err log.Debug().Msgf("No custom benchmark config file found: %q", benchFile) } - ff, cc := p.GetFactory().Forwarders(), config.Benchmarks.Containers + ff, cc := p.getFactory().Forwarders(), config.Benchmarks.Containers oo := make([]runtime.Object, 0, len(ff)) for k, f := range ff { if !strings.HasPrefix(k, path) { diff --git a/internal/dao/rbac.go b/internal/dao/rbac.go index 68dcaddb64..7e688aee4d 100644 --- a/internal/dao/rbac.go +++ b/internal/dao/rbac.go @@ -60,7 +60,7 @@ func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) { } func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.GetFactory().Get(crbGVR, path, true, labels.Everything()) + o, err := r.getFactory().Get(crbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { return nil, err } - crbo, err := r.GetFactory().Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything()) + crbo, err := r.getFactory().Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -85,7 +85,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { } func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.GetFactory().Get(rbGVR, path, true, labels.Everything()) + o, err := r.getFactory().Get(rbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -96,7 +96,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { } if rb.RoleRef.Kind == "ClusterRole" { - o, e := r.GetFactory().Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything()) + o, e := r.getFactory().Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything()) if e != nil { return nil, e } @@ -108,7 +108,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil } - ro, err := r.GetFactory().Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything()) + ro, err := r.getFactory().Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -123,7 +123,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { log.Debug().Msgf("LOAD-CR %q", path) - o, err := r.GetFactory().Get(crGVR, path, true, labels.Everything()) + o, err := r.getFactory().Get(crGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -138,7 +138,7 @@ func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { } func (r *Rbac) loadRole(path string) ([]runtime.Object, error) { - o, err := r.GetFactory().Get(rGVR, path, true, labels.Everything()) + o, err := r.getFactory().Get(rGVR, path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/rbac_policy.go b/internal/dao/rbac_policy.go index e74f5b7718..7c901c227a 100644 --- a/internal/dao/rbac_policy.go +++ b/internal/dao/rbac_policy.go @@ -195,7 +195,7 @@ func isSameSubject(kind, ns, name string, subject *rbacv1.Subject) bool { func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { const gvr = "rbac.authorization.k8s.io/v1/clusterroles" - oo, err := p.GetFactory().List(gvr, client.ClusterScope, false, labels.Everything()) + oo, err := p.getFactory().List(gvr, client.ClusterScope, false, labels.Everything()) if err != nil { return nil, err } @@ -215,7 +215,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { func (p *Policy) fetchRoles() ([]rbacv1.Role, error) { const gvr = "rbac.authorization.k8s.io/v1/roles" - oo, err := p.GetFactory().List(gvr, client.BlankNamespace, false, labels.Everything()) + oo, err := p.getFactory().List(gvr, client.BlankNamespace, false, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/resource.go b/internal/dao/resource.go index 5cf85d71d3..d704c4b5df 100644 --- a/internal/dao/resource.go +++ b/internal/dao/resource.go @@ -33,12 +33,12 @@ func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error } } - return r.GetFactory().List(r.gvr.String(), ns, false, lsel) + return r.getFactory().List(r.gvrStr(), ns, false, lsel) } // Get returns a resource instance if found, else an error. func (r *Resource) Get(_ context.Context, path string) (runtime.Object, error) { - return r.GetFactory().Get(r.gvr.String(), path, true, labels.Everything()) + return r.getFactory().Get(r.gvrStr(), path, true, labels.Everything()) } // ToYAML returns a resource yaml. diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 3d3dbb4123..219b613c39 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -174,7 +174,7 @@ func (s *StatefulSet) Pod(fqn string) (string, error) { } func (s *StatefulSet) getStatefulSet(fqn string) (*appsv1.StatefulSet, error) { - o, err := s.GetFactory().Get(s.gvr.String(), fqn, true, labels.Everything()) + o, err := s.getFactory().Get(s.gvrStr(), fqn, true, labels.Everything()) if err != nil { return nil, err } @@ -191,7 +191,7 @@ func (s *StatefulSet) getStatefulSet(fqn string) (*appsv1.StatefulSet, error) { // ScanSA scans for serviceaccount refs. func (s *StatefulSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := s.GetFactory().List(s.GVR(), ns, wait, labels.Everything()) + oo, err := s.getFactory().List(s.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } @@ -217,7 +217,7 @@ func (s *StatefulSet) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, // Scan scans for cluster resource refs. func (s *StatefulSet) Scan(ctx context.Context, gvr client.GVR, fqn string, wait bool) (Refs, error) { ns, n := client.Namespaced(fqn) - oo, err := s.GetFactory().List(s.GVR(), ns, wait, labels.Everything()) + oo, err := s.getFactory().List(s.GVR(), ns, wait, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/svc.go b/internal/dao/svc.go index 382a4373c2..1e8fcfe877 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -51,7 +51,7 @@ func (s *Service) Pod(fqn string) (string, error) { // GetInstance returns a service instance. func (s *Service) GetInstance(fqn string) (*v1.Service, error) { - o, err := s.GetFactory().Get(s.gvr.String(), fqn, true, labels.Everything()) + o, err := s.getFactory().Get(s.gvrStr(), fqn, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/stack.go b/internal/model/stack.go index 19f291a304..b660a1ab1b 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -166,6 +166,8 @@ func (s *Stack) Top() Component { return nil } + s.mx.RLock() + defer s.mx.RUnlock() return s.components[len(s.components)-1] } diff --git a/internal/model/table.go b/internal/model/table.go index c6e84c9fcf..6259382799 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -76,6 +76,9 @@ func (t *Table) SetInstance(path string) { // AddListener adds a new model listener. func (t *Table) AddListener(l TableListener) { + t.mx.Lock() + defer t.mx.Unlock() + t.listeners = append(t.listeners, l) } @@ -91,8 +94,8 @@ func (t *Table) RemoveListener(l TableListener) { if victim >= 0 { t.mx.Lock() - defer t.mx.Unlock() t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...) + t.mx.Unlock() } } @@ -289,16 +292,23 @@ func (t *Table) reconcile(ctx context.Context) error { } func (t *Table) fireTableChanged(data *render.TableData) { + var ll []TableListener t.mx.RLock() - defer t.mx.RUnlock() + ll = t.listeners + t.mx.RUnlock() - for _, l := range t.listeners { + for _, l := range ll { l.TableDataChanged(data) } } func (t *Table) fireTableLoadFailed(err error) { - for _, l := range t.listeners { + var ll []TableListener + t.mx.RLock() + ll = t.listeners + t.mx.RUnlock() + + for _, l := range ll { l.TableLoadFailed(err) } } diff --git a/internal/ui/config.go b/internal/ui/config.go index 27c5cf82ef..fb2191fff9 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -20,6 +20,7 @@ import ( // Synchronizer manages ui event queue. type synchronizer interface { Flash() *model.Flash + Logo() *Logo UpdateClusterInfo() QueueUpdateDraw(func()) QueueUpdate(func()) @@ -101,7 +102,7 @@ func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) erro for { select { case evt := <-w.Events: - if evt.Name == c.skinFile && evt.Op != fsnotify.Chmod { + if evt.Op != fsnotify.Chmod && filepath.Base(evt.Name) == filepath.Base(c.skinFile) { log.Debug().Msgf("Skin changed: %s", c.skinFile) s.QueueUpdateDraw(func() { c.RefreshStyles(s) @@ -141,11 +142,13 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error if err := c.Config.Load(evt.Name); err != nil { log.Error().Err(err).Msgf("k9s config reload failed") s.Flash().Warn("k9s config reload failed. Check k9s logs!") + s.Logo().Warn("K9s config reload failed!") } } else { if err := c.Config.K9s.Reload(); err != nil { log.Error().Err(err).Msgf("k9s context config reload failed") s.Flash().Warn("Context config reload failed. Check k9s logs!") + s.Logo().Warn("Context config reload failed!") } } s.QueueUpdateDraw(func() { @@ -252,10 +255,11 @@ func (c *Configurator) loadSkinFile(s synchronizer) { if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { s.Flash().Warnf("Skin file %q not found in skins dir: %s", filepath.Base(skinFile), config.AppSkinsDir) + c.updateStyles("") } else { s.Flash().Errf("Failed to parse skin file -- %s: %s.", filepath.Base(skinFile), err) + c.updateStyles(skinFile) } - c.updateStyles("") } else { s.Flash().Infof("Skin file loaded: %q", skinFile) c.updateStyles(skinFile) diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index 7117de9cc2..a38d26a1bd 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -72,6 +72,7 @@ func newMockSynchronizer() synchronizer { func (s synchronizer) Flash() *model.Flash { return model.NewFlash(100 * time.Millisecond) } +func (s synchronizer) Logo() *ui.Logo { return nil } func (s synchronizer) UpdateClusterInfo() {} func (s synchronizer) QueueUpdateDraw(func()) {} func (s synchronizer) QueueUpdate(func()) {} diff --git a/internal/ui/indicator.go b/internal/ui/indicator.go index 2e0b524bce..d9fb97a19d 100644 --- a/internal/ui/indicator.go +++ b/internal/ui/indicator.go @@ -56,8 +56,8 @@ func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) { s.SetPermanent(fmt.Sprintf( statusIndicatorFmt, data.K9sVer, + data.Context, data.Cluster, - data.User, data.K8sVer, render.PrintPerc(data.Cpu), render.PrintPerc(data.Mem), diff --git a/internal/ui/logo.go b/internal/ui/logo.go index eb8f8501ad..34724f46ab 100644 --- a/internal/ui/logo.go +++ b/internal/ui/logo.go @@ -52,7 +52,10 @@ func (l *Logo) Status() *tview.TextView { // StylesChanged notifies the skin changed. func (l *Logo) StylesChanged(s *config.Styles) { l.styles = s - l.Reset() + l.SetBackgroundColor(l.styles.BgColor()) + l.status.SetBackgroundColor(l.styles.BgColor()) + l.logo.SetBackgroundColor(l.styles.BgColor()) + l.refreshLogo(l.styles.Body().LogoColor) } // IsBenchmarking checks if benchmarking is active or not. @@ -64,10 +67,7 @@ func (l *Logo) IsBenchmarking() bool { // Reset clears out the logo view and resets colors. func (l *Logo) Reset() { l.status.Clear() - l.SetBackgroundColor(l.styles.BgColor()) - l.status.SetBackgroundColor(l.styles.BgColor()) - l.logo.SetBackgroundColor(l.styles.BgColor()) - l.refreshLogo(l.styles.Body().LogoColor) + l.StylesChanged(l.styles) } // Err displays a log error state. diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index bf468c555c..45613a4830 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -5,6 +5,7 @@ package ui import ( "fmt" + "sync" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" @@ -83,6 +84,7 @@ type Prompt struct { styles *config.Styles model PromptModel spacer int + mx sync.RWMutex } // NewPrompt returns a new command view. @@ -206,17 +208,29 @@ func (p *Prompt) activate() { p.model.Notify(false) } -func (p *Prompt) update(text, suggestion string) { - p.Clear() - p.write(text, suggestion) +func (p *Prompt) Clear() { + p.mx.Lock() + defer p.mx.Unlock() + + p.TextView.Clear() +} + +func (p *Prompt) Draw(sc tcell.Screen) { + p.mx.RLock() + defer p.mx.RUnlock() + + p.TextView.Draw(sc) } -func (p *Prompt) suggest(text, suggestion string) { +func (p *Prompt) update(text, suggestion string) { p.Clear() p.write(text, suggestion) } func (p *Prompt) write(text, suggest string) { + p.mx.Lock() + defer p.mx.Unlock() + p.SetCursorIndex(p.spacer + len(text)) txt := text if suggest != "" { @@ -240,7 +254,7 @@ func (p *Prompt) BufferChanged(text, suggestion string) { // SuggestionChanged notifies the suggestion changed. func (p *Prompt) SuggestionChanged(text, suggestion string) { - p.suggest(text, suggestion) + p.update(text, suggestion) } // BufferActive indicates the buff activity changed. diff --git a/internal/view/ns.go b/internal/view/ns.go index ada763e93a..a9b9764f04 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -58,6 +58,9 @@ func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { func (n *Namespace) useNamespace(fqn string) { _, ns := client.Namespaced(fqn) + if client.CleanseNamespace(n.App().Config.ActiveNamespace()) == ns { + return + } if err := n.App().switchNS(ns); err != nil { n.App().Flash().Err(err) return diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index e9caf609e9..b0ffecdffa 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.5' +version: 'v0.31.6' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From ecb253622e86146265393a4411c17bc584d56ad5 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Thu, 18 Jan 2024 09:02:30 -0700 Subject: [PATCH 081/169] K9s/release v0.31.7 (#2491) * fix #2488 * rel v0.31.7 --- Makefile | 2 +- change_logs/release_v0.31.7.md | 49 ++++++++++++++++++++++++++++++++++ cmd/root.go | 23 +++++++--------- internal/client/config.go | 7 +++-- snap/snapcraft.yaml | 2 +- 5 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 change_logs/release_v0.31.7.md diff --git a/Makefile b/Makefile index d64fd545fc..cba35ac3e6 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.6 +VERSION ?= v0.31.7 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.7.md b/change_logs/release_v0.31.7.md new file mode 100644 index 0000000000..ac4179c8c5 --- /dev/null +++ b/change_logs/release_v0.31.7.md @@ -0,0 +1,49 @@ + + +# Release v0.31.7 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +😱 More aftermath... 😱 + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;( +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant. + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2488](https://github.com/derailed/k9s/issues/2488) linux_amd64 "--kubeconfig" not working on v0.31.6 + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index db029ab92b..2ffc3ea1d1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -357,25 +357,20 @@ type ( ) func initK8sFlagCompletion() { - conn := client.NewConfig(k8sFlags) - cfg, err := conn.RawConfig() - if err != nil { - log.Error().Err(err).Msgf("k8s config getter failed") - } - - _ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.Context { + _ = rootCmd.RegisterFlagCompletionFunc("context", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.Context { return cfg.Contexts })) - _ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.Cluster { + _ = rootCmd.RegisterFlagCompletionFunc("cluster", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.Cluster { return cfg.Clusters })) - _ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletion(&cfg, func(cfg *api.Config) map[string]*api.AuthInfo { + _ = rootCmd.RegisterFlagCompletionFunc("user", k8sFlagCompletion(func(cfg *api.Config) map[string]*api.AuthInfo { return cfg.AuthInfos })) _ = rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, s string) ([]string, cobra.ShellCompDirective) { + conn := client.NewConfig(k8sFlags) if c, err := client.InitConnection(conn); err == nil { if nss, err := c.ValidNamespaceNames(); err == nil { return filterFlagCompletions(nss, s) @@ -386,13 +381,15 @@ func initK8sFlagCompletion() { }) } -func k8sFlagCompletion[T any](cfg *api.Config, picker k8sPickerFn[T]) completeFn { +func k8sFlagCompletion[T any](picker k8sPickerFn[T]) completeFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if cfg == nil { - return nil, cobra.ShellCompDirectiveError + conn := client.NewConfig(k8sFlags) + cfg, err := conn.RawConfig() + if err != nil { + log.Error().Err(err).Msgf("k8s config getter failed") } - return filterFlagCompletions(picker(cfg), toComplete) + return filterFlagCompletions(picker(&cfg), toComplete) } } diff --git a/internal/client/config.go b/internal/client/config.go index 0a3253e5fa..28f1d53d88 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -26,14 +26,13 @@ const ( // Config tracks a kubernetes configuration. type Config struct { flags *genericclioptions.ConfigFlags - mutex *sync.RWMutex + mx sync.RWMutex } // NewConfig returns a new k8s config or an error if the flags are invalid. func NewConfig(f *genericclioptions.ConfigFlags) *Config { return &Config{ flags: f, - mutex: &sync.RWMutex{}, } } @@ -299,8 +298,8 @@ func (c *Config) CurrentNamespaceName() (string, error) { // ConfigAccess return the current kubeconfig api server access configuration. func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.mx.RLock() + defer c.mx.RUnlock() return c.clientConfig().ConfigAccess(), nil } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b0ffecdffa..70e77da71b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.6' +version: 'v0.31.7' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From c76703a6e8f4d38666775571fa0c822d779b2189 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Thu, 18 Jan 2024 12:33:10 -0700 Subject: [PATCH 082/169] fix #2492 (#2493) --- internal/dao/registry.go | 8 +++++++- internal/view/app.go | 6 +++--- internal/view/command.go | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 0fb80a0f8e..5efd2df486 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -67,6 +67,12 @@ var stdGroups = []string{ "v1", } +func (m ResourceMetas) clear() { + for k := range m { + delete(m, k) + } +} + // Meta represents available resource metas. type Meta struct { resMetas ResourceMetas @@ -188,7 +194,7 @@ func (m *Meta) LoadResources(f Factory) error { m.mx.Lock() defer m.mx.Unlock() - m.resMetas = make(ResourceMetas, 100) + m.resMetas.clear() if err := loadPreferred(f, m.resMetas); err != nil { return err } diff --git a/internal/view/app.go b/internal/view/app.go index 52e5bee70d..1a3bc67149 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -469,9 +469,6 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { if err != nil { return err } - if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil { - return err - } if cns, ok := ci.NSArg(); ok { ct.Namespace.Active = cns } @@ -493,6 +490,9 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { log.Error().Err(err).Msg("config save failed!") } a.initFactory(ns) + if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil { + return err + } log.Debug().Msgf("--> Switching Context %q -- %q -- %q", name, ns, a.Config.ActiveView()) a.Flash().Infof("Switching context to %q::%q", name, ns) diff --git a/internal/view/command.go b/internal/view/command.go index cdc311880b..5ca68b69d9 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -41,7 +41,7 @@ func NewCommand(app *App) *Command { func (c *Command) Init(path string) error { c.alias = dao.NewAlias(c.app.factory) if _, err := c.alias.Ensure(path); err != nil { - log.Error().Err(err).Msgf("command init failed!") + log.Error().Err(err).Msgf("Alias ensure failed!") return err } customViewers = loadCustomViewers() From 1a2ee1028cedda4eb1d750f1d2998cf67292c32f Mon Sep 17 00:00:00 2001 From: Alexandru Placinta Date: Fri, 19 Jan 2024 01:36:52 +0100 Subject: [PATCH 083/169] Secrets are decoded upon describe (#2461) --- internal/client/gvr.go | 5 ++ internal/dao/alias_test.go | 33 ---------- internal/dao/container_test.go | 2 +- internal/dao/secret.go | 101 ++++++++++++++++++++++++++++++ internal/dao/secret_test.go | 44 +++++++++++++ internal/dao/testdata/secret.json | 15 +++++ internal/dao/utils_test.go | 77 +++++++++++++++++++++++ internal/model/describe.go | 10 +++ internal/model/registry.go | 4 ++ internal/model/types.go | 8 +++ internal/view/live_view.go | 17 +++++ internal/view/secret.go | 9 +-- 12 files changed, 283 insertions(+), 42 deletions(-) create mode 100644 internal/dao/secret.go create mode 100644 internal/dao/secret_test.go create mode 100644 internal/dao/testdata/secret.json create mode 100644 internal/dao/utils_test.go diff --git a/internal/client/gvr.go b/internal/client/gvr.go index fabb81f721..853efb80ae 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -135,6 +135,11 @@ func (g GVR) G() string { return g.g } +// IsDecodable checks if the k8s resource has a decodable view +func (g GVR) IsDecodable() bool { + return g.GVK().Kind == "secrets" +} + // GVRs represents a collection of gvr. type GVRs []GVR diff --git a/internal/dao/alias_test.go b/internal/dao/alias_test.go index f28b16db6f..315c06f7ac 100644 --- a/internal/dao/alias_test.go +++ b/internal/dao/alias_test.go @@ -12,11 +12,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/watch" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" ) func TestAsGVR(t *testing.T) { @@ -88,32 +84,3 @@ func makeAliases() *dao.Alias { }, } } - -type testFactory struct{} - -func makeFactory() dao.Factory { - return testFactory{} -} - -var _ dao.Factory = testFactory{} - -func (f testFactory) Client() client.Connection { - return nil -} -func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { - return nil, nil -} -func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { - return nil, nil -} -func (f testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) { - return nil, nil -} -func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { - return nil, nil -} -func (f testFactory) WaitForCacheSync() {} -func (f testFactory) Forwarders() watch.Forwarders { - return nil -} -func (f testFactory) DeleteForwarder(string) {} diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index 25877dfd08..38a24dd815 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -73,7 +73,7 @@ func (c *conn) IsActiveNamespace(string) bool { return fal type podFactory struct{} -var _ dao.Factory = testFactory{} +var _ dao.Factory = &testFactory{} func (f podFactory) Client() client.Connection { return makeConn() diff --git a/internal/dao/secret.go b/internal/dao/secret.go new file mode 100644 index 0000000000..d9b0955344 --- /dev/null +++ b/internal/dao/secret.go @@ -0,0 +1,101 @@ +// Copyright Authors of K9s + +package dao + +import ( + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// Secret represents a secret K8s resource. +type Secret struct { + Table + decode bool +} + +// Describe describes a secret that can be encoded or decoded. +func (s *Secret) Describe(path string) (string, error) { + encodedDescription, err := s.Table.Describe(path) + + if err != nil { + return "", err + } + + if !s.decode { + return encodedDescription, nil + } + + return s.Decode(encodedDescription, path) +} + +// SetDecode sets the decode flag. +func (s *Secret) SetDecode(flag bool) { + s.decode = flag +} + +// Decode removes the encoded part from the secret's description and appends the +// secret's decoded data. +func (s *Secret) Decode(encodedDescription, path string) (string, error) { + o, err := s.getFactory().Get(s.GVR(), path, true, labels.Everything()) + + if err != nil { + return "", err + } + + dataEndIndex := strings.Index(encodedDescription, "====") + + if dataEndIndex == -1 { + return "", fmt.Errorf("Unable to find data section in secret description") + } + + dataEndIndex += 4 + + if dataEndIndex >= len(encodedDescription) { + return "", fmt.Errorf("Data section in secret description is invalid") + } + + // Remove the encoded part from k8s's describe API + // More details about the reasoning of index: https://github.com/kubernetes/kubectl/blob/v0.29.0/pkg/describe/describe.go#L2542 + body := encodedDescription[0:dataEndIndex] + + d, err := ExtractSecrets(o.(*unstructured.Unstructured)) + + if err != nil { + return "", err + } + + decodedSecrets := []string{} + + for k, v := range d { + decodedSecrets = append(decodedSecrets, "\n", k, ":\t", v) + } + + return body + strings.Join(decodedSecrets, ""), nil +} + +// ExtractSecrets takes an unstructured object and attempts to convert it into a +// Kubernetes Secret. +// It returns a map where the keys are the secret data keys and the values are +// the corresponding secret data values. +// If the conversion fails, it returns an error. +func ExtractSecrets(o *unstructured.Unstructured) (map[string]string, error) { + var secret v1.Secret + err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &secret) + + if err != nil { + return nil, err + } + + secretData := make(map[string]string, len(secret.Data)) + + for k, val := range secret.Data { + secretData[k] = string(val) + } + + return secretData, nil +} diff --git a/internal/dao/secret_test.go b/internal/dao/secret_test.go new file mode 100644 index 0000000000..4602ec8580 --- /dev/null +++ b/internal/dao/secret_test.go @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/stretchr/testify/assert" +) + +func TestEncodedSecretDescribe(t *testing.T) { + s := dao.Secret{} + s.Init(makeFactory(), client.NewGVR("v1/secrets")) + + encodedString := + ` +Name: bootstrap-token-abcdef +Namespace: kube-system +Labels: +Annotations: + +Type: generic + +Data +==== +token-secret: 24 bytes` + + expected := "\nName: bootstrap-token-abcdef\n" + + "Namespace: kube-system\n" + + "Labels: \n" + + "Annotations: \n" + + "\n" + + "Type: generic\n" + + "\n" + + "Data\n" + + "====\n" + + "token-secret:\t0123456789abcdef" + + decodedDescription, _ := s.Decode(encodedString, "kube-system/bootstrap-token-abcdef") + assert.Equal(t, expected, decodedDescription) +} diff --git a/internal/dao/testdata/secret.json b/internal/dao/testdata/secret.json new file mode 100644 index 0000000000..fa560690ae --- /dev/null +++ b/internal/dao/testdata/secret.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "v1", + "data": { + "token-secret": "MDEyMzQ1Njc4OWFiY2RlZg==" + }, + "kind": "Secret", + "metadata": { + "creationTimestamp": "2024-01-15T18:19:00Z", + "name": "bootstrap-token-abcdef", + "namespace": "kube-system", + "resourceVersion": "243", + "uid": "6f5695d4-c0f4-4b65-890a-b1115ffd1f3b" + }, + "type": "bootstrap.kubernetes.io/token" +} diff --git a/internal/dao/utils_test.go b/internal/dao/utils_test.go new file mode 100644 index 0000000000..75da42e206 --- /dev/null +++ b/internal/dao/utils_test.go @@ -0,0 +1,77 @@ +package dao_test + +import ( + "encoding/json" + "fmt" + "os" + "path" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/watch" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" +) + +type testFactory struct { + inventory map[string]map[string][]runtime.Object +} + +func makeFactory() dao.Factory { + return &testFactory{ + inventory: map[string]map[string][]runtime.Object{ + "kube-system": { + "v1/secrets": { + load("secret"), + }, + }, + }, + } +} + +var _ dao.Factory = &testFactory{} + +func (f *testFactory) Client() client.Connection { + return nil +} +func (f *testFactory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.Object, error) { + ns, po := path.Split(fqn) + ns = strings.Trim(ns, "/") + + for _, o := range f.inventory[ns][gvr] { + if o.(*unstructured.Unstructured).GetName() == po { + return o, nil + } + } + + return nil, nil +} +func (f *testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { + return f.inventory[ns][gvr], nil +} + +func (f *testFactory) ForResource(ns, gvr string) (informers.GenericInformer, error) { + return nil, nil +} +func (f *testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { + return nil, nil +} +func (f *testFactory) WaitForCacheSync() {} +func (f *testFactory) Forwarders() watch.Forwarders { + return nil +} +func (f *testFactory) DeleteForwarder(string) {} + +type testResource struct{} + +func load(n string) *unstructured.Unstructured { + raw, _ := os.ReadFile(fmt.Sprintf("testdata/%s.json", n)) + + var o unstructured.Unstructured + json.Unmarshal(raw, &o) + + return &o +} diff --git a/internal/model/describe.go b/internal/model/describe.go index 29c380caca..55b525bfb4 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -27,6 +27,7 @@ type Describe struct { lines []string refreshRate time.Duration listeners []ResourceViewerListener + decode bool } // NewDescribe returns a new describe resource model. @@ -180,6 +181,10 @@ func (d *Describe) describe(ctx context.Context, gvr client.GVR, path string) (s return "", fmt.Errorf("no describer for %q", meta.DAO.GVR()) } + if desc, ok := meta.DAO.(*dao.Secret); ok { + desc.SetDecode(d.decode) + } + return desc.Describe(path) } @@ -202,3 +207,8 @@ func (d *Describe) RemoveListener(l ResourceViewerListener) { d.listeners = append(d.listeners[:victim], d.listeners[victim+1:]...) } } + +// Toggle toggles the decode flag. +func (d *Describe) Toggle() { + d.decode = !d.decode +} diff --git a/internal/model/registry.go b/internal/model/registry.go index 9c333f3106..b4936c380f 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -125,6 +125,10 @@ var Registry = map[string]ResourceMeta{ DAO: &dao.Table{}, Renderer: &render.Event{}, }, + "v1/secrets": { + DAO: &dao.Secret{}, + Renderer: &render.Generic{}, + }, // Apps... "apps/v1/deployments": { diff --git a/internal/model/types.go b/internal/model/types.go index 0484def5df..b8ec0b6c84 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -43,6 +43,14 @@ type ResourceViewer interface { RemoveListener(ResourceViewerListener) } +// EncDecResourceViewer interface extends the ResourceViewer interface and +// adds a `Toggle` that allows the user to switch between encoded or decoded +// state of the view. +type EncDecResourceViewer interface { + ResourceViewer + Toggle() +} + // Igniter represents a runnable view. type Igniter interface { // Start starts a component. diff --git a/internal/view/live_view.go b/internal/view/live_view.go index 980d3742c2..4f146d1889 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -161,6 +161,23 @@ func (v *LiveView) bindKeys() { ui.KeyM: ui.NewKeyAction("Toggle ManagedFields", v.toggleManagedCmd, true), }) } + if v.model != nil && v.model.GVR().IsDecodable() { + v.actions.Add(ui.KeyActions{ + ui.KeyT: ui.NewKeyAction("Toggle Encoded / Decoded", v.toggleEncodedDecodedCmd, true), + }) + } +} + +func (v *LiveView) toggleEncodedDecodedCmd(evt *tcell.EventKey) *tcell.EventKey { + m, ok := v.model.(model.EncDecResourceViewer) + + if !ok { + return evt + } + + m.Toggle() + v.Start() + return nil } func (v *LiveView) editCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/secret.go b/internal/view/secret.go index 3237c24550..e37cdb7687 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -8,10 +8,8 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" ) @@ -53,17 +51,12 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - var secret v1.Secret - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &secret) + d, err := dao.ExtractSecrets(o.(*unstructured.Unstructured)) if err != nil { s.App().Flash().Err(err) return nil } - d := make(map[string]string, len(secret.Data)) - for k, val := range secret.Data { - d[k] = string(val) - } raw, err := yaml.Marshal(d) if err != nil { s.App().Flash().Errf("Error decoding secret %s", err) From e5cadf1f25d92132276a63f434bb83cd0e047ca5 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Fri, 19 Jan 2024 08:57:56 -0700 Subject: [PATCH 084/169] [Bug] Fix #2486 (#2494) --- internal/dao/port_forwarder.go | 9 +++++++-- internal/view/pf_extender.go | 4 +--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 19382e58e4..97043ff138 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -24,6 +24,8 @@ import ( "k8s.io/client-go/transport/spdy" ) +const defaultTimeout = 30 * time.Second + // PortForwarder tracks a port forward stream. type PortForwarder struct { Factory @@ -94,7 +96,10 @@ func (p *PortForwarder) Container() string { func (p *PortForwarder) Stop() { log.Debug().Msgf("<<< Stopping PortForward %s", p.ID()) p.active = false - close(p.stopChan) + if p.stopChan != nil { + close(p.stopChan) + p.stopChan = nil + } } // FQN returns the portforward unique id. @@ -169,7 +174,7 @@ func (p *PortForwarder) forwardPorts(method string, url *url.URL, addr, portMap if err != nil { return nil, err } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url) + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport, Timeout: defaultTimeout}, method, url) return portforward.NewOnAddresses(dialer, []string{addr}, []string{portMap}, p.stopChan, p.readyChan, p.Out, p.ErrOut) } diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 0f5710b74a..7a5506bea8 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -99,11 +99,9 @@ func runForward(v ResourceViewer, pf watch.Forwarder, f *portforward.PortForward pf.SetActive(true) if err := f.ForwardPorts(); err != nil { v.App().Flash().Err(err) - return } - v.App().QueueUpdateDraw(func() { - v.App().factory.DeleteForwarder(pf.FQN()) + v.App().factory.DeleteForwarder(pf.ID()) pf.SetActive(false) }) } From 2070690923f134728a1338967ad8d8d7d12106ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:49:39 -0700 Subject: [PATCH 085/169] Bump k8s.io/kubectl from 0.29.0 to 0.29.1 (#2497) Bumps [k8s.io/kubectl](https://github.com/kubernetes/kubectl) from 0.29.0 to 0.29.1. - [Commits](https://github.com/kubernetes/kubectl/compare/v0.29.0...v0.29.1) --- updated-dependencies: - dependency-name: k8s.io/kubectl dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 67cccb8701..575f70e30c 100644 --- a/go.mod +++ b/go.mod @@ -31,14 +31,14 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.13.3 - k8s.io/api v0.29.0 + k8s.io/api v0.29.1 k8s.io/apiextensions-apiserver v0.29.0 - k8s.io/apimachinery v0.29.0 - k8s.io/cli-runtime v0.29.0 - k8s.io/client-go v0.29.0 + k8s.io/apimachinery v0.29.1 + k8s.io/cli-runtime v0.29.1 + k8s.io/client-go v0.29.1 k8s.io/klog/v2 v2.120.0 - k8s.io/kubectl v0.29.0 - k8s.io/metrics v0.29.0 + k8s.io/kubectl v0.29.1 + k8s.io/metrics v0.29.1 sigs.k8s.io/yaml v1.4.0 ) @@ -299,7 +299,7 @@ require ( golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.152.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -313,7 +313,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.5 // indirect k8s.io/apiserver v0.29.0 // indirect - k8s.io/component-base v0.29.0 // indirect + k8s.io/component-base v0.29.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect modernc.org/libc v1.29.0 // indirect diff --git a/go.sum b/go.sum index 14fd97e547..b96324553a 100644 --- a/go.sum +++ b/go.sum @@ -1567,8 +1567,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1848,28 +1848,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= -k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= -k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= +k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= -k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= -k8s.io/metrics v0.29.0 h1:a6dWcNM+EEowMzMZ8trka6wZtSRIfEA/9oLjuhBksGc= -k8s.io/metrics v0.29.0/go.mod h1:UCuTT4dC/x/x6ODSk87IWIZQnuAfcwxOjb1gjWJdjMA= +k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= +k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= +k8s.io/metrics v0.29.1 h1:qutc3aIPMCniMuEApuLaeYX47rdCn8eycVDx7R6wMlQ= +k8s.io/metrics v0.29.1/go.mod h1:JrbV2U71+v7d/9qb90UVKL8r0uJ6Z2Hy4V7mDm05cKs= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= From e877bd9590fab6ac72a717db6a6ca9cfb47dd3f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:33:36 -0700 Subject: [PATCH 086/169] Bump alpine from 3.19.0 to 3.19.1 (#2515) Bumps alpine from 3.19.0 to 3.19.1. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5b5a39675d..6677c86fd6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # ----------------------------------------------------------------------------- # Build the final Docker image -FROM alpine:3.19.0 +FROM alpine:3.19.1 ARG KUBECTL_VERSION="v1.29.0" COPY --from=build /k9s/execs/k9s /bin/k9s From 7c74fefb81138dfbda37c15e19425323e6c1abb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:34:16 -0700 Subject: [PATCH 087/169] Bump helm.sh/helm/v3 from 3.13.3 to 3.14.0 (#2514) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.13.3 to 3.14.0. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.13.3...v3.14.0) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 575f70e30c..4a80b8da18 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.13.3 + helm.sh/helm/v3 v3.14.0 k8s.io/api v0.29.1 k8s.io/apiextensions-apiserver v0.29.0 k8s.io/apimachinery v0.29.1 @@ -113,7 +113,7 @@ require ( github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect diff --git a/go.sum b/go.sum index b96324553a..98d5313ee8 100644 --- a/go.sum +++ b/go.sum @@ -440,8 +440,8 @@ github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPO github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= @@ -1839,8 +1839,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= -helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= +helm.sh/helm/v3 v3.14.0 h1:TaZIH6uOchn7L27ptwnnuHJiFrT/BsD4dFdp/HLT2nM= +helm.sh/helm/v3 v3.14.0/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c82950f2455bcb6d766174b5be04b7ceb2ae7728 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:34:46 -0700 Subject: [PATCH 088/169] Bump k8s.io/apiextensions-apiserver from 0.29.0 to 0.29.1 (#2513) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.29.0 to 0.29.1. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.29.0...v0.29.1) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 4a80b8da18..587dcb33a2 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.0 k8s.io/api v0.29.1 - k8s.io/apiextensions-apiserver v0.29.0 + k8s.io/apiextensions-apiserver v0.29.1 k8s.io/apimachinery v0.29.1 k8s.io/cli-runtime v0.29.1 k8s.io/client-go v0.29.1 @@ -289,6 +289,7 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + go.uber.org/goleak v1.2.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect @@ -312,7 +313,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.29.0 // indirect + k8s.io/apiserver v0.29.1 // indirect k8s.io/component-base v0.29.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/go.sum b/go.sum index 98d5313ee8..8517997c5c 100644 --- a/go.sum +++ b/go.sum @@ -1209,8 +1209,8 @@ go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93V go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -1850,12 +1850,12 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= +k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= -k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= +k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= From b17ea6910c79d2a31c1af39824e6ef5703a1d62f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 07:38:34 -0700 Subject: [PATCH 089/169] Bump github.com/opencontainers/runc from 1.1.5 to 1.1.12 (#2519) Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.5 to 1.1.12. - [Release notes](https://github.com/opencontainers/runc/releases) - [Changelog](https://github.com/opencontainers/runc/blob/v1.1.12/CHANGELOG.md) - [Commits](https://github.com/opencontainers/runc/compare/v1.1.5...v1.1.12) --- updated-dependencies: - dependency-name: github.com/opencontainers/runc dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 25 ++----------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 587dcb33a2..040048a20d 100644 --- a/go.mod +++ b/go.mod @@ -225,7 +225,7 @@ require ( github.com/onsi/gomega v1.29.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect - github.com/opencontainers/runc v1.1.5 // indirect + github.com/opencontainers/runc v1.1.12 // indirect github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/openvex/go-vex v0.2.5 // indirect diff --git a/go.sum b/go.sum index 8517997c5c..4fe30dfcf2 100644 --- a/go.sum +++ b/go.sum @@ -327,12 +327,10 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -351,7 +349,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= @@ -376,7 +373,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -410,7 +406,6 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= @@ -462,7 +457,6 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -538,7 +532,6 @@ github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXs github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -893,7 +886,6 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -913,7 +905,6 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= @@ -939,12 +930,10 @@ 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/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w= github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= @@ -1052,7 +1041,6 @@ github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd7 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -1116,7 +1104,6 @@ github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM= github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y= github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ= github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= @@ -1134,7 +1121,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w= @@ -1143,8 +1129,6 @@ github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RV github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA= @@ -1310,7 +1294,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -1394,7 +1377,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1404,7 +1386,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1446,12 +1427,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From a1e94856bad7929f27e52bea2f22932813e3a4bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 07:39:21 -0700 Subject: [PATCH 090/169] Bump github.com/anchore/stereoscope (#2518) Bumps [github.com/anchore/stereoscope](https://github.com/anchore/stereoscope) from 0.0.0-20231220161148-590920dabc54 to 0.0.1. - [Commits](https://github.com/anchore/stereoscope/commits/v0.0.1) --- updated-dependencies: - dependency-name: github.com/anchore/stereoscope dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 040048a20d..fba79463b2 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect - github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 // indirect + github.com/anchore/stereoscope v0.0.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect diff --git a/go.sum b/go.sum index 4fe30dfcf2..cf5d5c77a9 100644 --- a/go.sum +++ b/go.sum @@ -257,8 +257,8 @@ github.com/anchore/grype v0.74.0 h1:YesFYnishQEC646iCt0hAvuEfQTvLrhCGJrANyB0c2Q= github.com/anchore/grype v0.74.0/go.mod h1:kxRA1NCUGjTyO0C+babd46oSH2VL3PnF8b+tWGcT21k= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 h1:i2YK5QEs9H2YB3B2zv+AGR44ves0nmAGOD07lMphH14= -github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU= +github.com/anchore/stereoscope v0.0.1 h1:OxF7PaxMltnAxjLnDMyka+SKRIQar/bBkDdavsnjyxM= +github.com/anchore/stereoscope v0.0.1/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU= github.com/anchore/syft v0.100.0 h1:XUpV4xWmD2cBS9hhhEdJEppItz0AxG8f5W3JhI2tQvY= github.com/anchore/syft v0.100.0/go.mod h1:laFRFA/okrA+ut+wPCU32hNkdPEwQfXyaB7E21ymWFc= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= From 3c20f0d5a1e475698a497c16b16cac7e4d024677 Mon Sep 17 00:00:00 2001 From: Adam Sranko <43452028+eiachh@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:54:38 +0100 Subject: [PATCH 091/169] Added defaultsToFullScreen flag for Live/Details view,logs (#2516) * Added fullScreen flag for LiveView, updated readme.md * Added fullScreenLView default value for tests * Extended DefaultsToFullScreen to include logView and DetailsView --- README.md | 27 ++++++++++++------- internal/config/json/schemas/k9s.json | 4 +-- internal/config/json/testdata/k9s/cool.yaml | 1 - internal/config/json/testdata/k9s/toast.yaml | 1 - internal/config/logger.go | 1 - internal/config/testdata/configs/default.yaml | 2 +- .../config/testdata/configs/expected.yaml | 2 +- internal/config/testdata/configs/k9s.yaml | 2 +- .../config/testdata/configs/k9s_toast.yaml | 1 - internal/config/types.go | 3 +++ internal/view/details.go | 17 +++++++----- internal/view/live_view.go | 17 +++++++----- internal/view/log_indicator.go | 2 +- 13 files changed, 49 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2d92ceb037..8b63cf1613 100644 --- a/README.md +++ b/README.md @@ -385,16 +385,27 @@ K9s uses aliases to navigate most K8s resources. refreshRate: 2 # Number of retries once the connection to the api-server is lost. Default 15. maxConnRetry: 5 - # Enable mouse support. Default false - enableMouse: true - # Set to true to hide K9s header. Default false - headless: false - # Set to true to hide K9s crumbs. Default false - crumbsless: false # Indicates whether modification commands like delete/kill/edit are disabled. Default is false readOnly: false # Toggles whether k9s should exit when CTRL-C is pressed. When set to true, you will need to exist k9s via the :quit command. Default is false. noExitOnCtrlC: false + #UI settings + ui: + # Enable mouse support. Default false + enableMouse: false + # Set to true to hide K9s header. Default false + headless: false + # Set to true to hide the K9S logo Default false + logoless: false + # Set to true to hide K9s crumbs. Default false + crumbsless: false + noIcons: false + # Toggles reactive UI. This option provide for watching on disk artifacts changes and update the UI live Defaults to false. + reactive: false + # By default all contexts wil use the dracula skin unless explicitly overridden in the context config file. + skin: dracula # => assumes the file skins/dracula.yaml is present in the $XDG_DATA_HOME/k9s/skins directory + # Allows to set certain views default fullscreen mode. (yaml, helm history, describe, value_extender, details, logs) Default false + defaultsToFullScreen: false # Toggles icons display as not all terminal support these chars. noIcons: false # Toggles whether k9s should check for the latest revision from the Github repository releases. Default is false. @@ -409,8 +420,6 @@ K9s uses aliases to navigate most K8s resources. buffer: 500 # Represents how far to go back in the log timeline in seconds. Setting to -1 will tail logs. Default is -1. sinceSeconds: 300 # => tail the last 5 mins. - # Go full screen while displaying logs. Default false - fullScreenLogs: false # Toggles log line wrap. Default false textWrap: false # Toggles log line timestamp info. Default false @@ -912,6 +921,7 @@ k9s: reactive: false # By default all contexts wil use the dracula skin unless explicitly overridden in the context config file. skin: dracula # => assumes the file skins/dracula.yaml is present in the $XDG_DATA_HOME/k9s/skins directory + defaultsToFullScreen: false skipLatestRevCheck: false disablePodCounting: false shellPod: @@ -929,7 +939,6 @@ k9s: tail: 100 buffer: 5000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/json/schemas/k9s.json b/internal/config/json/schemas/k9s.json index 7830fdfd1e..e217bbec29 100644 --- a/internal/config/json/schemas/k9s.json +++ b/internal/config/json/schemas/k9s.json @@ -25,7 +25,8 @@ "crumbsless": {"type": "boolean"}, "noIcons": {"type": "boolean"}, "reactive": {"type": "boolean"}, - "skin": {"type": "string"} + "skin": {"type": "string"}, + "defaultsToFullScreen": {"type": "boolean"} } }, "shellPod": { @@ -101,7 +102,6 @@ "tail": {"type": "integer"}, "buffer": {"type": "integer"}, "sinceSeconds": {"type": "integer"}, - "fullScreen": {"type": "boolean"}, "textWrap": {"type": "boolean"}, "showTime": {"type": "boolean"} } diff --git a/internal/config/json/testdata/k9s/cool.yaml b/internal/config/json/testdata/k9s/cool.yaml index d09da47b80..5261b9d7b0 100644 --- a/internal/config/json/testdata/k9s/cool.yaml +++ b/internal/config/json/testdata/k9s/cool.yaml @@ -28,7 +28,6 @@ k9s: tail: 100 buffer: 5000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/json/testdata/k9s/toast.yaml b/internal/config/json/testdata/k9s/toast.yaml index b380e0a311..61dcda41db 100644 --- a/internal/config/json/testdata/k9s/toast.yaml +++ b/internal/config/json/testdata/k9s/toast.yaml @@ -22,7 +22,6 @@ k9s: tail: 100 buffer: 5000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/logger.go b/internal/config/logger.go index 88c7653dd2..4c6a6a1832 100644 --- a/internal/config/logger.go +++ b/internal/config/logger.go @@ -19,7 +19,6 @@ type Logger struct { TailCount int64 `json:"tail" yaml:"tail"` BufferSize int `json:"buffer" yaml:"buffer"` SinceSeconds int64 `json:"sinceSeconds" yaml:"sinceSeconds"` - FullScreen bool `json:"fullScreen" yaml:"fullScreen"` TextWrap bool `json:"textWrap" yaml:"textWrap"` ShowTime bool `json:"showTime" yaml:"showTime"` } diff --git a/internal/config/testdata/configs/default.yaml b/internal/config/testdata/configs/default.yaml index 3cd4634890..abf8432ba4 100644 --- a/internal/config/testdata/configs/default.yaml +++ b/internal/config/testdata/configs/default.yaml @@ -12,6 +12,7 @@ k9s: crumbsless: false reactive: false noIcons: false + defaultsToFullScreen: false skipLatestRevCheck: false disablePodCounting: false shellPod: @@ -29,7 +30,6 @@ k9s: tail: 100 buffer: 5000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/testdata/configs/expected.yaml b/internal/config/testdata/configs/expected.yaml index 0038921199..e85a32f160 100644 --- a/internal/config/testdata/configs/expected.yaml +++ b/internal/config/testdata/configs/expected.yaml @@ -12,6 +12,7 @@ k9s: crumbsless: false reactive: false noIcons: false + defaultsToFullScreen: false skipLatestRevCheck: false disablePodCounting: false shellPod: @@ -29,7 +30,6 @@ k9s: tail: 500 buffer: 800 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/testdata/configs/k9s.yaml b/internal/config/testdata/configs/k9s.yaml index 050392b6af..8f3546d357 100644 --- a/internal/config/testdata/configs/k9s.yaml +++ b/internal/config/testdata/configs/k9s.yaml @@ -12,6 +12,7 @@ k9s: crumbsless: false reactive: false noIcons: false + defaultsToFullScreen: false skipLatestRevCheck: false disablePodCounting: false shellPod: @@ -29,7 +30,6 @@ k9s: tail: 200 buffer: 2000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/testdata/configs/k9s_toast.yaml b/internal/config/testdata/configs/k9s_toast.yaml index 00e56a062c..668326ef9b 100644 --- a/internal/config/testdata/configs/k9s_toast.yaml +++ b/internal/config/testdata/configs/k9s_toast.yaml @@ -28,7 +28,6 @@ k9s: tail: 200 buffer: 2000 sinceSeconds: -1 - fullScreen: false textWrap: false showTime: false thresholds: diff --git a/internal/config/types.go b/internal/config/types.go index 3176ea9423..6938e55585 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -31,4 +31,7 @@ type UI struct { // Skin reference the general k9s skin name. // Can be overridden per context. Skin string `json:"skin" yaml:"skin,omitempty"` + + // DefaultsToFullScreen toggles fullscreen on views like logs, yaml, details. + DefaultsToFullScreen bool `json:"defaultsToFullScreen" yaml:"defaultsToFullScreen"` } diff --git a/internal/view/details.go b/internal/view/details.go index 2a52e24da2..5ad7b4fb2a 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -76,6 +76,7 @@ func (d *Details) Init(_ context.Context) error { d.app.Styles.AddListener(d) d.StylesChanged(d.app.Styles) + d.setFullScreen(d.app.Config.K9s.UI.DefaultsToFullScreen) d.app.Prompt().SetModel(d.cmdBuff) d.cmdBuff.AddListener(d) @@ -226,16 +227,20 @@ func (d *Details) toggleFullScreenCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - d.fullScreen = !d.fullScreen - d.SetFullScreen(d.fullScreen) - d.Box.SetBorder(!d.fullScreen) - if d.fullScreen { + d.setFullScreen(!d.fullScreen) + + return nil +} + +func (d *Details) setFullScreen(isFullScreen bool) { + d.fullScreen = isFullScreen + d.SetFullScreen(isFullScreen) + d.Box.SetBorder(!isFullScreen) + if isFullScreen { d.Box.SetBorderPadding(0, 0, 0, 0) } else { d.Box.SetBorderPadding(0, 0, 1, 1) } - - return nil } func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/live_view.go b/internal/view/live_view.go index 4f146d1889..ce32382720 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -78,6 +78,7 @@ func (v *LiveView) Init(_ context.Context) error { v.app.Styles.AddListener(v) v.StylesChanged(v.app.Styles) + v.setFullScreen(v.app.Config.K9s.UI.DefaultsToFullScreen) v.app.Prompt().SetModel(v.cmdBuff) v.cmdBuff.AddListener(v) @@ -286,16 +287,20 @@ func (v *LiveView) toggleFullScreenCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - v.fullScreen = !v.fullScreen - v.SetFullScreen(v.fullScreen) - v.Box.SetBorder(!v.fullScreen) - if v.fullScreen { + v.setFullScreen(!v.fullScreen) + + return nil +} + +func (v *LiveView) setFullScreen(isFullScreen bool) { + v.fullScreen = isFullScreen + v.SetFullScreen(isFullScreen) + v.Box.SetBorder(!isFullScreen) + if isFullScreen { v.Box.SetBorderPadding(0, 0, 0, 0) } else { v.Box.SetBorderPadding(0, 0, 1, 1) } - - return nil } func (v *LiveView) nextCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index 6e3f65291b..ce80654b09 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -34,7 +34,7 @@ func NewLogIndicator(cfg *config.Config, styles *config.Styles, allContainers bo TextView: tview.NewTextView(), indicator: make([]byte, 0, 100), scrollStatus: 1, - fullScreen: cfg.K9s.Logger.FullScreen, + fullScreen: cfg.K9s.UI.DefaultsToFullScreen, textWrap: cfg.K9s.Logger.TextWrap, showTime: cfg.K9s.Logger.ShowTime, shouldDisplayAllContainers: allContainers, From cd5522cd359153f375d9fccaf87a449577ecf043 Mon Sep 17 00:00:00 2001 From: Adam Sranko <43452028+eiachh@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:03:00 +0100 Subject: [PATCH 092/169] Fix Toggle Faults filtering (#2509) * Added valid column to workloads for toast filtering * Removed redundant status func call, changed workload header order --- internal/dao/workload.go | 18 ++++++++++++++---- internal/render/workload.go | 4 +++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/internal/dao/workload.go b/internal/dao/workload.go index ae9693b831..72027605ce 100644 --- a/internal/dao/workload.go +++ b/internal/dao/workload.go @@ -122,12 +122,14 @@ func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error ts = m.CreationTimestamp } } + stat := status(gvr, r, table.ColumnDefinitions) oo = append(oo, &render.WorkloadRes{Row: metav1.TableRow{Cells: []interface{}{ gvr.String(), ns, r.Cells[indexOf("Name", table.ColumnDefinitions)], - diagnose(gvr, r, table.ColumnDefinitions), - status(gvr, r, table.ColumnDefinitions), + stat, + readiness(gvr, r, table.ColumnDefinitions), + validity(stat), ts, }}}) } @@ -138,7 +140,7 @@ func (a *Workload) List(ctx context.Context, ns string) ([]runtime.Object, error // Helpers... -func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { +func readiness(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { switch gvr { case PodGVR, DpGVR, StsGVR: return r.Cells[indexOf("Ready", h)].(string) @@ -153,7 +155,7 @@ func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) return render.NAValue } -func diagnose(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { +func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { switch gvr { case PodGVR: if !isReady(r.Cells[indexOf("Ready", h)].(string)) || r.Cells[indexOf("Status", h)] != render.PhaseRunning { @@ -187,6 +189,14 @@ func diagnose(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinitio return StatusOK } +func validity(status string) string { + if status != "DEGRADED" { + return "" + } + + return status +} + func isReady(s string) bool { tt := strings.Split(s, "/") if len(tt) != 2 { diff --git a/internal/render/workload.go b/internal/render/workload.go index 058cfd4078..250c52ecff 100644 --- a/internal/render/workload.go +++ b/internal/render/workload.go @@ -44,6 +44,7 @@ func (Workload) Header(string) Header { HeaderColumn{Name: "NAME"}, HeaderColumn{Name: "STATUS"}, HeaderColumn{Name: "READY"}, + HeaderColumn{Name: "VALID", Wide: true}, HeaderColumn{Name: "AGE", Time: true}, } } @@ -62,7 +63,8 @@ func (n Workload) Render(o interface{}, _ string, r *Row) error { res.Row.Cells[2].(string), res.Row.Cells[3].(string), res.Row.Cells[4].(string), - ToAge(res.Row.Cells[5].(metav1.Time)), + res.Row.Cells[5].(string), + ToAge(res.Row.Cells[6].(metav1.Time)), } return nil From 5a673e20b385f83d92dbce5d53406ad5bbc15afa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 08:03:51 -0700 Subject: [PATCH 093/169] Bump k8s.io/klog/v2 from 2.120.0 to 2.120.1 (#2499) Bumps [k8s.io/klog/v2](https://github.com/kubernetes/klog) from 2.120.0 to 2.120.1. - [Release notes](https://github.com/kubernetes/klog/releases) - [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes/klog/compare/v2.120.0...v2.120.1) --- updated-dependencies: - dependency-name: k8s.io/klog/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fba79463b2..29e150031f 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( k8s.io/apimachinery v0.29.1 k8s.io/cli-runtime v0.29.1 k8s.io/client-go v0.29.1 - k8s.io/klog/v2 v2.120.0 + k8s.io/klog/v2 v2.120.1 k8s.io/kubectl v0.29.1 k8s.io/metrics v0.29.1 sigs.k8s.io/yaml v1.4.0 diff --git a/go.sum b/go.sum index cf5d5c77a9..47f6240625 100644 --- a/go.sum +++ b/go.sum @@ -1841,8 +1841,8 @@ k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= -k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= -k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= From 763a6b0e000da9d78b01662dece08cd379ba7240 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Tue, 6 Feb 2024 07:56:21 +0800 Subject: [PATCH 094/169] adding the f command to pf extender view (#2511) --- internal/view/dp_test.go | 2 +- internal/view/ds_test.go | 2 +- internal/view/pf_extender.go | 38 ++++++++++++++++++++++++++++++++++++ internal/view/pod.go | 28 -------------------------- internal/view/sts_test.go | 2 +- internal/view/svc_test.go | 2 +- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go index 5434db4bfc..aa2935c164 100644 --- a/internal/view/dp_test.go +++ b/internal/view/dp_test.go @@ -16,5 +16,5 @@ func TestDeploy(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Deployments", v.Name()) - assert.Equal(t, 14, len(v.Hints())) + assert.Equal(t, 15, len(v.Hints())) } diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go index 17e8ed9dce..2a73445d5d 100644 --- a/internal/view/ds_test.go +++ b/internal/view/ds_test.go @@ -16,5 +16,5 @@ func TestDaemonSet(t *testing.T) { assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "DaemonSets", v.Name()) - assert.Equal(t, 15, len(v.Hints())) + assert.Equal(t, 16, len(v.Hints())) } diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 7a5506bea8..627395f76e 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -4,9 +4,12 @@ package view import ( + "context" "fmt" "strings" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/port" "github.com/derailed/k9s/internal/ui" @@ -35,6 +38,7 @@ func NewPortForwardExtender(r ResourceViewer) ResourceViewer { func (p *PortForwardExtender) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ + ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true), ui.KeyShiftF: ui.NewKeyAction("Port-Forward", p.portFwdCmd, true), }) } @@ -61,6 +65,32 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } +func (p *PortForwardExtender) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { + path := p.GetTable().GetSelectedItem() + if path == "" { + return evt + } + + podName, err := p.fetchPodName(path) + if err != nil { + p.App().Flash().Err(err) + return nil + } + + if !p.App().factory.Forwarders().IsPodForwarded(podName) { + p.App().Flash().Errf("no port-forward defined") + return nil + } + + pf := NewPortForward(client.NewGVR("portforwards")) + pf.SetContextFn(p.portForwardContext) + if err := p.App().inject(pf, false); err != nil { + p.App().Flash().Err(err) + } + + return nil +} + func (p *PortForwardExtender) fetchPodName(path string) (string, error) { res, err := dao.AccessorFor(p.App().factory, p.GVR()) if err != nil { @@ -74,6 +104,14 @@ func (p *PortForwardExtender) fetchPodName(path string) (string, error) { return ctrl.Pod(path) } +func (p *PortForwardExtender) portForwardContext(ctx context.Context) context.Context { + if bc := p.App().BenchFile; bc != "" { + ctx = context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) + } + + return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem()) +} + // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/view/pod.go b/internal/view/pod.go index 6278691cea..bb5959b47a 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -117,7 +117,6 @@ func (p *Pod) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ ui.KeyN: ui.NewKeyAction("Show Node", p.showNode, true), - ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), false), ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd(statusCol, true), false), @@ -196,33 +195,6 @@ func (p *Pod) showNode(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { - path := p.GetTable().GetSelectedItem() - if path == "" { - return evt - } - - if !p.App().factory.Forwarders().IsPodForwarded(path) { - p.App().Flash().Errf("no port-forward defined") - return nil - } - pf := NewPortForward(client.NewGVR("portforwards")) - pf.SetContextFn(p.portForwardContext) - if err := p.App().inject(pf, false); err != nil { - p.App().Flash().Err(err) - } - - return nil -} - -func (p *Pod) portForwardContext(ctx context.Context) context.Context { - if bc := p.App().BenchFile; bc != "" { - ctx = context.WithValue(ctx, internal.KeyBenchCfg, p.App().BenchFile) - } - - return context.WithValue(ctx, internal.KeyPath, p.GetTable().GetSelectedItem()) -} - func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey { selections := p.GetTable().GetSelectedItems() if len(selections) == 0 { diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go index 889b025fda..5651fd38df 100644 --- a/internal/view/sts_test.go +++ b/internal/view/sts_test.go @@ -16,5 +16,5 @@ func TestStatefulSetNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "StatefulSets", s.Name()) - assert.Equal(t, 12, len(s.Hints())) + assert.Equal(t, 13, len(s.Hints())) } diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index babf102ac5..cc50f92fe7 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -173,5 +173,5 @@ func TestServiceNew(t *testing.T) { assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Services", s.Name()) - assert.Equal(t, 10, len(s.Hints())) + assert.Equal(t, 11, len(s.Hints())) } From 90a810ffc2ec5d5460ae4f43325e295f158dec65 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 6 Feb 2024 19:21:28 -0700 Subject: [PATCH 095/169] K9s/release v0.31.8 (#2528) * [Maint] Fix race condition issue * [Bug] Fix #2501 * [Maint] Allow reference to resource aliases for plugins * [Feat] Intro cp namespace command + misc cleanup * [Maint] Rev k8s v0.29.1 * [Bug] Fix #1033, #1558 * [Bug] Fix #2527 * [Bug] Fix #2520 * rel v0.31.8 --- Makefile | 2 +- change_logs/release_v0.31.8.md | 102 +++++++++ go.mod | 4 +- go.sum | 8 +- internal/client/config.go | 20 +- internal/config/data/config.go | 19 +- internal/config/data/dir.go | 22 +- internal/config/k9s.go | 3 +- internal/dao/helm_chart.go | 24 +- internal/dao/helm_history.go | 8 +- internal/dao/pod.go | 1 + internal/dao/registry.go | 4 +- internal/dao/secret.go | 1 + internal/dao/utils_test.go | 7 +- internal/render/helpers.go | 3 +- internal/render/job.go | 4 +- internal/render/pod.go | 33 ++- internal/render/pod_int_test.go | 282 +++++++++++++++++++++++ internal/render/pod_test.go | 388 +++++++++++++++++++++++++++++++- internal/ui/config.go | 7 +- internal/view/actions.go | 11 +- internal/view/actions_test.go | 13 +- internal/view/app.go | 7 +- internal/view/browser.go | 9 +- internal/view/command.go | 18 +- internal/view/helpers.go | 19 ++ internal/view/live_view.go | 2 +- internal/view/ns.go | 6 - internal/view/pod.go | 2 +- internal/view/table.go | 17 +- internal/view/xray.go | 4 +- internal/vul/scanner.go | 45 ++-- snap/snapcraft.yaml | 2 +- 33 files changed, 979 insertions(+), 118 deletions(-) create mode 100644 change_logs/release_v0.31.8.md diff --git a/Makefile b/Makefile index cba35ac3e6..d464c796f5 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.7 +VERSION ?= v0.31.8 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.8.md b/change_logs/release_v0.31.8.md new file mode 100644 index 0000000000..f17473e165 --- /dev/null +++ b/change_logs/release_v0.31.8.md @@ -0,0 +1,102 @@ + + +# Release v0.31.8 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Thank you all for pitching in and helping flesh out issues!! + +Please make sure to add gory details to issues ie relevant configs, debug logs, etc... + +Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;( +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant. + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## ♫ Sounds Behind The Release ♭ + +Going back to the classics... + +* [Ambulance Blues - Neil Young](https://www.youtube.com/watch?v=bCQisTEdBwY) +* [Christopher Columbus - Burning Spear](https://www.youtube.com/watch?v=5qbMKTY_Cr0) +* [Feelin' the Same - Clinton Fearon](https://www.youtube.com/watch?v=aRPF2Yta_cs) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Andreas Frangopoulos](https://github.com/qubeio) +* [Tu Hoang](https://github.com/rebyn) +* [Shoshin Nikita](https://github.com/ShoshinNikita) +* [Dima Altukhov](https://github.com/alt-dima) +* [wpbeckwith](https://github.com/wpbeckwith) +* [a-thomas-22](https://github.com/a-thomas-22) +* [kmath313](https://github.com/kmath313) +* [Jörgen](https://github.com/wthrbtn) +* [Eckl, Máté](https://github.com/ecklm) +* [Jacky Nguyen](https://github.com/nktpro) +* [Chris Bradley](https://github.com/chrisbradleydev) +* [Vytautas Kubilius](https://github.com/vytautaskubilius) +* [Patrick Christensen](https://github.com/BuriedStPatrick) +* [Ollie Lowson](https://github.com/ollielowson-wcbs) +* [Mike Macaulay](https://github.com/mmacaula) +* [David Birks](https://github.com/dbirks) +* [James Hounshell](https://github.com/jameshounshell) +* [elapse2039](https://github.com/elapse2039) +* [Vinicius Xavier](https://github.com/vinixaavier) +* [Phuc Phung](https://github.com/Foxhound401) +* [ollielowson](https://github.com/ollielowson) + +> Sponsorship cancellations since the last release: **4!** 🥹 + +--- + +## Resolved Issues + +* [#2527](https://github.com/derailed/k9s/issues/2527) Multiple k9s panels open in parallel for the same cluster breaks config.yaml +* [#2520](https://github.com/derailed/k9s/issues/2520) pods with init container with restartPolicy: Always stay in Init status +* [#2501](https://github.com/derailed/k9s/issues/2501) Cannot add plugins to helm scope bug +* [#2492](https://github.com/derailed/k9s/issues/2492) API Resources "carry over" between contexts, causing errors if they share shortnames +* [#1158](https://github.com/derailed/k9s/issues/1158) Removing a helm release incorrectly determines the namespace of resources +* [#1033](https://github.com/derailed/k9s/issues/1033) Helm delete deletes only the helm entry but not the deployment + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2509](https://github.com/derailed/k9s/pull/2509) Fix Toggle Faults filtering +* [#2511](https://github.com/derailed/k9s/pull/2511) adding the f command to pf extender view +* [#2518](https://github.com/derailed/k9s/pull/2518) Added defaultsToFullScreen flag for Live/Details view,logs + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/go.mod b/go.mod index 29e150031f..a395caa623 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/anchore/syft v0.100.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/derailed/popeye v0.11.2 + github.com/derailed/popeye v0.11.3 github.com/derailed/tcell/v2 v2.3.1-rc.3 github.com/derailed/tview v0.8.3 github.com/fatih/color v1.16.0 @@ -22,7 +22,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/petergtz/pegomock v2.9.0+incompatible github.com/rakyll/hey v0.1.4 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.32.0 github.com/sahilm/fuzzy v0.1.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 47f6240625..6985a7727d 100644 --- a/go.sum +++ b/go.sum @@ -381,8 +381,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= -github.com/derailed/popeye v0.11.2 h1:8MKMjYBJdYNktTKeh98TeT127jZY6CFAsurrENoTZCY= -github.com/derailed/popeye v0.11.2/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY= +github.com/derailed/popeye v0.11.3 h1:gQUp6zuSIRDBdyLS1Ln0nFs8FbQ+KGE+iQxe0w4Ug8M= +github.com/derailed/popeye v0.11.3/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= github.com/derailed/tview v0.8.3 h1:jhN7LW7pfCWf7Z6VC5Dpi/1usavOBZxz2mY90//TMsU= @@ -1013,8 +1013,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/internal/client/config.go b/internal/client/config.go index 28f1d53d88..0dafc59e64 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -20,7 +20,7 @@ const ( defaultCallTimeoutDuration time.Duration = 15 * time.Second // UsePersistentConfig caches client config to avoid reloads. - UsePersistentConfig = true + UsePersistentConfig = false ) // Config tracks a kubernetes configuration. @@ -85,6 +85,24 @@ func (c *Config) SwitchContext(name string) error { return nil } +func (c *Config) Clone(ns string) (*genericclioptions.ConfigFlags, error) { + flags := genericclioptions.NewConfigFlags(false) + ct, err := c.CurrentContextName() + if err != nil { + return nil, err + } + cl, err := c.CurrentClusterName() + if err != nil { + return nil, err + } + flags.Context, flags.ClusterName = &ct, &cl + flags.Namespace = &ns + flags.Timeout = c.Flags().Timeout + flags.KubeConfig = c.Flags().KubeConfig + + return flags, nil +} + // CurrentClusterName returns the currently active cluster name. func (c *Config) CurrentClusterName() (string, error) { if isSet(c.flags.ClusterName) { diff --git a/internal/config/data/config.go b/internal/config/data/config.go index f005571ad2..130bf15b36 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -6,7 +6,6 @@ package data import ( "fmt" "io" - "os" "sync" "github.com/derailed/k9s/internal/client" @@ -29,6 +28,8 @@ func NewConfig(ct *api.Context) *Config { // Validate ensures config is in norms. func (c *Config) Validate(conn client.Connection, ks KubeSettings) { + c.mx.Lock() + defer c.mx.Unlock() if c.Context == nil { c.Context = NewContext() @@ -42,19 +43,3 @@ func (c *Config) Dump(w io.Writer) { fmt.Fprintf(w, "%s\n", string(bb)) } - -// Save saves the config to disk. -func (c *Config) Save(path string) error { - c.mx.RLock() - defer c.mx.RUnlock() - - if err := EnsureDirPath(path, DefaultDirMod); err != nil { - return err - } - cfg, err := yaml.Marshal(c) - if err != nil { - return err - } - - return os.WriteFile(path, cfg, DefaultFileMod) -} diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index 0eb3f5f407..b945706f24 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "sync" "github.com/derailed/k9s/internal/config/json" "github.com/rs/zerolog/log" @@ -18,6 +19,7 @@ import ( // Dir tracks context configurations. type Dir struct { root string + mx sync.Mutex } // NewDir returns a new instance. @@ -50,14 +52,32 @@ func (d *Dir) Load(n string, ct *api.Context) (*Config, error) { func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { cfg := NewConfig(ct) - if err := cfg.Save(path); err != nil { + if err := d.Save(path, cfg); err != nil { return nil, err } return cfg, nil } +func (d *Dir) Save(path string, c *Config) error { + d.mx.Lock() + defer d.mx.Unlock() + + if err := EnsureDirPath(path, DefaultDirMod); err != nil { + return err + } + cfg, err := yaml.Marshal(c) + if err != nil { + return err + } + + return os.WriteFile(path, cfg, DefaultFileMod) +} + func (d *Dir) loadConfig(path string) (*Config, error) { + d.mx.Lock() + defer d.mx.Unlock() + bb, err := os.ReadFile(path) if err != nil { return nil, err diff --git a/internal/config/k9s.go b/internal/config/k9s.go index f4eb69fe8d..72e4a34b6a 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -78,7 +78,7 @@ func (k *K9s) Save() error { data.MainConfigFile, ) - return k.getActiveConfig().Save(path) + return k.dir.Save(path, k.getActiveConfig()) } // Merge merges k9s configs. @@ -157,7 +157,6 @@ func (k *K9s) ActiveContextName() string { // ActiveContext returns the currently active context. func (k *K9s) ActiveContext() (*data.Context, error) { - if cfg := k.getActiveConfig(); cfg != nil && cfg.Context != nil { return cfg.Context, nil } diff --git a/internal/dao/helm_chart.go b/internal/dao/helm_chart.go index 0f1e8fdbcf..2efe9f6b60 100644 --- a/internal/dao/helm_chart.go +++ b/internal/dao/helm_chart.go @@ -15,6 +15,7 @@ import ( "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" ) var ( @@ -31,7 +32,7 @@ type HelmChart struct { // List returns a collection of resources. func (h *HelmChart) List(ctx context.Context, ns string) ([]runtime.Object, error) { - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return nil, err } @@ -55,7 +56,7 @@ func (h *HelmChart) List(ctx context.Context, ns string) ([]runtime.Object, erro // Get returns a resource. func (h *HelmChart) Get(_ context.Context, path string) (runtime.Object, error) { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return nil, err } @@ -70,7 +71,7 @@ func (h *HelmChart) Get(_ context.Context, path string) (runtime.Object, error) // GetValues returns values for a release func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return nil, err } @@ -87,7 +88,7 @@ func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) { // Describe returns the chart notes. func (h *HelmChart) Describe(path string) (string, error) { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return "", err } @@ -102,7 +103,7 @@ func (h *HelmChart) Describe(path string) (string, error) { // ToYAML returns the chart manifest. func (h *HelmChart) ToYAML(path string, showManaged bool) (string, error) { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return "", err } @@ -122,10 +123,13 @@ func (h *HelmChart) Delete(_ context.Context, path string, _ *metav1.DeletionPro // Uninstall uninstalls a HelmChart. func (h *HelmChart) Uninstall(path string, keepHist bool) error { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + flags := h.Client().Config().Flags() + flags.Namespace = &ns + cfg, err := ensureHelmConfig(flags, ns) if err != nil { return err } + u := action.NewUninstall(cfg) u.KeepHistory = keepHist res, err := u.Run(n) @@ -140,13 +144,13 @@ func (h *HelmChart) Uninstall(path string, keepHist bool) error { } // ensureHelmConfig return a new configuration. -func ensureHelmConfig(c client.Connection, ns string) (*action.Configuration, error) { +func ensureHelmConfig(flags *genericclioptions.ConfigFlags, ns string) (*action.Configuration, error) { cfg := new(action.Configuration) - err := cfg.Init(c.Config().Flags(), ns, os.Getenv("HELM_DRIVER"), helmLogger) + err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger) return cfg, err } -func helmLogger(s string, args ...interface{}) { - log.Debug().Msgf("%s %v", s, args) +func helmLogger(fmt string, args ...interface{}) { + log.Debug().Msgf("[Helm] "+fmt, args...) } diff --git a/internal/dao/helm_history.go b/internal/dao/helm_history.go index d589298cd6..3dd30deaea 100644 --- a/internal/dao/helm_history.go +++ b/internal/dao/helm_history.go @@ -39,7 +39,7 @@ func (h *HelmHistory) List(ctx context.Context, _ string) ([]runtime.Object, err } ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return nil, err } @@ -65,7 +65,7 @@ func (h *HelmHistory) Get(_ context.Context, path string) (runtime.Object, error } ns, n := client.Namespaced(fqn) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return nil, err } @@ -134,7 +134,7 @@ func (h *HelmHistory) GetValues(path string, allValues bool) ([]byte, error) { func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return err } @@ -152,7 +152,7 @@ func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error { // Delete uninstall a Helm. func (h *HelmHistory) Delete(_ context.Context, path string, _ *metav1.DeletionPropagation, _ Grace) error { ns, n := client.Namespaced(path) - cfg, err := ensureHelmConfig(h.Client(), ns) + cfg, err := ensureHelmConfig(h.Client().Config().Flags(), ns) if err != nil { return err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 1f34b60cef..f0bd20ad06 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -54,6 +54,7 @@ func (p *Pod) IsHappy(po v1.Pod) bool { return false } } + return true } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 5efd2df486..ff11836dbc 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -319,8 +319,8 @@ func loadK9s(m ResourceMetas) { func loadHelm(m ResourceMetas) { m[client.NewGVR("helm")] = metav1.APIResource{ - Name: "chart", - Kind: "Chart", + Name: "helm", + Kind: "Helm", Namespaced: true, Verbs: []string{"delete"}, Categories: []string{helmCat}, diff --git a/internal/dao/secret.go b/internal/dao/secret.go index d9b0955344..8cc47868c6 100644 --- a/internal/dao/secret.go +++ b/internal/dao/secret.go @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package dao diff --git a/internal/dao/utils_test.go b/internal/dao/utils_test.go index 75da42e206..1984f63acb 100644 --- a/internal/dao/utils_test.go +++ b/internal/dao/utils_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dao_test import ( @@ -65,13 +68,11 @@ func (f *testFactory) Forwarders() watch.Forwarders { } func (f *testFactory) DeleteForwarder(string) {} -type testResource struct{} - func load(n string) *unstructured.Unstructured { raw, _ := os.ReadFile(fmt.Sprintf("testdata/%s.json", n)) var o unstructured.Unstructured - json.Unmarshal(raw, &o) + _ = json.Unmarshal(raw, &o) return &o } diff --git a/internal/render/helpers.go b/internal/render/helpers.go index bafe900a20..bd474cdd41 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -4,6 +4,7 @@ package render import ( + "context" "math" "sort" "strconv" @@ -28,7 +29,7 @@ func computeVulScore(m metav1.ObjectMeta, spec *v1.PodSpec) string { return "0" } ii := ExtractImages(spec) - vul.ImgScanner.Enqueue(ii...) + vul.ImgScanner.Enqueue(context.Background(), ii...) return vul.ImgScanner.Score(ii...) } diff --git a/internal/render/job.go b/internal/render/job.go index 8fda057b98..89298d314d 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -74,13 +74,11 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { } func (Job) diagnose(ready string, completed *metav1.Time) error { - if completed == nil { - return nil - } tokens := strings.Split(ready, "/") if tokens[0] != tokens[1] { return fmt.Errorf("expecting %s completion got %s", tokens[1], tokens[0]) } + return nil } diff --git a/internal/render/pod.go b/internal/render/pod.go index f90e5c4f7b..710bd6eefd 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -335,7 +335,7 @@ func (p *Pod) Phase(po *v1.Pod) string { status = po.Status.Reason } - status, ok := p.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) + status, ok := p.initContainerPhase(po, status) if ok { return status } @@ -374,13 +374,16 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) { return status, running } -func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (string, bool) { - for i, cs := range st.InitContainerStatuses { - s := checkContainerStatus(cs, i, initCount) - if s == "" { - continue +func (*Pod) initContainerPhase(po *v1.Pod, status string) (string, bool) { + count := len(po.Spec.InitContainers) + rs := make(map[string]bool, count) + for _, c := range po.Spec.InitContainers { + rs[c.Name] = restartableInitCO(c.RestartPolicy) + } + for i, cs := range po.Status.InitContainerStatuses { + if s := checkInitContainerStatus(cs, i, count, rs[cs.Name]); s != "" { + return s, true } - return s, true } return status, false @@ -389,7 +392,7 @@ func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (s // ---------------------------------------------------------------------------- // Helpers.. -func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { +func checkInitContainerStatus(cs v1.ContainerStatus, count, initCount int, restartable bool) string { switch { case cs.State.Terminated != nil: if cs.State.Terminated.ExitCode == 0 { @@ -402,11 +405,15 @@ func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { return "Init:Signal:" + strconv.Itoa(int(cs.State.Terminated.Signal)) } return "Init:ExitCode:" + strconv.Itoa(int(cs.State.Terminated.ExitCode)) + case restartable && cs.Started != nil && *cs.Started: + if cs.Ready { + return "" + } case cs.State.Waiting != nil && cs.State.Waiting.Reason != "" && cs.State.Waiting.Reason != "PodInitializing": return "Init:" + cs.State.Waiting.Reason - default: - return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount) } + + return "Init:" + strconv.Itoa(count) + "/" + strconv.Itoa(initCount) } // PosStatus computes pod status. @@ -429,7 +436,7 @@ func PodStatus(pod *v1.Pod) string { case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0: continue case container.State.Terminated != nil: - if len(container.State.Terminated.Reason) == 0 { + if container.State.Terminated.Reason == "" { if container.State.Terminated.Signal != 0 { reason = fmt.Sprintf("Init:Signal:%d", container.State.Terminated.Signal) } else { @@ -494,3 +501,7 @@ func hasPodReadyCondition(conditions []v1.PodCondition) bool { return false } + +func restartableInitCO(p *v1.ContainerRestartPolicy) bool { + return p != nil && *p == v1.ContainerRestartPolicyAlways +} diff --git a/internal/render/pod_int_test.go b/internal/render/pod_int_test.go index 11b07cd587..74d9bcee2c 100644 --- a/internal/render/pod_int_test.go +++ b/internal/render/pod_int_test.go @@ -13,6 +13,288 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) +func Test_checkInitContainerStatus(t *testing.T) { + true := true + uu := map[string]struct { + status v1.ContainerStatus + e string + count, total int + restart bool + }{ + "none": { + e: "Init:0/0", + }, + "restart": { + status: v1.ContainerStatus{ + Name: "ic1", + Started: &true, + State: v1.ContainerState{}, + }, + restart: true, + e: "Init:0/0", + }, + "no-restart": { + status: v1.ContainerStatus{ + Name: "ic1", + Started: &true, + State: v1.ContainerState{}, + }, + e: "Init:0/0", + }, + "terminated-reason": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 1, + Reason: "blah", + }, + }, + }, + e: "Init:blah", + }, + "terminated-signal": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 1, + Signal: 9, + }, + }, + }, + e: "Init:Signal:9", + }, + "terminated-code": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 1, + }, + }, + }, + e: "Init:ExitCode:1", + }, + "terminated-restart": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: "blah", + }, + }, + }, + }, + "waiting": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: "blah", + }, + }, + }, + e: "Init:blah", + }, + "waiting-init": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: "PodInitializing", + }, + }, + }, + e: "Init:0/0", + }, + "running": { + status: v1.ContainerStatus{ + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + e: "Init:0/0", + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, checkInitContainerStatus(u.status, u.count, u.total, u.restart)) + }) + } +} + +func Test_containerPhase(t *testing.T) { + uu := map[string]struct { + status v1.PodStatus + e string + ok bool + }{ + "none": {}, + "empty": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + }, + }, + "waiting": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: "waiting", + }, + }, + }, + }, + }, + e: "waiting", + }, + "terminated": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: "done", + }, + }, + }, + }, + }, + e: "done", + }, + "terminated-sig": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Signal: 9, + }, + }, + }, + }, + }, + e: "Signal:9", + }, + "terminated-code": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 2, + }, + }, + }, + }, + }, + e: "ExitCode:2", + }, + "running": { + status: v1.PodStatus{ + Phase: PhaseUnknown, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + Ready: true, + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + }, + ok: true, + }, + } + + var p Pod + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + s, ok := p.containerPhase(u.status, "") + assert.Equal(t, u.ok, ok) + assert.Equal(t, u.e, s) + }) + } +} + +func Test_restartableInitCO(t *testing.T) { + always, never := v1.ContainerRestartPolicyAlways, v1.ContainerRestartPolicy("never") + uu := map[string]struct { + p *v1.ContainerRestartPolicy + e bool + }{ + "empty": {}, + "set": { + p: &always, + e: true, + }, + "unset": { + p: &never, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, restartableInitCO(u.p)) + }) + } +} + func Test_gatherPodMx(t *testing.T) { uu := map[string]struct { cc []v1.Container diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index f274f20621..ec2e058a78 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -227,11 +227,31 @@ func TestCheckPodStatus(t *testing.T) { }, e: render.PhaseRunning, }, - "backoff": { + "gated": { pod: v1.Pod{ Status: v1.PodStatus{ + Conditions: []v1.PodCondition{ + {Type: v1.PodScheduled, Reason: v1.PodReasonSchedulingGated}, + }, Phase: v1.PodRunning, InitContainerStatuses: []v1.ContainerStatus{}, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + }, + }, + e: v1.PodReasonSchedulingGated, + }, + + "backoff": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, ContainerStatuses: []v1.ContainerStatus{ { Name: "c1", @@ -246,6 +266,256 @@ func TestCheckPodStatus(t *testing.T) { }, e: render.PhaseImagePullBackOff, }, + "backoff-init": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + }, + }, + e: "Init:ImagePullBackOff", + }, + + "init-terminated-cool": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{}, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + }, + }, + e: "Init:0/0", + }, + + "init-terminated-reason": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 1, + Reason: "blah", + }, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + }, + }, + e: "Init:blah", + }, + "init-terminated-sig": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 2, + Signal: 9, + }, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + }, + }, + e: "Init:Signal:9", + }, + "init-terminated-code": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 2, + }, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: render.PhaseImagePullBackOff, + }, + }, + }, + }, + }, + }, + e: "Init:ExitCode:2", + }, + + "co-reason": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + Reason: "blah", + }, + }, + }, + }, + }, + }, + e: "blah", + }, + "co-reason-ready": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + Ready: true, + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + }, + }, + e: "Running", + }, + "co-reason-completed": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Conditions: []v1.PodCondition{ + {Type: v1.PodReady, Status: v1.ConditionTrue}, + }, + Phase: render.PhaseCompleted, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + Ready: true, + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + }, + }, + e: "Running", + }, + + "co-sig": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 2, + Signal: 9, + }, + }, + }, + }, + }, + }, + e: "Signal:9", + }, + "co-code": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Terminated: &v1.ContainerStateTerminated{ + ExitCode: 2, + }, + }, + }, + }, + }, + }, + e: "ExitCode:2", + }, + "co-ready": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: v1.PodRunning, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + }, + }, + e: "Running", + }, } for k := range uu { @@ -254,7 +524,123 @@ func TestCheckPodStatus(t *testing.T) { assert.Equal(t, u.e, render.PodStatus(&u.pod)) }) } +} +func TestCheckPhase(t *testing.T) { + always := v1.ContainerRestartPolicyAlways + uu := map[string]struct { + pod v1.Pod + e string + }{ + "unknown": { + pod: v1.Pod{ + Status: v1.PodStatus{ + Phase: render.PhaseUnknown, + }, + }, + e: render.PhaseUnknown, + }, + "terminating": { + pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &metav1.Time{Time: testTime()}, + }, + Status: v1.PodStatus{ + Phase: render.PhaseUnknown, + Reason: "bla", + }, + }, + e: render.PhaseTerminating, + }, + "terminating-toast-node": { + pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &metav1.Time{Time: testTime()}, + }, + Status: v1.PodStatus{ + Phase: render.PhaseUnknown, + Reason: render.NodeUnreachablePodReason, + }, + }, + e: render.PhaseUnknown, + }, + "restartable": { + pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &metav1.Time{Time: testTime()}, + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "ic1", + RestartPolicy: &always, + }, + }, + }, + Status: v1.PodStatus{ + Phase: render.PhaseUnknown, + Reason: "bla", + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + }, + }, + }, + }, + e: "Init:0/1", + }, + "waiting": { + pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + DeletionTimestamp: &metav1.Time{Time: testTime()}, + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + { + Name: "ic1", + RestartPolicy: &always, + }, + }, + Containers: []v1.Container{ + { + Name: "c1", + }, + }, + }, + Status: v1.PodStatus{ + Phase: render.PhaseUnknown, + Reason: "bla", + InitContainerStatuses: []v1.ContainerStatus{ + { + Name: "ic1", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{}, + }, + }, + }, + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "c1", + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{ + Reason: "bla", + }, + }, + }, + }, + }, + }, + e: "Init:0/1", + }, + } + + var p render.Pod + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, p.Phase(&u.pod)) + }) + } } // ---------------------------------------------------------------------------- diff --git a/internal/ui/config.go b/internal/ui/config.go index fb2191fff9..70435617d4 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -214,7 +214,7 @@ func (c *Configurator) activeConfig() (cluster string, context string, ok bool) if err != nil { return } - cluster, context = ct.ClusterName, c.Config.K9s.ActiveContextName() + cluster, context = ct.GetClusterName(), c.Config.K9s.ActiveContextName() if cluster != "" && context != "" { ok = true } @@ -254,14 +254,13 @@ func (c *Configurator) loadSkinFile(s synchronizer) { log.Debug().Msgf("Loading skin file: %q", skinFile) if err := c.Styles.Load(skinFile); err != nil { if errors.Is(err, os.ErrNotExist) { - s.Flash().Warnf("Skin file %q not found in skins dir: %s", filepath.Base(skinFile), config.AppSkinsDir) + log.Warn().Msgf("Skin file %q not found in skins dir: %s", filepath.Base(skinFile), config.AppSkinsDir) c.updateStyles("") } else { - s.Flash().Errf("Failed to parse skin file -- %s: %s.", filepath.Base(skinFile), err) + log.Error().Msgf("Failed to parse skin file -- %s: %s.", filepath.Base(skinFile), err) c.updateStyles(skinFile) } } else { - s.Flash().Infof("Skin file loaded: %q", skinFile) c.updateStyles(skinFile) } } diff --git a/internal/view/actions.go b/internal/view/actions.go index 60007ab7a8..5cbe9f2470 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -22,7 +22,7 @@ const AllScopes = "all" type Runner interface { App() *App GetSelectedItem() string - Aliases() []string + Aliases() map[string]struct{} EnvFn() EnvFunc } @@ -44,13 +44,13 @@ func includes(aliases []string, s string) bool { return false } -func inScope(scopes, aliases []string) bool { +func inScope(scopes []string, aliases map[string]struct{}) bool { if hasAll(scopes) { return true } for _, s := range scopes { - if includes(aliases, s) { - return true + if _, ok := aliases[s]; ok { + return ok } } @@ -119,8 +119,9 @@ func pluginActions(r Runner, aa ui.KeyActions) error { if err := pp.Load(r.App().Config.ContextPluginsPath()); err != nil { errs = errors.Join(errs, err) } + aliases := r.Aliases() for k, plugin := range pp.Plugins { - if !inScope(plugin.Scopes, r.Aliases()) { + if !inScope(plugin.Scopes, aliases) { continue } key, err := asKey(plugin.ShortCut) diff --git a/internal/view/actions_test.go b/internal/view/actions_test.go index 47176c0a7a..76f8abaf9c 100644 --- a/internal/view/actions_test.go +++ b/internal/view/actions_test.go @@ -53,15 +53,16 @@ func TestIncludes(t *testing.T) { func TestInScope(t *testing.T) { uu := map[string]struct { - ss, aa []string - e bool + ss []string + aa map[string]struct{} + e bool }{ "empty": {}, - "yes": {e: true, ss: []string{"blee", "duh", "fred"}, aa: []string{"blee", "fred", "duh"}}, - "no": {ss: []string{"blee", "duh", "fred"}, aa: []string{"blee1", "fred1"}}, - "empty scopes": {aa: []string{"blee1", "fred1"}}, + "yes": {e: true, ss: []string{"blee", "duh", "fred"}, aa: map[string]struct{}{"blee": {}, "fred": {}, "duh": {}}}, + "no": {ss: []string{"blee", "duh", "fred"}, aa: map[string]struct{}{"blee1": {}, "fred1": {}}}, + "empty scopes": {aa: map[string]struct{}{"blee1": {}, "fred1": {}}}, "empty aliases": {ss: []string{"blee1", "fred1"}}, - "all": {e: true, ss: []string{AllScopes}, aa: []string{"blee1", "fred1"}}, + "all": {e: true, ss: []string{AllScopes}, aa: map[string]struct{}{"blee1": {}, "fred1": {}}}, } for k := range uu { diff --git a/internal/view/app.go b/internal/view/app.go index 1a3bc67149..cdc9dd2f21 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -434,9 +434,6 @@ func (a *App) switchNS(ns string) error { if err := a.Config.SetActiveNamespace(ns); err != nil { return err } - if err := a.Config.Save(); err != nil { - return err - } return a.factory.SetActiveNS(ns) } @@ -517,6 +514,10 @@ func (a *App) BailOut() { } }() + if err := a.Config.Save(); err != nil { + log.Error().Err(err).Msg("config save failed!") + } + if err := nukeK9sShell(a); err != nil { log.Error().Err(err).Msgf("nuking k9s shell pod") } diff --git a/internal/view/browser.go b/internal/view/browser.go index 36503bee51..e688f54c5e 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -229,8 +229,8 @@ func (b *Browser) SetContextFn(f ContextFunc) { b.contextFn = f } func (b *Browser) GetTable() *Table { return b.Table } // Aliases returns all available aliases. -func (b *Browser) Aliases() []string { - return append(b.meta.ShortNames, b.meta.SingularName, b.meta.Name) +func (b *Browser) Aliases() map[string]struct{} { + return aliasesFor(b.meta, b.app.command.AliasesFor(b.meta.Name)) } // ---------------------------------------------------------------------------- @@ -449,9 +449,6 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { if err := b.app.Config.SetActiveNamespace(b.GetModel().GetNamespace()); err != nil { log.Error().Err(err).Msg("Config save NS failed!") } - if err := b.app.Config.Save(); err != nil { - log.Error().Err(err).Msg("Config save failed!") - } return nil } @@ -539,6 +536,8 @@ func (b *Browser) namespaceActions(aa ui.KeyActions) { if !b.meta.Namespaced || b.GetTable().Path != "" { return } + aa[ui.KeyN] = ui.NewKeyAction("Copy Namespace", b.cpNsCmd, false) + b.namespaces = make(map[int]string, data.MaxFavoritesNS) aa[ui.Key0] = ui.NewKeyAction(client.NamespaceAll, b.switchNamespaceCmd, true) b.namespaces[0] = client.NamespaceAll diff --git a/internal/view/command.go b/internal/view/command.go index 5ca68b69d9..3373fce0b6 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -37,6 +37,18 @@ func NewCommand(app *App) *Command { } } +// AliasesFor gather all known aliases for a given resource. +func (c *Command) AliasesFor(s string) []string { + aa := make([]string, 0, 10) + for k, v := range c.alias.Alias { + if v == s { + aa = append(aa, k) + } + } + + return aa +} + // Init initializes the command. func (c *Command) Init(path string) error { c.alias = dao.NewAlias(c.app.factory) @@ -128,9 +140,6 @@ func (c *Command) xrayCmd(p *cmd.Interpreter) error { if err := c.app.switchNS(ns); err != nil { return err } - if err := c.app.Config.Save(); err != nil { - return err - } return c.exec(p, client.NewGVR("xrays"), NewXray(gvr), true) } @@ -309,9 +318,6 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component, if clearStack { cmd := contextRX.ReplaceAllString(p.GetLine(), "") c.app.Config.SetActiveView(cmd) - if err := c.app.Config.Save(); err != nil { - log.Error().Err(err).Msg("Config save failed!") - } } if err := c.app.inject(comp, clearStack); err != nil { return err diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 90803dff31..70605969a6 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -23,8 +23,27 @@ import ( "github.com/derailed/tview" "github.com/rs/zerolog/log" "github.com/sahilm/fuzzy" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func aliasesFor(m v1.APIResource, aa []string) map[string]struct{} { + rr := make(map[string]struct{}) + rr[m.Name] = struct{}{} + for _, a := range aa { + rr[a] = struct{}{} + } + if m.ShortNames != nil { + for _, a := range m.ShortNames { + rr[a] = struct{}{} + } + } + if m.SingularName != "" { + rr[m.SingularName] = struct{}{} + } + + return rr +} + func clipboardWrite(text string) error { return clipboard.WriteAll(text) } diff --git a/internal/view/live_view.go b/internal/view/live_view.go index ce32382720..e97a0bc0f2 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -164,7 +164,7 @@ func (v *LiveView) bindKeys() { } if v.model != nil && v.model.GVR().IsDecodable() { v.actions.Add(ui.KeyActions{ - ui.KeyT: ui.NewKeyAction("Toggle Encoded / Decoded", v.toggleEncodedDecodedCmd, true), + ui.KeyX: ui.NewKeyAction("Toggle Decode", v.toggleEncodedDecodedCmd, true), }) } } diff --git a/internal/view/ns.go b/internal/view/ns.go index a9b9764f04..e86432fd30 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -9,7 +9,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" - "github.com/rs/zerolog/log" ) const ( @@ -69,11 +68,6 @@ func (n *Namespace) useNamespace(fqn string) { n.App().Flash().Err(err) return } - - n.App().Flash().Infof("Namespace %s is now active!", ns) - if err := n.App().Config.Save(); err != nil { - log.Error().Err(err).Msg("Config file save failed!") - } } func (n *Namespace) decorate(td *render.TableData) { diff --git a/internal/view/pod.go b/internal/view/pod.go index bb5959b47a..de1fc1fe35 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -116,7 +116,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) { } aa.Add(ui.KeyActions{ - ui.KeyN: ui.NewKeyAction("Show Node", p.showNode, true), + ui.KeyO: ui.NewKeyAction("Show Node", p.showNode, true), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), false), ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd(statusCol, true), false), diff --git a/internal/view/table.go b/internal/view/table.go index f0297d80ef..48bb9c9bdd 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -220,7 +220,22 @@ func (t *Table) cpCmd(evt *tcell.EventKey) *tcell.EventKey { t.app.Flash().Err(err) return nil } - t.app.Flash().Info("Current selection copied to clipboard...") + t.app.Flash().Info("Resource name copied to clipboard...") + + return nil +} + +func (t *Table) cpNsCmd(evt *tcell.EventKey) *tcell.EventKey { + path := t.GetSelectedItem() + if path == "" { + return evt + } + ns, _ := client.Namespaced(path) + if err := clipboardWrite(ns); err != nil { + t.app.Flash().Err(err) + return nil + } + t.app.Flash().Info("Resource namespace copied to clipboard...") return nil } diff --git a/internal/view/xray.go b/internal/view/xray.go index 694ddd84f1..8bab5c70c2 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -247,8 +247,8 @@ func (x *Xray) k9sEnv() Env { } // Aliases returns all available aliases. -func (x *Xray) Aliases() []string { - return append(x.meta.ShortNames, x.meta.SingularName, x.meta.Name) +func (x *Xray) Aliases() map[string]struct{} { + return aliasesFor(x.meta, x.app.command.AliasesFor(x.meta.Name)) } func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index 01f1f15424..aeed2f612f 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -4,6 +4,7 @@ package vul import ( + "context" "errors" "fmt" "sync" @@ -33,6 +34,12 @@ import ( var ImgScanner *imageScanner +const ( + imgChanSize = 3 + imgScanTimeout = 2 * time.Second + scanConcurrency = 2 +) + type imageScanner struct { store *store.Store dbCloser *db.Closer @@ -60,6 +67,7 @@ func (s *imageScanner) ShouldExcludes(m metav1.ObjectMeta) bool { func (s *imageScanner) GetScan(img string) (*Scan, bool) { s.mx.RLock() defer s.mx.RUnlock() + scan, ok := s.scans[img] return scan, ok @@ -106,6 +114,7 @@ func (s *imageScanner) Stop() { if s.dbCloser != nil { s.dbCloser.Close() + s.dbCloser = nil } } @@ -127,27 +136,35 @@ func (s *imageScanner) isInitialized() bool { return s.initialized } -func (s *imageScanner) Enqueue(images ...string) { +func (s *imageScanner) Enqueue(ctx context.Context, images ...string) { if !s.isInitialized() { return } - for _, i := range images { - go func(img string) { - if _, ok := s.GetScan(img); ok { - return - } - sc := newScan(img) - s.setScan(img, sc) - if err := s.scan(img, sc); err != nil { - log.Warn().Err(err).Msgf("Scan failed for img %s --", img) - } - }(i) + ctx, cancel := context.WithTimeout(ctx, imgScanTimeout) + defer cancel() + + for _, img := range images { + if _, ok := s.GetScan(img); ok { + continue + } + go s.scanWorker(ctx, img) + } +} + +func (s *imageScanner) scanWorker(ctx context.Context, img string) { + defer log.Debug().Msgf("ScanWorker bailing out!") + + log.Debug().Msgf("ScanWorker processing: %q", img) + sc := newScan(img) + s.setScan(img, sc) + if err := s.scan(ctx, img, sc); err != nil { + log.Warn().Err(err).Msgf("Scan failed for img %s --", img) } } -func (s *imageScanner) scan(img string, sc *Scan) error { +func (s *imageScanner) scan(ctx context.Context, img string, sc *Scan) error { defer func(t time.Time) { - log.Debug().Msgf("Scan %s images: %v", img, time.Since(t)) + log.Debug().Msgf("ScanTime %q: %v", img, time.Since(t)) }(time.Now()) var errs error diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 70e77da71b..918ed46a11 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.7' +version: 'v0.31.8' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 9ca46ab027dc624d88d412cbb4699740892fddea Mon Sep 17 00:00:00 2001 From: Rabin Yasharzadehe Date: Wed, 7 Feb 2024 18:17:22 +0200 Subject: [PATCH 096/169] Add liveMigration example to plugins (#2522) --- plugins/liveMigration.yaml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 plugins/liveMigration.yaml diff --git a/plugins/liveMigration.yaml b/plugins/liveMigration.yaml new file mode 100644 index 0000000000..2ddd08cfc7 --- /dev/null +++ b/plugins/liveMigration.yaml @@ -0,0 +1,34 @@ +# $XDG_CONFIG_HOME/k9s/plugins.yaml +plugins: + # liveMigration plugin config by rabin-io + # + # Trigger virtual machine live migration, for VM's running on k8s cluster using kubevirt + # or Openshift with CNV (OpenShift Virtualization) installed. + # + # Require `virtctl` cli in your PATH, + # can be downloaded from Openshift `Command Line Tools` page + # or from kubevirt site https://kubevirt.io/user-guide/operations/virtctl_client_tool/ + # + # + liveMigration: + # Can be triggered from the VMI (VirtualMachineInstance) view, with shortcut `m` + shortCut: m + # Description to show in K9s menu + description: Live Migrate moves VM to another compute node + # Enable confirmation dialog + confirm: true + # Collections of views that support this shortcut. (You can use `all`) + scopes: + - virtualmachineinstance + # Whether or not to run the command in background mode + background: false + # The command to run upon invocation. + command: virtctl + # Defines the command arguments + args: + - migrate + - $NAME + - -n + - $NAMESPACE + - --context + - $CONTEXT From 914bffc0a7c8baed3981b55307d6f7f22742b2c9 Mon Sep 17 00:00:00 2001 From: Othmane EL MASSARI <47815944+othmane399@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:19:48 +0100 Subject: [PATCH 097/169] feat: allow override of plugins & hotkeys shortcuts (#2510) * feat: allow override of plugins & hotkeys shortcuts * add docs and log.info when overrides * edit log message * fix doc * Update README.md --- README.md | 9 ++++++--- internal/config/hotkey.go | 1 + internal/config/json/schemas/hotkeys.json | 1 + internal/config/json/schemas/plugins.json | 1 + internal/config/plugin.go | 1 + internal/view/actions.go | 9 +++++++-- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b63cf1613..efb02d3155 100644 --- a/README.md +++ b/README.md @@ -532,9 +532,10 @@ In order to surface hotkeys globally please follow these steps: shortCut: Shift-2 description: Xray Deployments command: xray deploy - # Hitting Ctrl-U view the resources in the namespace of your current selection - ctrl-u: - shortCut: Ctrl-U + # Hitting Shift-S view the resources in the namespace of your current selection + shift-s: + shortCut: Shift-S + override: true # => will override the default shortcut related action if set to true (default to false) description: Namespaced resources command: "$RESOURCE_NAME $NAMESPACE" keepHistory: true # whether you can return to the previous view @@ -642,6 +643,7 @@ K9s allows you to extend your command line and tooling by defining your very own A plugin is defined as follows: * Shortcut option represents the key combination a user would type to activate the plugin +* Override option make that the default action related to the shortcut will be overrided by the plugin * Confirm option (when enabled) lets you see the command that is going to be executed and gives you an option to confirm or prevent execution * Description will be printed next to the shortcut in the k9s menu * Scopes defines a collection of resources names/short-names for the views associated with the plugin. You can specify `all` to provide this shortcut for all views. @@ -678,6 +680,7 @@ plugins: # Defines a plugin to provide a `ctrl-l` shortcut to tail the logs while in pod view. fred: shortCut: Ctrl-L + override: false confirm: false description: Pod logs scopes: diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index 4aec92acdb..91801eb8b7 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -20,6 +20,7 @@ type HotKeys struct { // HotKey describes a K9s hotkey. type HotKey struct { ShortCut string `yaml:"shortCut"` + Override bool `yaml:"override"` Description string `yaml:"description"` Command string `yaml:"command"` KeepHistory bool `yaml:"keepHistory"` diff --git a/internal/config/json/schemas/hotkeys.json b/internal/config/json/schemas/hotkeys.json index 627730ffd7..49906422d3 100644 --- a/internal/config/json/schemas/hotkeys.json +++ b/internal/config/json/schemas/hotkeys.json @@ -10,6 +10,7 @@ "type": "object", "properties": { "shortCut": {"type": "string"}, + "override": { "type": "boolean" }, "description": {"type": "string"}, "command": {"type": "string"}, "keepHistory": {"type": "boolean"} diff --git a/internal/config/json/schemas/plugins.json b/internal/config/json/schemas/plugins.json index 3445bd165b..5c41eb4883 100644 --- a/internal/config/json/schemas/plugins.json +++ b/internal/config/json/schemas/plugins.json @@ -11,6 +11,7 @@ "additionalProperties": false, "properties": { "shortCut": { "type": "string" }, + "override": { "type": "boolean" }, "description": { "type": "string" }, "confirm": { "type": "boolean" }, "scopes": { diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 7b111294ad..e0992fe5d6 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -29,6 +29,7 @@ type Plugin struct { Scopes []string `yaml:"scopes"` Args []string `yaml:"args"` ShortCut string `yaml:"shortCut"` + Override bool `yaml:"override"` Pipes []string `yaml:"pipes"` Description string `yaml:"description"` Command string `yaml:"command"` diff --git a/internal/view/actions.go b/internal/view/actions.go index 5cbe9f2470..2a1f8b1cb4 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -76,9 +76,11 @@ func hotKeyActions(r Runner, aa ui.KeyActions) error { continue } _, ok := aa[key] - if ok { + if ok && !hk.Override { errs = errors.Join(errs, fmt.Errorf("duplicated hotkeys found for %q in %q", hk.ShortCut, k)) continue + } else if ok && hk.Override == true { + log.Info().Msgf("Action %q has been overrided by hotkey in %q", hk.ShortCut, k) } command, err := r.EnvFn()().Substitute(hk.Command) @@ -125,14 +127,17 @@ func pluginActions(r Runner, aa ui.KeyActions) error { continue } key, err := asKey(plugin.ShortCut) + if err != nil { errs = errors.Join(errs, err) continue } _, ok := aa[key] - if ok { + if ok && !plugin.Override { errs = errors.Join(errs, fmt.Errorf("duplicated plugin key found for %q in %q", plugin.ShortCut, k)) continue + } else if ok && plugin.Override == true { + log.Info().Msgf("Action %q has been overrided by plugin in %q", plugin.ShortCut, k) } aa[key] = ui.NewKeyActionWithOpts( plugin.Description, From c9273251846f2ecd63d08fc5b84e45e8c9112d7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:11:04 -0700 Subject: [PATCH 098/169] Bump helm.sh/helm/v3 from 3.14.0 to 3.14.1 (#2541) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.14.0 to 3.14.1. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.14.0...v3.14.1) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a395caa623..f6e554fb61 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.14.0 + helm.sh/helm/v3 v3.14.1 k8s.io/api v0.29.1 k8s.io/apiextensions-apiserver v0.29.1 k8s.io/apimachinery v0.29.1 diff --git a/go.sum b/go.sum index 6985a7727d..59b5e9e244 100644 --- a/go.sum +++ b/go.sum @@ -1818,8 +1818,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.14.0 h1:TaZIH6uOchn7L27ptwnnuHJiFrT/BsD4dFdp/HLT2nM= -helm.sh/helm/v3 v3.14.0/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= +helm.sh/helm/v3 v3.14.1 h1:4AwRLx+wfzlPtvrsbDmWP5PUokGmf9/nAmEdk21vae8= +helm.sh/helm/v3 v3.14.1/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 87a672b1835cadb411b9ac6d1a4928a5a9a3d1c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:12:51 -0700 Subject: [PATCH 099/169] Bump golangci/golangci-lint-action from 3.7.0 to 4.0.0 (#2534) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.7.0 to 4.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/3.7.0...v4.0.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bebd4c743a..7c9cdad467 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: cache-dependency-path: go.sum - name: Lint - uses: golangci/golangci-lint-action@3.7.0 + uses: golangci/golangci-lint-action@v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check \ No newline at end of file From b55c094f933f99832df06b5c614a6f5bdf90c675 Mon Sep 17 00:00:00 2001 From: doy-materialize <110416385+doy-materialize@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:13:52 -0500 Subject: [PATCH 100/169] fix the --write flag (#2531) --write sets k.manualReadOnly to false, but this was previously setting the read only state to true if k.manualReadOnly was set at all --- internal/config/k9s.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 72e4a34b6a..a1b45558b9 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -304,7 +304,7 @@ func (k *K9s) IsReadOnly() bool { ro = *cfg.Context.ReadOnly } if k.manualReadOnly != nil { - ro = true + ro = *k.manualReadOnly } return ro From 8e7b99750d1f99b1340ce4b40b824aa4a846a79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Tomaszkiewicz?= Date: Thu, 15 Feb 2024 18:17:37 +0100 Subject: [PATCH 101/169] fix: issue 2175 - fallback to env names when cannot get username from user.Current() (#2505) --- internal/config/helpers.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/config/helpers.go b/internal/config/helpers.go index af04c2fbe5..a62b3cd9c2 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -58,6 +58,14 @@ func InNSList(nn []interface{}, ns string) bool { func MustK9sUser() string { usr, err := user.Current() if err != nil { + envUsr := os.Getenv("USER") + if envUsr != "" { + return envUsr + } + envUsr = os.Getenv("LOGNAME") + if envUsr != "" { + return envUsr + } log.Fatal().Err(err).Msg("Die on retrieving user info") } return usr.Username From 207d05615a79051edda188089447b418fc96ea1b Mon Sep 17 00:00:00 2001 From: Artem Musalitin <38328305+MrRTi@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:18:59 +0100 Subject: [PATCH 102/169] Add moon and dawn variants (#2537) --- skins/rose-pine-dawn.yaml | 110 ++++++++++++++++++++++++++++++++++++++ skins/rose-pine-moon.yaml | 110 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 skins/rose-pine-dawn.yaml create mode 100644 skins/rose-pine-moon.yaml diff --git a/skins/rose-pine-dawn.yaml b/skins/rose-pine-dawn.yaml new file mode 100644 index 0000000000..da4edfd236 --- /dev/null +++ b/skins/rose-pine-dawn.yaml @@ -0,0 +1,110 @@ +# ----------------------------------------------------------------------------- +# Rose Pine Dawn +# https://rosepinetheme.com/palette/ingredients/ +# ----------------------------------------------------------------------------- +# +text: &text "#575279" +base: &base "#faf4ed" +overlay: &overlay "#f2e9e1" +muted: &muted "#9893a5" +rose: &rose "#d7827e" +pine: &pine "#286983" +gold: &gold "#ea9d34" +iris: &iris "#907aa9" +love: &love "#b4637a" + +# Skin... +k9s: + # General K9s styles + body: + fgColor: *text + bgColor: *base + logoColor: *iris + # Command prompt styles + prompt: + fgColor: *text + bgColor: *base + suggestColor: *iris + # ClusterInfoView styles. + info: + fgColor: *iris + sectionColor: *text + # Dialog styles. + dialog: + fgColor: *text + bgColor: *base + buttonFgColor: *text + buttonBgColor: *iris + buttonFocusFgColor: *gold + buttonFocusBgColor: *iris + labelFgColor: *gold + fieldFgColor: *text + frame: + # Borders styles. + border: + fgColor: *overlay + focusColor: *overlay + menu: + fgColor: *text + keyColor: *iris + # Used for favorite namespaces + numKeyColor: *iris + # CrumbView attributes for history navigation. + crumbs: + fgColor: *text + bgColor: *overlay + activeColor: *overlay + # Resource status and update styles + status: + newColor: *rose + modifyColor: *iris + addColor: *pine + errorColor: *love + highlightcolor: *gold + killColor: *muted + completedColor: *muted + # Border title styles. + title: + fgColor: *text + bgColor: *overlay + highlightColor: *gold + counterColor: *iris + filterColor: *iris + views: + # Charts skins... + charts: + bgColor: default + defaultDialColors: + - *iris + - *love + defaultChartColors: + - *iris + - *love + # TableView attributes. + table: + fgColor: *text + bgColor: *base + # Header row styles. + header: + fgColor: *text + bgColor: *base + sorterColor: *rose + # Xray view attributes. + xray: + fgColor: *text + bgColor: *base + cursorColor: *overlay + graphicColor: *iris + showIcons: false + # YAML info styles. + yaml: + keyColor: *iris + colonColor: *iris + valueColor: *text + # Logs styles. + logs: + fgColor: *text + bgColor: *base + indicator: + fgColor: *text + bgColor: *iris diff --git a/skins/rose-pine-moon.yaml b/skins/rose-pine-moon.yaml new file mode 100644 index 0000000000..df7e36317a --- /dev/null +++ b/skins/rose-pine-moon.yaml @@ -0,0 +1,110 @@ +# ----------------------------------------------------------------------------- +# Rose Pine Main +# https://rosepinetheme.com/palette/ingredients/ +# ----------------------------------------------------------------------------- +# +text: &text "#e0def4" +base: &base "#232136" +overlay: &overlay "#393552" +muted: &muted "#6e6a86" +rose: &rose "#ea9a97" +pine: &pine "#3e8fb0" +gold: &gold "#f6c177" +iris: &iris "#c4a7e7" +love: &love "#eb6f92" + +# Skin... +k9s: + # General K9s styles + body: + fgColor: *text + bgColor: *base + logoColor: *iris + # Command prompt styles + prompt: + fgColor: *text + bgColor: *base + suggestColor: *iris + # ClusterInfoView styles. + info: + fgColor: *iris + sectionColor: *text + # Dialog styles. + dialog: + fgColor: *text + bgColor: *base + buttonFgColor: *text + buttonBgColor: *iris + buttonFocusFgColor: *gold + buttonFocusBgColor: *iris + labelFgColor: *gold + fieldFgColor: *text + frame: + # Borders styles. + border: + fgColor: *overlay + focusColor: *overlay + menu: + fgColor: *text + keyColor: *iris + # Used for favorite namespaces + numKeyColor: *iris + # CrumbView attributes for history navigation. + crumbs: + fgColor: *text + bgColor: *overlay + activeColor: *overlay + # Resource status and update styles + status: + newColor: *rose + modifyColor: *iris + addColor: *pine + errorColor: *love + highlightcolor: *gold + killColor: *muted + completedColor: *muted + # Border title styles. + title: + fgColor: *text + bgColor: *overlay + highlightColor: *gold + counterColor: *iris + filterColor: *iris + views: + # Charts skins... + charts: + bgColor: default + defaultDialColors: + - *iris + - *love + defaultChartColors: + - *iris + - *love + # TableView attributes. + table: + fgColor: *text + bgColor: *base + # Header row styles. + header: + fgColor: *text + bgColor: *base + sorterColor: *rose + # Xray view attributes. + xray: + fgColor: *text + bgColor: *base + cursorColor: *overlay + graphicColor: *iris + showIcons: false + # YAML info styles. + yaml: + keyColor: *iris + colonColor: *iris + valueColor: *text + # Logs styles. + logs: + fgColor: *text + bgColor: *base + indicator: + fgColor: *text + bgColor: *iris From f2f4077b592dcbb4162cbfe07bd99546d47d9955 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Thu, 15 Feb 2024 17:43:29 -0700 Subject: [PATCH 103/169] K9s/release v0.31.9 (#2543) * [Bug] fix #2535 * [Bug] fix #2532 * [Bug] fix #2536 * [Bug] fix #2533 * [Bug] fix #2538 * [Maint] cleaning up * Release notes --- Makefile | 2 +- change_logs/release_v0.31.9.md | 98 ++++++++++ internal/client/client.go | 15 +- internal/client/client_test.go | 2 +- internal/client/metrics.go | 2 +- internal/client/types.go | 2 +- internal/config/alias.go | 14 ++ internal/config/mock/test_helpers.go | 2 +- internal/dao/alias.go | 4 + internal/dao/container_test.go | 16 +- internal/dao/cronjob.go | 6 +- internal/dao/dp.go | 6 +- internal/dao/ds.go | 4 +- internal/dao/generic.go | 2 +- internal/dao/node.go | 7 +- internal/dao/pod.go | 7 +- internal/dao/popeye.go | 275 ++++++++++++++------------- internal/dao/port_forwarder.go | 4 +- internal/dao/registry.go | 9 +- internal/dao/sts.go | 8 +- internal/dao/workload.go | 2 +- internal/model/registry.go | 17 +- internal/ui/indicator.go | 2 +- internal/view/actions.go | 26 +-- internal/view/app.go | 2 + internal/view/browser.go | 6 +- internal/view/cluster_info.go | 2 +- internal/view/command.go | 16 +- internal/view/drain_dialog.go | 14 +- internal/view/exec.go | 7 +- internal/view/node.go | 34 ++-- internal/watch/factory.go | 2 +- snap/snapcraft.yaml | 2 +- 33 files changed, 376 insertions(+), 241 deletions(-) create mode 100644 change_logs/release_v0.31.9.md diff --git a/Makefile b/Makefile index d464c796f5..2382ab3354 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.8 +VERSION ?= v0.31.9 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.31.9.md b/change_logs/release_v0.31.9.md new file mode 100644 index 0000000000..25273f8813 --- /dev/null +++ b/change_logs/release_v0.31.9.md @@ -0,0 +1,98 @@ + + +# Release v0.31.9 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +```text +S .-'-. + o __| F `\ + S `-,-`--._ `\ + [] .->' X `|-' + `=/ (__/_ / + \_, ` _) + `----; | +``` + +⛔️ WE HAVE A PIPER DOWN! I REPEAT PIPER IS DOWN!! ⛔️ + +Popeye is undergoing heavy surgery at the moment so I had to break the bridge. +If you dig Popeye please run the binary separately for the time being. +I'll post another message here once the spinach formula upgrade is successful! + +Also please make sure to add the gory details to issues ie relevant configs, debug logs, etc... +Comments like: `same here!` or `me to!` doesn't really cut it for us to zero in ;( + +Everyone has slightly different settings/platforms so every little bits of info helps with the resolves even if seemingly irrelevant. + +Thank you all for pitching in and helping flesh out issues!! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## ♫ Sounds Behind The Release ♭ + +Ushered or Taylored out? + +* [Rough God Goes Riding - Van Morrison](https://www.youtube.com/watch?v=-kGrwRlJxcM) +* [Walk On - John Hiatt](https://www.youtube.com/watch?v=YVdMyeTQCkw) +* [On The Beach - Neil Young](https://www.youtube.com/watch?v=KBVde75e4sU) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Francis Lalonde](https://github.com/f-lalonde) +* [e-conomic a/s](https://github.com/e-conomic) + +> Sponsorship cancellations since the last release: **2!** 🥹 + +--- + +## Resolved Issues + +* [#2540](https://github.com/derailed/k9s/issues/2540) Option --write not functional +* [#2538](https://github.com/derailed/k9s/issues/2538) Opening screen dumps (sd) in K9s results in Failed to launch editor error message +* [#2536](https://github.com/derailed/k9s/issues/2536) Recent namespaces are lost when changing context +* [#2535](https://github.com/derailed/k9s/issues/2535) Namespaced configmap edit fails for user with RoleBinding to a role that allows it +* [#2532](https://github.com/derailed/k9s/issues/2532) Sporadic crashes (Maybe??) + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2541](https://github.com/derailed/k9s/pull/2541) Add Rose Pine moon and dawn variants to skins +* [#2531](https://github.com/derailed/k9s/pull/2531) fix the --write flag +* [#2516](https://github.com/derailed/k9s/pull/2516) Added defaultsToFullScreen flag for Live/Details view,logs + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/internal/client/client.go b/internal/client/client.go index 981e5db9f6..85946451ed 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -84,7 +84,7 @@ func (a *APIClient) ConnectionOK() bool { return a.connOK } -func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { +func makeSAR(ns, gvr, name string) *authorizationv1.SelfSubjectAccessReview { if ns == ClusterScope { ns = BlankNamespace } @@ -98,13 +98,14 @@ func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { Version: res.Version, Resource: res.Resource, Subresource: spec.SubResource(), + Name: name, }, }, } } -func makeCacheKey(ns, gvr string, vv []string) string { - return ns + ":" + gvr + "::" + strings.Join(vv, ",") +func makeCacheKey(ns, gvr, n string, vv []string) string { + return ns + ":" + gvr + ":" + n + "::" + strings.Join(vv, ",") } // ActiveContext returns the current context name. @@ -142,14 +143,14 @@ func (a *APIClient) clearCache() { } // CanI checks if user has access to a certain resource. -func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) { +func (a *APIClient) CanI(ns, gvr, name string, verbs []string) (auth bool, err error) { if !a.getConnOK() { return false, errors.New("ACCESS -- No API server connection") } if IsClusterWide(ns) { ns = BlankNamespace } - key := makeCacheKey(ns, gvr, verbs) + key := makeCacheKey(ns, gvr, name, verbs) if v, ok := a.cache.Get(key); ok { if auth, ok = v.(bool); ok { return auth, nil @@ -160,7 +161,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) if err != nil { return false, err } - client, sar := dial.AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr) + client, sar := dial.AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr, name) ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout()) defer cancel() @@ -215,7 +216,7 @@ func (a *APIClient) IsValidNamespace(ns string) bool { return true } - ok, err := a.CanI(ClusterScope, "v1/namespaces", []string{ListVerb}) + ok, err := a.CanI(ClusterScope, "v1/namespaces", "", []string{ListVerb}) if ok && err == nil { nn, _ := a.ValidNamespaceNames() _, ok = nn[ns] diff --git a/internal/client/client_test.go b/internal/client/client_test.go index e5c17ddd71..e60634880e 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -74,7 +74,7 @@ func TestMakeSAR(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr.String())) + assert.Equal(t, u.sar, makeSAR(u.ns, u.gvr.String(), "")) }) } } diff --git a/internal/client/metrics.go b/internal/client/metrics.go index c31ba6c5b0..13485c71a4 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -93,7 +93,7 @@ func (m *MetricsServer) checkAccess(ns, gvr, msg string) error { return errors.New("no metrics-server detected on cluster") } - auth, err := m.CanI(ns, gvr, ListAccess) + auth, err := m.CanI(ns, gvr, "", ListAccess) if err != nil { return err } diff --git a/internal/client/types.go b/internal/client/types.go index c6bcf760a1..8dad38a06a 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -80,7 +80,7 @@ type PodsMetricsMap map[string]*mv1beta1.PodMetrics // Authorizer checks what a user can or cannot do to a resource. type Authorizer interface { // CanI returns true if the user can use these actions for a given resource. - CanI(ns, gvr string, verbs []string) (bool, error) + CanI(ns, gvr, n string, verbs []string) (bool, error) } // Connection represents a Kubernetes apiserver connection. diff --git a/internal/config/alias.go b/internal/config/alias.go index 21cf26ba18..35cd8e788c 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -33,6 +33,20 @@ func NewAliases() *Aliases { } } +func (a *Aliases) AliasesFor(s string) []string { + aa := make([]string, 0, 10) + + a.mx.RLock() + defer a.mx.RUnlock() + for k, v := range a.Alias { + if v == s { + aa = append(aa, k) + } + } + + return aa +} + // Keys returns all aliases keys. func (a *Aliases) Keys() []string { a.mx.RLock() diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index a20db334c5..c5a3b69a75 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -115,7 +115,7 @@ func NewMockConnectionWithContext(ct string) mockConnection { return mockConnection{ct: ct} } -func (m mockConnection) CanI(ns, gvr string, verbs []string) (bool, error) { +func (m mockConnection) CanI(ns, gvr, n string, verbs []string) (bool, error) { return true, nil } func (m mockConnection) Config() *client.Config { diff --git a/internal/dao/alias.go b/internal/dao/alias.go index f07b86c655..bf4bf308d5 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -37,6 +37,10 @@ func NewAlias(f Factory) *Alias { return &a } +func (a *Alias) AliasesFor(s string) []string { + return a.Aliases.AliasesFor(s) +} + // Check verifies an alias is defined for this command. func (a *Alias) Check(cmd string) (string, bool) { return a.Aliases.Get(cmd) diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index 38a24dd815..f53c82959d 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -62,14 +62,14 @@ func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return n func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error) { return "", false, nil } -func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil } -func (c *conn) CurrentNamespaceName() (string, error) { return "", nil } -func (c *conn) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil } -func (c *conn) ActiveContext() string { return "" } -func (c *conn) ActiveNamespace() string { return "" } -func (c *conn) IsValidNamespace(string) bool { return true } -func (c *conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil } -func (c *conn) IsActiveNamespace(string) bool { return false } +func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil } +func (c *conn) CurrentNamespaceName() (string, error) { return "", nil } +func (c *conn) CanI(ns, gvr, n string, verbs []string) (bool, error) { return true, nil } +func (c *conn) ActiveContext() string { return "" } +func (c *conn) ActiveNamespace() string { return "" } +func (c *conn) IsValidNamespace(string) bool { return true } +func (c *conn) ValidNamespaceNames() (client.NamespaceNames, error) { return nil, nil } +func (c *conn) IsActiveNamespace(string) bool { return false } type podFactory struct{} diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 6feee0ca11..b02b95759e 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -47,8 +47,8 @@ func (c *CronJob) ListImages(ctx context.Context, fqn string) ([]string, error) // Run a CronJob. func (c *CronJob) Run(path string) error { - ns, _ := client.Namespaced(path) - auth, err := c.Client().CanI(ns, jobGVR, []string{client.GetVerb, client.CreateVerb}) + ns, n := client.Namespaced(path) + auth, err := c.Client().CanI(ns, jobGVR, n, []string{client.GetVerb, client.CreateVerb}) if err != nil { return err } @@ -144,7 +144,7 @@ func (c *CronJob) GetInstance(fqn string) (*batchv1.CronJob, error) { // ToggleSuspend toggles suspend/resume on a CronJob. func (c *CronJob) ToggleSuspend(ctx context.Context, path string) error { ns, n := client.Namespaced(path) - auth, err := c.Client().CanI(ns, c.GVR(), []string{client.GetVerb, client.UpdateVerb}) + auth, err := c.Client().CanI(ns, c.GVR(), n, []string{client.GetVerb, client.UpdateVerb}) if err != nil { return err } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 4ab1a4badb..4f88d4a02a 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -57,7 +57,7 @@ func (d *Deployment) IsHappy(dp appsv1.Deployment) bool { // Scale a Deployment. func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{client.GetVerb, client.UpdateVerb}) + auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", n, []string{client.GetVerb, client.UpdateVerb}) if err != nil { return err } @@ -91,7 +91,7 @@ func (d *Deployment) Restart(ctx context.Context, path string) error { return err } - auth, err := d.Client().CanI(dp.Namespace, "apps/v1/deployments", []string{client.PatchVerb}) + auth, err := d.Client().CanI(dp.Namespace, "apps/v1/deployments", dp.Name, []string{client.PatchVerb}) if err != nil { return err } @@ -266,7 +266,7 @@ func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/deployments", []string{client.PatchVerb}) + auth, err := d.Client().CanI(ns, "apps/v1/deployments", n, []string{client.PatchVerb}) if err != nil { return err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 1488960762..0e2d9430a0 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -68,7 +68,7 @@ func (d *DaemonSet) Restart(ctx context.Context, path string) error { return err } - auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", []string{client.PatchVerb}) + auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", ds.Name, []string{client.PatchVerb}) if err != nil { return err } @@ -285,7 +285,7 @@ func (d *DaemonSet) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/daemonset", []string{client.PatchVerb}) + auth, err := d.Client().CanI(ns, "apps/v1/daemonset", n, []string{client.PatchVerb}) if err != nil { return err } diff --git a/internal/dao/generic.go b/internal/dao/generic.go index cf579cf7e8..e0aff2b649 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -106,7 +106,7 @@ func (g *Generic) ToYAML(path string, showManaged bool) (string, error) { // Delete deletes a resource. func (g *Generic) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error { ns, n := client.Namespaced(path) - auth, err := g.Client().CanI(ns, g.gvrStr(), []string{client.DeleteVerb}) + auth, err := g.Client().CanI(ns, g.gvrStr(), n, []string{client.DeleteVerb}) if err != nil { return err } diff --git a/internal/dao/node.go b/internal/dao/node.go index da5c8ddfba..1ee29ed143 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -106,7 +106,7 @@ func (n *Node) Drain(path string, opts DrainOptions, w io.Writer) error { dd, errs := h.GetPodsForDeletion(path) if len(errs) != 0 { for _, e := range errs { - if _, err := h.ErrOut.Write([]byte(e.Error() + "\n")); err != nil { + if _, err := h.ErrOut.Write([]byte(fmt.Sprintf("[%s] %s\n", path, e.Error()))); err != nil { return err } } @@ -247,7 +247,8 @@ func (n *Node) ensureCordoned(path string) (bool, error) { // FetchNode retrieves a node. func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) { - auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", []string{"get"}) + _, n := client.Namespaced(path) + auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", n, []string{"get"}) if err != nil { return nil, err } @@ -271,7 +272,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) { // FetchNodes retrieves all nodes. func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) { - auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", []string{client.ListVerb}) + auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", "", []string{client.ListVerb}) if err != nil { return nil, err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index f0bd20ad06..e9c6e09f46 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -132,8 +132,8 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { // Logs fetch container logs for a given pod and container. func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) { - ns, _ := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods:log", []string{client.GetVerb}) + ns, n := client.Namespaced(path) + auth, err := p.Client().CanI(ns, "v1/pods:log", n, []string{client.GetVerb}) if err != nil { return nil, err } @@ -141,7 +141,6 @@ func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, er return nil, fmt.Errorf("user is not authorized to view pod logs") } - ns, n := client.Namespaced(path) dial, err := p.Client().DialLogs() if err != nil { return nil, err @@ -457,7 +456,7 @@ func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pod", []string{client.PatchVerb}) + auth, err := p.Client().CanI(ns, "v1/pod", n, []string{client.PatchVerb}) if err != nil { return err } diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index e940f1b561..ec1aa801d3 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -3,140 +3,141 @@ package dao -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "sort" - "time" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - cfg "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/popeye/pkg" - "github.com/derailed/popeye/pkg/config" - "github.com/derailed/popeye/types" - "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/runtime" -) - -var _ Accessor = (*Popeye)(nil) - -// Popeye tracks cluster sanitization. -type Popeye struct { - NonResource -} - -// NewPopeye returns a new set of aliases. -func NewPopeye(f Factory) *Popeye { - a := Popeye{} - a.Init(f, client.NewGVR("popeye")) - - return &a -} - -type readWriteCloser struct { - *bytes.Buffer -} - -// Close close read stream. -func (readWriteCloser) Close() error { - return nil -} - -// List returns a collection of aliases. -func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) { - defer func(t time.Time) { - log.Debug().Msgf("Popeye -- Elapsed %v", time.Since(t)) - if err := recover(); err != nil { - log.Debug().Msgf("POPEYE DIED!") - } - }(time.Now()) - - flags, js := config.NewFlags(), "json" - flags.Output = &js - flags.ActiveNamespace = &ns - - if report, ok := ctx.Value(internal.KeyPath).(string); ok && report != "" { - ns, n := client.Namespaced(report) - sections := []string{n} - flags.Sections = §ions - flags.ActiveNamespace = &ns - } - spinach := filepath.Join(cfg.AppConfigDir, "spinach.yaml") - if c, err := p.getFactory().Client().Config().CurrentContextName(); err == nil { - spinach = filepath.Join(cfg.AppConfigDir, fmt.Sprintf("%s_spinach.yaml", c)) - } - if _, err := os.Stat(spinach); err == nil { - flags.Spinach = &spinach - } - - popeye, err := pkg.NewPopeye(flags, &log.Logger) - if err != nil { - return nil, err - } - popeye.SetFactory(newPopeyeFactory(p.Factory)) - if err = popeye.Init(); err != nil { - return nil, err - } - - buff := readWriteCloser{Buffer: bytes.NewBufferString("")} - popeye.SetOutputTarget(buff) - if _, _, err = popeye.Sanitize(); err != nil { - log.Error().Err(err).Msgf("BOOM %#v", *flags.Sections) - return nil, err - } - - var b render.Builder - if err = json.Unmarshal(buff.Bytes(), &b); err != nil { - return nil, err - } - - oo := make([]runtime.Object, 0, len(b.Report.Sections)) - sort.Sort(b.Report.Sections) - for _, s := range b.Report.Sections { - s.Tally.Count = len(s.Outcome) - if s.Tally.Sum() > 0 { - oo = append(oo, s) - } - } - - return oo, nil -} - -// Get retrieves a resource. -func (p *Popeye) Get(_ context.Context, _ string) (runtime.Object, error) { - return nil, errors.New("NYI!!") -} - -// ---------------------------------------------------------------------------- -// Helpers... - -type popFactory struct { - Factory -} - -var _ types.Factory = (*popFactory)(nil) - -func newPopeyeFactory(f Factory) *popFactory { - return &popFactory{Factory: f} -} - -func (p *popFactory) Client() types.Connection { - return &popeyeConnection{Connection: p.Factory.Client()} -} - -type popeyeConnection struct { - client.Connection -} - -var _ types.Connection = (*popeyeConnection)(nil) - -func (c *popeyeConnection) Config() types.Config { - return c.Connection.Config() -} +// !!BOZO!! +// import ( +// "bytes" +// "context" +// "encoding/json" +// "errors" +// "fmt" +// "os" +// "path/filepath" +// "sort" +// "time" + +// "github.com/derailed/k9s/internal" +// "github.com/derailed/k9s/internal/client" +// cfg "github.com/derailed/k9s/internal/config" +// "github.com/derailed/k9s/internal/render" +// "github.com/derailed/popeye/pkg" +// "github.com/derailed/popeye/pkg/config" +// "github.com/derailed/popeye/types" +// "github.com/rs/zerolog/log" +// "k8s.io/apimachinery/pkg/runtime" +// ) + +// var _ Accessor = (*Popeye)(nil) + +// // Popeye tracks cluster sanitization. +// type Popeye struct { +// NonResource +// } + +// // NewPopeye returns a new set of aliases. +// func NewPopeye(f Factory) *Popeye { +// a := Popeye{} +// a.Init(f, client.NewGVR("popeye")) + +// return &a +// } + +// type readWriteCloser struct { +// *bytes.Buffer +// } + +// // Close close read stream. +// func (readWriteCloser) Close() error { +// return nil +// } + +// // List returns a collection of aliases. +// func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) { +// defer func(t time.Time) { +// log.Debug().Msgf("Popeye -- Elapsed %v", time.Since(t)) +// if err := recover(); err != nil { +// log.Debug().Msgf("POPEYE DIED!") +// } +// }(time.Now()) + +// flags, js := config.NewFlags(), "json" +// flags.Output = &js +// flags.ActiveNamespace = &ns + +// if report, ok := ctx.Value(internal.KeyPath).(string); ok && report != "" { +// ns, n := client.Namespaced(report) +// sections := []string{n} +// flags.Sections = §ions +// flags.ActiveNamespace = &ns +// } +// spinach := filepath.Join(cfg.AppConfigDir, "spinach.yaml") +// if c, err := p.getFactory().Client().Config().CurrentContextName(); err == nil { +// spinach = filepath.Join(cfg.AppConfigDir, fmt.Sprintf("%s_spinach.yaml", c)) +// } +// if _, err := os.Stat(spinach); err == nil { +// flags.Spinach = &spinach +// } + +// popeye, err := pkg.NewPopeye(flags, &log.Logger) +// if err != nil { +// return nil, err +// } +// popeye.SetFactory(newPopeyeFactory(p.Factory)) +// if err = popeye.Init(); err != nil { +// return nil, err +// } + +// buff := readWriteCloser{Buffer: bytes.NewBufferString("")} +// popeye.SetOutputTarget(buff) +// if _, _, err = popeye.Sanitize(); err != nil { +// log.Error().Err(err).Msgf("BOOM %#v", *flags.Sections) +// return nil, err +// } + +// var b render.Builder +// if err = json.Unmarshal(buff.Bytes(), &b); err != nil { +// return nil, err +// } + +// oo := make([]runtime.Object, 0, len(b.Report.Sections)) +// sort.Sort(b.Report.Sections) +// for _, s := range b.Report.Sections { +// s.Tally.Count = len(s.Outcome) +// if s.Tally.Sum() > 0 { +// oo = append(oo, s) +// } +// } + +// return oo, nil +// } + +// // Get retrieves a resource. +// func (p *Popeye) Get(_ context.Context, _ string) (runtime.Object, error) { +// return nil, errors.New("NYI!!") +// } + +// // ---------------------------------------------------------------------------- +// // Helpers... + +// type popFactory struct { +// Factory +// } + +// var _ types.Factory = (*popFactory)(nil) + +// func newPopeyeFactory(f Factory) *popFactory { +// return &popFactory{Factory: f} +// } + +// func (p *popFactory) Client() types.Connection { +// return &popeyeConnection{Connection: p.Factory.Client()} +// } + +// type popeyeConnection struct { +// client.Connection +// } + +// var _ types.Connection = (*popeyeConnection)(nil) + +// func (c *popeyeConnection) Config() types.Config { +// return c.Connection.Config() +// } diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 97043ff138..345ad3bb13 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -117,7 +117,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por p.path, p.tunnel, p.age = path, tt, time.Now() ns, n := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods", []string{client.GetVerb}) + auth, err := p.Client().CanI(ns, "v1/pods", n, []string{client.GetVerb}) if err != nil { return nil, err } @@ -136,7 +136,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase) } - auth, err = p.Client().CanI(ns, "v1/pods:portforward", []string{client.CreateVerb}) + auth, err = p.Client().CanI(ns, "v1/pods:portforward", "", []string{client.CreateVerb}) if err != nil { return nil, err } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index ff11836dbc..4060d4e9b0 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -107,10 +107,11 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, client.NewGVR("batch/v1/jobs"): &Job{}, client.NewGVR("v1/namespaces"): &Namespace{}, - client.NewGVR("popeye"): &Popeye{}, - client.NewGVR("helm"): &HelmChart{}, - client.NewGVR("helm-history"): &HelmHistory{}, - client.NewGVR("dir"): &Dir{}, + // !!BOZO!! + //client.NewGVR("popeye"): &Popeye{}, + client.NewGVR("helm"): &HelmChart{}, + client.NewGVR("helm-history"): &HelmHistory{}, + client.NewGVR("dir"): &Dir{}, } r, ok := m[gvr] diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 219b613c39..f562adef7c 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -58,7 +58,7 @@ func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool { // Scale a StatefulSet. func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) error { ns, n := client.Namespaced(path) - auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{client.GetVerb, client.UpdateVerb}) + auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", n, []string{client.GetVerb, client.UpdateVerb}) if err != nil { return err } @@ -87,7 +87,7 @@ func (s *StatefulSet) Restart(ctx context.Context, path string) error { return err } - ns, _ := client.Namespaced(path) + ns, n := client.Namespaced(path) pp, err := podsFromSelector(s.Factory, ns, sts.Spec.Selector.MatchLabels) if err != nil { return err @@ -96,7 +96,7 @@ func (s *StatefulSet) Restart(ctx context.Context, path string) error { s.Forwarders().Kill(client.FQN(p.Namespace, p.Name)) } - auth, err := s.Client().CanI(sts.Namespace, "apps/v1/statefulsets", []string{client.PatchVerb}) + auth, err := s.Client().CanI(sts.Namespace, "apps/v1/statefulsets", n, []string{client.PatchVerb}) if err != nil { return err } @@ -296,7 +296,7 @@ func (s *StatefulSet) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (s *StatefulSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := s.Client().CanI(ns, "apps/v1/statefulset", []string{client.PatchVerb}) + auth, err := s.Client().CanI(ns, "apps/v1/statefulset", n, []string{client.PatchVerb}) if err != nil { return err } diff --git a/internal/dao/workload.go b/internal/dao/workload.go index 72027605ce..dfe4aa05b4 100644 --- a/internal/dao/workload.go +++ b/internal/dao/workload.go @@ -48,7 +48,7 @@ type Workload struct { func (w *Workload) Delete(ctx context.Context, path string, propagation *metav1.DeletionPropagation, grace Grace) error { gvr, _ := ctx.Value(internal.KeyGVR).(client.GVR) ns, n := client.Namespaced(path) - auth, err := w.Client().CanI(ns, gvr.String(), []string{client.DeleteVerb}) + auth, err := w.Client().CanI(ns, gvr.String(), n, []string{client.DeleteVerb}) if err != nil { return err } diff --git a/internal/model/registry.go b/internal/model/registry.go index b4936c380f..55db15f832 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -82,14 +82,15 @@ var Registry = map[string]ResourceMeta{ DAO: &dao.Alias{}, Renderer: &render.Alias{}, }, - "popeye": { - DAO: &dao.Popeye{}, - Renderer: &render.Popeye{}, - }, - "sanitizer": { - DAO: &dao.Popeye{}, - TreeRenderer: &xray.Section{}, - }, + // !!BOZO!! + //"popeye": { + // DAO: &dao.Popeye{}, + // Renderer: &render.Popeye{}, + //}, + //"sanitizer": { + // DAO: &dao.Popeye{}, + // TreeRenderer: &xray.Section{}, + //}, // Core... "v1/endpoints": { diff --git a/internal/ui/indicator.go b/internal/ui/indicator.go index d9fb97a19d..ed4645a424 100644 --- a/internal/ui/indicator.go +++ b/internal/ui/indicator.go @@ -74,8 +74,8 @@ func (s *StatusIndicator) ClusterInfoChanged(prev, cur model.ClusterMeta) { s.SetPermanent(fmt.Sprintf( statusIndicatorFmt, cur.K9sVer, + cur.Context, cur.Cluster, - cur.User, cur.K8sVer, AsPercDelta(prev.Cpu, cur.Cpu), AsPercDelta(prev.Cpu, cur.Mem), diff --git a/internal/view/actions.go b/internal/view/actions.go index 2a1f8b1cb4..6709b4451b 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -75,12 +75,12 @@ func hotKeyActions(r Runner, aa ui.KeyActions) error { errs = errors.Join(errs, err) continue } - _, ok := aa[key] - if ok && !hk.Override { - errs = errors.Join(errs, fmt.Errorf("duplicated hotkeys found for %q in %q", hk.ShortCut, k)) - continue - } else if ok && hk.Override == true { - log.Info().Msgf("Action %q has been overrided by hotkey in %q", hk.ShortCut, k) + if _, ok := aa[key]; ok { + if !hk.Override { + errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k)) + continue + } + log.Info().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k) } command, err := r.EnvFn()().Substitute(hk.Command) @@ -127,18 +127,18 @@ func pluginActions(r Runner, aa ui.KeyActions) error { continue } key, err := asKey(plugin.ShortCut) - if err != nil { errs = errors.Join(errs, err) continue } - _, ok := aa[key] - if ok && !plugin.Override { - errs = errors.Join(errs, fmt.Errorf("duplicated plugin key found for %q in %q", plugin.ShortCut, k)) - continue - } else if ok && plugin.Override == true { - log.Info().Msgf("Action %q has been overrided by plugin in %q", plugin.ShortCut, k) + if _, ok := aa[key]; ok { + if !plugin.Override { + errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k)) + continue + } + log.Info().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) } + aa[key] = ui.NewKeyActionWithOpts( plugin.Description, pluginAction(r, plugin), diff --git a/internal/view/app.go b/internal/view/app.go index cdc9dd2f21..1cfb2dcdd7 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -485,6 +485,8 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { } if err := a.Config.Save(); err != nil { log.Error().Err(err).Msg("config save failed!") + } else { + log.Debug().Msgf("Saved context config for: %q", name) } a.initFactory(ns) if err := a.command.Reset(a.Config.ContextAliasesPath(), true); err != nil { diff --git a/internal/view/browser.go b/internal/view/browser.go index e688f54c5e..932fbbb6cd 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -398,7 +398,7 @@ func editRes(app *App, gvr client.GVR, path string) error { if gvr.String() == "v1/namespaces" { ns = n } - if ok, err := app.Conn().CanI(ns, gvr.String(), []string{"patch"}); !ok || err != nil { + if ok, err := app.Conn().CanI(ns, gvr.String(), n, []string{"patch"}); !ok || err != nil { return fmt.Errorf("current user can't edit resource %s", gvr) } @@ -423,7 +423,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { } ns := b.namespaces[i] - auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), client.ListAccess) + auth, err := b.App().factory.Client().CanI(ns, b.GVR().String(), "", client.ListAccess) if !auth { if err == nil { err = fmt.Errorf("current user can't access namespace %s", ns) @@ -556,7 +556,7 @@ func (b *Browser) simpleDelete(selections []string, msg string) { dialog.ShowConfirm(b.app.Styles.Dialog(), b.app.Content.Pages, "Confirm Delete", msg, func() { b.ShowDeleted() if len(selections) > 1 { - b.app.Flash().Infof("Delete %d marked %s", len(selections), b.GVR()) + b.app.Flash().Infof("Delete %d marked %s", len(selections), b.GVR().R()) } else { b.app.Flash().Infof("Delete resource %s %s", b.GVR(), selections[0]) } diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index e6a24dea69..98512a9ac0 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -53,7 +53,7 @@ func (c *ClusterInfo) StylesChanged(s *config.Styles) { func (c *ClusterInfo) hasMetrics() bool { mx := c.app.Conn().HasMetrics() if mx { - auth, err := c.app.Conn().CanI("", "metrics.k8s.io/v1beta1/nodes", client.ListAccess) + auth, err := c.app.Conn().CanI("", "metrics.k8s.io/v1beta1/nodes", "", client.ListAccess) if err != nil { log.Warn().Err(err).Msgf("No nodes metrics access") } diff --git a/internal/view/command.go b/internal/view/command.go index 3373fce0b6..9a8d335551 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -39,14 +39,7 @@ func NewCommand(app *App) *Command { // AliasesFor gather all known aliases for a given resource. func (c *Command) AliasesFor(s string) []string { - aa := make([]string, 0, 10) - for k, v := range c.alias.Alias { - if v == s { - aa = append(aa, k) - } - } - - return aa + return c.alias.AliasesFor(s) } // Init initializes the command. @@ -163,6 +156,13 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { } if context, ok := p.HasContext(); ok { + if context != c.app.Config.ActiveContextName() { + if err := c.app.Config.Save(); err != nil { + log.Error().Err(err).Msg("config save failed!") + } else { + log.Debug().Msgf("Saved context config for: %q", context) + } + } res, err := dao.AccessorFor(c.app.factory, client.NewGVR("contexts")) if err != nil { return err diff --git a/internal/view/drain_dialog.go b/internal/view/drain_dialog.go index 722c89cedf..b576a18517 100644 --- a/internal/view/drain_dialog.go +++ b/internal/view/drain_dialog.go @@ -4,6 +4,7 @@ package view import ( + "fmt" "strconv" "time" @@ -15,10 +16,10 @@ import ( const drainKey = "drain" // DrainFunc represents a drain callback function. -type DrainFunc func(v ResourceViewer, path string, opts dao.DrainOptions) +type DrainFunc func(v ResourceViewer, sels []string, opts dao.DrainOptions) // ShowDrain pops a node drain dialog. -func ShowDrain(view ResourceViewer, path string, opts dao.DrainOptions, okFn DrainFunc) { +func ShowDrain(view ResourceViewer, sels []string, opts dao.DrainOptions, okFn DrainFunc) { styles := view.App().Styles f := tview.NewForm() @@ -63,10 +64,17 @@ func ShowDrain(view ResourceViewer, path string, opts dao.DrainOptions, okFn Dra }) f.AddButton("OK", func() { DismissDrain(view, pages) - okFn(view, path, opts) + okFn(view, sels, opts) }) modal := tview.NewModalForm("", f) + path := "Drain " + if len(sels) == 1 { + path += sels[0] + } else { + path += fmt.Sprintf("(%d) nodes", len(sels)) + } + path += "?" modal.SetText(path) modal.SetDoneFunc(func(_ int, b string) { DismissDrain(view, pages) diff --git a/internal/view/exec.go b/internal/view/exec.go index e0af76b92a..520731816b 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -123,12 +123,11 @@ func edit(a *App, opts shellOpts) bool { ) for _, e := range editorEnvVars { env := os.Getenv(e) - if env != "" { + if env == "" { continue } - bin, err = exec.LookPath(env) - if err != nil { - continue + if bin, err = exec.LookPath(env); err == nil { + break } } if bin == "" { diff --git a/internal/view/node.go b/internal/view/node.go index e39d4091ad..1be326f12e 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -96,8 +96,8 @@ func (n *Node) showPods(a *App, _ ui.Tabular, _ client.GVR, path string) { } func (n *Node) drainCmd(evt *tcell.EventKey) *tcell.EventKey { - path := n.GetTable().GetSelectedItem() - if path == "" { + sels := n.GetTable().GetSelectedItems() + if len(sels) == 0 { return evt } @@ -105,12 +105,12 @@ func (n *Node) drainCmd(evt *tcell.EventKey) *tcell.EventKey { GracePeriodSeconds: -1, Timeout: 5 * time.Second, } - ShowDrain(n, path, opts, drainNode) + ShowDrain(n, sels, opts, drainNode) return nil } -func drainNode(v ResourceViewer, path string, opts dao.DrainOptions) { +func drainNode(v ResourceViewer, sels []string, opts dao.DrainOptions) { res, err := dao.AccessorFor(v.App().factory, v.GVR()) if err != nil { v.App().Flash().Err(err) @@ -125,14 +125,14 @@ func drainNode(v ResourceViewer, path string, opts dao.DrainOptions) { v.Stop() defer v.Start() { - d := NewDetails(v.App(), "Drain Progress", path, contentYAML, true) + d := NewDetails(v.App(), "Drain Progress", "nodes", contentYAML, true) if err := v.App().inject(d, false); err != nil { v.App().Flash().Err(err) } - - if err := m.Drain(path, opts, d.GetWriter()); err != nil { - v.App().Flash().Err(err) - return + for _, sel := range sels { + if err := m.Drain(sel, opts, d.GetWriter()); err != nil { + v.App().Flash().Err(err) + } } v.Refresh() } @@ -140,8 +140,8 @@ func drainNode(v ResourceViewer, path string, opts dao.DrainOptions) { func (n *Node) toggleCordonCmd(cordon bool) func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { - path := n.GetTable().GetSelectedItem() - if path == "" { + sels := n.GetTable().GetSelectedItems() + if len(sels) == 0 { return evt } @@ -151,7 +151,11 @@ func (n *Node) toggleCordonCmd(cordon bool) func(evt *tcell.EventKey) *tcell.Eve } else { title, msg = title+"Uncordon", "Uncordon " } - msg += path + "?" + if len(sels) == 1 { + msg += sels[0] + "?" + } else { + msg += fmt.Sprintf("(%d) marked %s?", len(sels), n.GVR().R()) + } dialog.ShowConfirm(n.App().Styles.Dialog(), n.App().Content.Pages, title, msg, func() { res, err := dao.AccessorFor(n.App().factory, n.GVR()) if err != nil { @@ -163,8 +167,10 @@ func (n *Node) toggleCordonCmd(cordon bool) func(evt *tcell.EventKey) *tcell.Eve n.App().Flash().Err(fmt.Errorf("expecting a maintainer for %q", n.GVR())) return } - if err := m.ToggleCordon(path, cordon); err != nil { - n.App().Flash().Err(err) + for _, s := range sels { + if err := m.ToggleCordon(s, cordon); err != nil { + n.App().Flash().Err(err) + } } n.Refresh() }, func() {}) diff --git a/internal/watch/factory.go b/internal/watch/factory.go index d21726f121..6c896afa6d 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -191,7 +191,7 @@ func (f *Factory) isClusterWide() bool { // CanForResource return an informer is user has access. func (f *Factory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { - auth, err := f.Client().CanI(ns, gvr, verbs) + auth, err := f.Client().CanI(ns, gvr, "", verbs) if err != nil { return nil, err } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 918ed46a11..a8b389e7ec 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.8' +version: 'v0.31.9' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From dae2590e0feef190e8d51f43415ade47230f9ab4 Mon Sep 17 00:00:00 2001 From: Pierce Staab Date: Wed, 21 Feb 2024 18:53:35 -0600 Subject: [PATCH 104/169] Update flux plugin to handle inactive context (#2542) * Update flux.yaml * Update flux.yaml --- plugins/flux.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/flux.yaml b/plugins/flux.yaml index cbe887d3bb..72a9655542 100644 --- a/plugins/flux.yaml +++ b/plugins/flux.yaml @@ -183,10 +183,11 @@ plugins: - -c - >- kubectl get + --context $CONTEXT --all-namespaces helmreleases.helm.toolkit.fluxcd.io -o json | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.namespace,.metadata.name,.spec.suspend] | @tsv' - | less + | less -K get-suspended-kustomizations: shortCut: Shift-S confirm: false @@ -199,7 +200,8 @@ plugins: - -c - >- kubectl get + --context $CONTEXT --all-namespaces kustomizations.kustomize.toolkit.fluxcd.io -o json | jq -r '.items[] | select(.spec.suspend==true) | [.metadata.name,.spec.suspend] | @tsv' - | less + | less -K From d06ab6407b087c23ec85af889cdf82a01f18af7f Mon Sep 17 00:00:00 2001 From: Marcus Noble Date: Thu, 22 Feb 2024 00:54:25 +0000 Subject: [PATCH 105/169] Correctly respect the KUBECACHEDIR env var (#2551) Signed-off-by: Marcus Noble --- internal/client/client.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index 85946451ed..f76467a297 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "os" "path/filepath" "strings" "sync" @@ -499,8 +500,13 @@ func (a *APIClient) CachedDiscovery() (*disk.CachedDiscoveryClient, error) { return nil, err } - httpCacheDir := filepath.Join(mustHomeDir(), ".kube", "http-cache") - discCacheDir := filepath.Join(mustHomeDir(), ".kube", "cache", "discovery", toHostDir(cfg.Host)) + baseCacheDir := os.Getenv("KUBECACHEDIR") + if baseCacheDir == "" { + baseCacheDir = filepath.Join(mustHomeDir(), ".kube", "cache") + } + + httpCacheDir := filepath.Join(baseCacheDir, "http") + discCacheDir := filepath.Join(baseCacheDir, "discovery", toHostDir(cfg.Host)) c, err := disk.NewCachedDiscoveryClientForConfig(cfg, discCacheDir, httpCacheDir, cacheExpiry) if err != nil { From 6481212fee308c73342c221eddda8580268bbca3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:13:41 -0700 Subject: [PATCH 106/169] Bump k8s.io/apimachinery from 0.29.1 to 0.29.2 (#2547) Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.29.1 to 0.29.2. - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.29.1...v0.29.2) --- updated-dependencies: - dependency-name: k8s.io/apimachinery dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f6e554fb61..634d11e8c9 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( helm.sh/helm/v3 v3.14.1 k8s.io/api v0.29.1 k8s.io/apiextensions-apiserver v0.29.1 - k8s.io/apimachinery v0.29.1 + k8s.io/apimachinery v0.29.2 k8s.io/cli-runtime v0.29.1 k8s.io/client-go v0.29.1 k8s.io/klog/v2 v2.120.1 diff --git a/go.sum b/go.sum index 59b5e9e244..bbdac2e4b5 100644 --- a/go.sum +++ b/go.sum @@ -1831,8 +1831,8 @@ k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= From bac8892e1f7d0b9193314bec37ad7900468fab3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:13:57 -0700 Subject: [PATCH 107/169] Bump k8s.io/api from 0.29.1 to 0.29.2 (#2550) Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.29.1 to 0.29.2. - [Commits](https://github.com/kubernetes/api/compare/v0.29.1...v0.29.2) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 634d11e8c9..0080c070b0 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.1 - k8s.io/api v0.29.1 + k8s.io/api v0.29.2 k8s.io/apiextensions-apiserver v0.29.1 k8s.io/apimachinery v0.29.2 k8s.io/cli-runtime v0.29.1 diff --git a/go.sum b/go.sum index bbdac2e4b5..782039d968 100644 --- a/go.sum +++ b/go.sum @@ -1827,8 +1827,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= +k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= From e90e5203bb4f40fcf54b4afd5fbdbabf384d33c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:14:17 -0700 Subject: [PATCH 108/169] Bump k8s.io/metrics from 0.29.1 to 0.29.2 (#2548) Bumps [k8s.io/metrics](https://github.com/kubernetes/metrics) from 0.29.1 to 0.29.2. - [Commits](https://github.com/kubernetes/metrics/compare/v0.29.1...v0.29.2) --- updated-dependencies: - dependency-name: k8s.io/metrics dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0080c070b0..6b8e0e15ec 100644 --- a/go.mod +++ b/go.mod @@ -35,10 +35,10 @@ require ( k8s.io/apiextensions-apiserver v0.29.1 k8s.io/apimachinery v0.29.2 k8s.io/cli-runtime v0.29.1 - k8s.io/client-go v0.29.1 + k8s.io/client-go v0.29.2 k8s.io/klog/v2 v2.120.1 k8s.io/kubectl v0.29.1 - k8s.io/metrics v0.29.1 + k8s.io/metrics v0.29.2 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index 782039d968..6608318c9b 100644 --- a/go.sum +++ b/go.sum @@ -1837,8 +1837,8 @@ k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= +k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= @@ -1847,8 +1847,8 @@ k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/A k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= -k8s.io/metrics v0.29.1 h1:qutc3aIPMCniMuEApuLaeYX47rdCn8eycVDx7R6wMlQ= -k8s.io/metrics v0.29.1/go.mod h1:JrbV2U71+v7d/9qb90UVKL8r0uJ6Z2Hy4V7mDm05cKs= +k8s.io/metrics v0.29.2 h1:oLSTHEr40V7c7C8wDRRhiAefjGRHROK5zeV8NT0tpzc= +k8s.io/metrics v0.29.2/go.mod h1:cWzACDpKElWhm0CElwfK+7I39wDNbmDDCX7hywjvgR4= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= From 93cad8c9587559e125fc7c4fb56443f1e6c51479 Mon Sep 17 00:00:00 2001 From: Jakob Edding <15202881+JakobEdding@users.noreply.github.com> Date: Thu, 22 Feb 2024 04:14:45 +0100 Subject: [PATCH 109/169] Use configured log fgColor to print log markers (#2546) --- internal/view/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/view/log.go b/internal/view/log.go index 3fdc49ff2c..a5df9bceff 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -453,7 +453,7 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey { func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey { _, _, w, _ := l.GetRect() - fmt.Fprintf(l.ansiWriter, "\n[white:-:b]%s[-:-:-]", strings.Repeat("─", w-4)) + fmt.Fprintf(l.ansiWriter, "\n[%s:-:b]%s[-:-:-]", l.app.Styles.Views().Log.FgColor.String(), strings.Repeat("─", w-4)) l.follow = true return nil From 9893d67f5d7b1545b3a12d1f53c1882cfa92ea34 Mon Sep 17 00:00:00 2001 From: Wralith Date: Wed, 28 Feb 2024 04:13:54 +0300 Subject: [PATCH 110/169] Add everforest skins (#2564) --- skins/everforest-dark.yaml | 114 ++++++++++++++++++++++++++++++++++++ skins/everforest-light.yaml | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 skins/everforest-dark.yaml create mode 100644 skins/everforest-light.yaml diff --git a/skins/everforest-dark.yaml b/skins/everforest-dark.yaml new file mode 100644 index 0000000000..5f2919371f --- /dev/null +++ b/skins/everforest-dark.yaml @@ -0,0 +1,114 @@ +# ----------------------------------------------------------------------------- +# Everforest Dark +# https://github.com/sainnhe/everforest/blob/master/palette.md#dark +# ----------------------------------------------------------------------------- +# +text: &text "#d3c6aa" +base: &base "#1e2326" +overlay: &overlay "#2e383c" +muted: &muted "#495156" +red: &red "#e67e80" +blue: &blue "#7fbbb3" +yellow: &yellow "#dbbc7f" +green: &green "#83c092" +pink: &pink "#d699b6" +orange: &orange "#e69875" + +# Skin... +k9s: + # General K9s styles + body: + fgColor: *text + bgColor: *base + logoColor: *green + # Command prompt styles + prompt: + fgColor: *text + bgColor: *base + suggestColor: *green + border: + command: *orange + default: *blue + # ClusterInfoView styles. + info: + fgColor: *green + sectionColor: *text + # Dialog styles. + dialog: + fgColor: *text + bgColor: *base + buttonFgColor: *text + buttonBgColor: *green + buttonFocusFgColor: *yellow + buttonFocusBgColor: *green + labelFgColor: *yellow + fieldFgColor: *text + frame: + # Borders styles. + border: + fgColor: *overlay + focusColor: *overlay + menu: + fgColor: *text + keyColor: *green + # Used for favorite namespaces + numKeyColor: *green + # CrumbView attributes for history navigation. + crumbs: + fgColor: *text + bgColor: *overlay + activeColor: *overlay + # Resource status and update styles + status: + newColor: *green + modifyColor: *red + addColor: *blue + errorColor: *pink + highlightcolor: *yellow + killColor: *muted + completedColor: *muted + # Border title styles. + title: + fgColor: *text + bgColor: *overlay + highlightColor: *yellow + counterColor: *green + filterColor: *green + views: + # Charts skins... + charts: + bgColor: default + defaultDialColors: + - *green + - *pink + defaultChartColors: + - *green + - *pink + # TableView attributes. + table: + fgColor: *text + bgColor: *base + # Header row styles. + header: + fgColor: *text + bgColor: *base + sorterColor: *red + # Xray view attributes. + xray: + fgColor: *text + bgColor: *base + cursorColor: *overlay + graphicColor: *green + showIcons: false + # YAML info styles. + yaml: + keyColor: *green + colonColor: *green + valueColor: *text + # Logs styles. + logs: + fgColor: *text + bgColor: *base + indicator: + fgColor: *text + bgColor: *base diff --git a/skins/everforest-light.yaml b/skins/everforest-light.yaml new file mode 100644 index 0000000000..dceab3e2ca --- /dev/null +++ b/skins/everforest-light.yaml @@ -0,0 +1,114 @@ +# ----------------------------------------------------------------------------- +# Everforest Light +# https://github.com/sainnhe/everforest/blob/master/palette.md#dark +# ----------------------------------------------------------------------------- +# +text: &text "#5c6a72" +base: &base "#f2efdf" +overlay: &overlay "#fffbef" +muted: &muted "#edeada" +red: &red "#f85552" +blue: &blue "#3a94c5" +yellow: &yellow "#dfa000" +green: &green "#35a77c" +pink: &pink "#df69ba" +orange: &orange "#f57d26" + +# Skin... +k9s: + # General K9s styles + body: + fgColor: *text + bgColor: *base + logoColor: *green + # Command prompt styles + prompt: + fgColor: *text + bgColor: *base + suggestColor: *green + border: + command: *orange + default: *blue + # ClusterInfoView styles. + info: + fgColor: *green + sectionColor: *text + # Dialog styles. + dialog: + fgColor: *text + bgColor: *base + buttonFgColor: *text + buttonBgColor: *green + buttonFocusFgColor: *yellow + buttonFocusBgColor: *green + labelFgColor: *yellow + fieldFgColor: *text + frame: + # Borders styles. + border: + fgColor: *overlay + focusColor: *overlay + menu: + fgColor: *text + keyColor: *green + # Used for favorite namespaces + numKeyColor: *green + # CrumbView attributes for history navigation. + crumbs: + fgColor: *text + bgColor: *overlay + activeColor: *overlay + # Resource status and update styles + status: + newColor: *green + modifyColor: *red + addColor: *blue + errorColor: *pink + highlightcolor: *yellow + killColor: *muted + completedColor: *muted + # Border title styles. + title: + fgColor: *text + bgColor: *overlay + highlightColor: *yellow + counterColor: *green + filterColor: *green + views: + # Charts skins... + charts: + bgColor: default + defaultDialColors: + - *green + - *pink + defaultChartColors: + - *green + - *pink + # TableView attributes. + table: + fgColor: *text + bgColor: *base + # Header row styles. + header: + fgColor: *text + bgColor: *base + sorterColor: *red + # Xray view attributes. + xray: + fgColor: *text + bgColor: *base + cursorColor: *overlay + graphicColor: *green + showIcons: false + # YAML info styles. + yaml: + keyColor: *green + colonColor: *green + valueColor: *text + # Logs styles. + logs: + fgColor: *text + bgColor: *base + indicator: + fgColor: *text + bgColor: *base From b141fbd4d9f1ba01b30cc3aa0a3b4041eb7a5839 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:50:57 -0700 Subject: [PATCH 111/169] Bump k8s.io/apiextensions-apiserver from 0.29.1 to 0.29.2 (#2565) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.29.1 to 0.29.2. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.29.1...v0.29.2) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 6b8e0e15ec..5900841d7e 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.1 k8s.io/api v0.29.2 - k8s.io/apiextensions-apiserver v0.29.1 + k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 k8s.io/cli-runtime v0.29.1 k8s.io/client-go v0.29.2 @@ -313,8 +313,8 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.29.1 // indirect - k8s.io/component-base v0.29.1 // indirect + k8s.io/apiserver v0.29.2 // indirect + k8s.io/component-base v0.29.2 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect modernc.org/libc v1.29.0 // indirect diff --git a/go.sum b/go.sum index 6608318c9b..13b084513d 100644 --- a/go.sum +++ b/go.sum @@ -1829,18 +1829,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= -k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= -k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= +k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= +k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g= -k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw= +k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= +k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= -k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= -k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= +k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= From fddd18f44e00fca6d964e4cf5ad1bf0425725000 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:51:50 -0700 Subject: [PATCH 112/169] Bump helm.sh/helm/v3 from 3.14.1 to 3.14.2 (#2555) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.14.1 to 3.14.2. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.14.1...v3.14.2) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5900841d7e..5aa309014d 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.14.1 + helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.2 k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 diff --git a/go.sum b/go.sum index 13b084513d..27423e05dd 100644 --- a/go.sum +++ b/go.sum @@ -1818,8 +1818,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.14.1 h1:4AwRLx+wfzlPtvrsbDmWP5PUokGmf9/nAmEdk21vae8= -helm.sh/helm/v3 v3.14.1/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= +helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= +helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 00e460078835a70aa8d79147aa9321e12d9c1b60 Mon Sep 17 00:00:00 2001 From: yinheli Date: Thu, 29 Feb 2024 22:53:53 +0800 Subject: [PATCH 113/169] feat: sort by role in node list view (#2558) --- internal/view/node.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/view/node.go b/internal/view/node.go index 1be326f12e..daf9ac4de7 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -85,6 +85,7 @@ func (n *Node) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ ui.KeyY: ui.NewKeyAction(yamlAction, n.yamlCmd, true), + ui.KeyShiftR: ui.NewKeyAction("Sort ROLE", n.GetTable().SortColCmd("ROLE", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort CPU", n.GetTable().SortColCmd(cpuCol, false), false), ui.KeyShiftM: ui.NewKeyAction("Sort MEM", n.GetTable().SortColCmd(memCol, false), false), ui.KeyShiftO: ui.NewKeyAction("Sort Pods", n.GetTable().SortColCmd("PODS", false), false), From d803b996493435ed196632580c6652d1cd2352e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:01:40 -0700 Subject: [PATCH 114/169] Bump k8s.io/kubectl from 0.29.1 to 0.29.2 (#2566) Bumps [k8s.io/kubectl](https://github.com/kubernetes/kubectl) from 0.29.1 to 0.29.2. - [Commits](https://github.com/kubernetes/kubectl/compare/v0.29.1...v0.29.2) --- updated-dependencies: - dependency-name: k8s.io/kubectl dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5aa309014d..798e70b01f 100644 --- a/go.mod +++ b/go.mod @@ -34,10 +34,10 @@ require ( k8s.io/api v0.29.2 k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 - k8s.io/cli-runtime v0.29.1 + k8s.io/cli-runtime v0.29.2 k8s.io/client-go v0.29.2 k8s.io/klog/v2 v2.120.1 - k8s.io/kubectl v0.29.1 + k8s.io/kubectl v0.29.2 k8s.io/metrics v0.29.2 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index 27423e05dd..c22b7ad5cb 100644 --- a/go.sum +++ b/go.sum @@ -1835,8 +1835,8 @@ k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= -k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= -k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= +k8s.io/cli-runtime v0.29.2 h1:smfsOcT4QujeghsNjECKN3lwyX9AwcFU0nvJ7sFN3ro= +k8s.io/cli-runtime v0.29.2/go.mod h1:KLisYYfoqeNfO+MkTWvpqIyb1wpJmmFJhioA0xd4MW8= k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= @@ -1845,8 +1845,8 @@ k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= -k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= +k8s.io/kubectl v0.29.2 h1:uaDYaBhumvkwz0S2XHt36fK0v5IdNgL7HyUniwb2IUo= +k8s.io/kubectl v0.29.2/go.mod h1:BhizuYBGcKaHWyq+G7txGw2fXg576QbPrrnQdQDZgqI= k8s.io/metrics v0.29.2 h1:oLSTHEr40V7c7C8wDRRhiAefjGRHROK5zeV8NT0tpzc= k8s.io/metrics v0.29.2/go.mod h1:cWzACDpKElWhm0CElwfK+7I39wDNbmDDCX7hywjvgR4= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= From 82ba6f9f37368f88aec7dee72cfa0c5483c9d878 Mon Sep 17 00:00:00 2001 From: ffais <42377700+ffais@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:05:15 +0100 Subject: [PATCH 115/169] Added context to the debug command for debug-container plugin (#2554) * fix: debug-container plugin added context to debug command Signed-off-by: ffais * fix: debug-container plugin upgrade netshoot version Signed-off-by: ffais --------- Signed-off-by: ffais --- plugins/debug-container.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/debug-container.yaml b/plugins/debug-container.yaml index 0d1e387a5f..f8561ca29b 100644 --- a/plugins/debug-container.yaml +++ b/plugins/debug-container.yaml @@ -11,4 +11,4 @@ plugins: confirm: true args: - -c - - "kubectl debug -it -n=$NAMESPACE $POD --target=$NAME --image=nicolaka/netshoot:v0.11 --share-processes -- bash" \ No newline at end of file + - "kubectl debug -it --context $CONTEXT -n=$NAMESPACE $POD --target=$NAME --image=nicolaka/netshoot:v0.12 --share-processes -- bash" \ No newline at end of file From 0d1653101696aaed67984d0188407503426c20e0 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sat, 2 Mar 2024 10:18:47 -0700 Subject: [PATCH 116/169] K9s/release v0.32.0 (#2577) * [Perf] improved load perf and ui updates * [Bug] Fix #2557 * [Maint] refactor + spring cleaning up * [Bug] Fix #2569 * [Maint] Refactor + cleanup * [Bug] Fix #2560 * [Maint] Refactor + cleanup * Release v0.32.0 --- Makefile | 2 +- change_logs/release_v0.32.0.md | 73 +++ cmd/root.go | 4 +- internal/client/client.go | 60 +-- internal/client/config.go | 2 +- internal/config/alias.go | 4 +- internal/config/config.go | 20 +- internal/config/config_test.go | 32 +- internal/config/data/config.go | 9 + internal/config/data/context.go | 7 + internal/config/data/context_int_test.go | 81 +++ internal/config/data/dir.go | 28 +- internal/config/data/helpers.go | 4 +- internal/config/data/ns.go | 15 + internal/config/files.go | 8 +- internal/config/hotkey.go | 6 +- internal/config/k9s.go | 10 +- internal/config/k9s_int_test.go | 2 +- internal/config/k9s_test.go | 4 +- internal/config/mock/test_helpers.go | 4 +- internal/config/plugin.go | 3 +- internal/config/views.go | 25 +- internal/dao/cluster.go | 4 +- internal/dao/cm.go | 13 + internal/dao/crd.go | 34 -- internal/dao/dp.go | 5 - internal/dao/ds.go | 7 +- internal/dao/helpers.go | 62 ++- internal/dao/log_items.go | 5 +- internal/dao/ns.go | 20 +- internal/dao/pod.go | 49 +- internal/dao/popeye.go | 2 +- internal/dao/registry.go | 48 +- internal/dao/secret.go | 8 +- internal/dao/sts.go | 5 - internal/dao/table.go | 46 +- internal/helpers.go | 46 ++ internal/helpers_test.go | 33 ++ internal/model/cluster.go | 2 +- internal/model/cluster_info.go | 2 - internal/model/describe.go | 3 +- internal/model/pulse_health.go | 11 +- internal/model/registry.go | 24 +- internal/model/rev_values.go | 3 +- internal/model/stack.go | 1 + internal/model/table.go | 122 +---- internal/model/table_int_test.go | 60 +-- internal/model/table_test.go | 10 +- internal/model/text.go | 4 +- internal/model/types.go | 19 +- internal/model/values.go | 3 +- internal/model/yaml.go | 2 +- internal/{render => model1}/color.go | 13 +- internal/model1/color_test.go | 66 +++ internal/{render => model1}/delta.go | 6 +- internal/model1/delta_test.go | 266 ++++++++++ internal/model1/fields.go | 41 ++ internal/{render => model1}/header.go | 50 +- internal/{render => model1}/header_test.go | 189 ++++--- internal/model1/helpers.go | 166 +++++++ internal/model1/helpers_test.go | 89 ++++ internal/model1/row.go | 92 ++++ internal/{render => model1}/row_event.go | 181 ++++--- internal/model1/row_event_test.go | 543 +++++++++++++++++++++ internal/{render => model1}/row_test.go | 170 +++---- internal/model1/rows.go | 61 +++ internal/model1/table_data.go | 496 +++++++++++++++++++ internal/model1/table_data_test.go | 404 +++++++++++++++ internal/model1/test_helper_test.go | 17 + internal/model1/types.go | 61 +++ internal/render/alias.go | 13 +- internal/render/alias_test.go | 35 +- internal/render/base.go | 10 +- internal/render/benchmark.go | 44 +- internal/render/benchmark_int_test.go | 13 +- internal/render/cm.go | 56 +++ internal/render/color_test.go | 66 --- internal/render/container.go | 75 ++- internal/render/container_test.go | 7 +- internal/render/context.go | 25 +- internal/render/context_test.go | 9 +- internal/render/cr.go | 15 +- internal/render/cr_test.go | 5 +- internal/render/crb.go | 21 +- internal/render/crb_test.go | 5 +- internal/render/crd.go | 37 +- internal/render/crd_test.go | 6 +- internal/render/cronjob.go | 38 +- internal/render/cronjob_test.go | 5 +- internal/render/delta_test.go | 266 ---------- internal/render/dir.go | 13 +- internal/render/dp.go | 47 +- internal/render/dp_test.go | 7 +- internal/render/ds.go | 33 +- internal/render/ds_test.go | 5 +- internal/render/ep.go | 19 +- internal/render/ep_test.go | 5 +- internal/render/ev.go | 27 +- internal/render/ev_test.go | 6 +- internal/render/generic.go | 23 +- internal/render/generic_test.go | 63 +-- internal/render/helm/chart.go | 29 +- internal/render/helm/history.go | 25 +- internal/render/helpers.go | 133 ++--- internal/render/helpers_test.go | 149 ++---- internal/render/img_scan.go | 35 +- internal/render/job.go | 31 +- internal/render/job_test.go | 5 +- internal/render/node.go | 47 +- internal/render/node_test.go | 7 +- internal/render/np.go | 31 +- internal/render/np_test.go | 5 +- internal/render/ns.go | 39 +- internal/render/ns_test.go | 45 +- internal/render/pdb.go | 31 +- internal/render/pdb_test.go | 5 +- internal/render/pod.go | 103 ++-- internal/render/pod_test.go | 143 +++--- internal/render/policy.go | 43 +- internal/render/policy_test.go | 5 +- internal/render/popeye.go | 319 ++++++------ internal/render/port_forward_test.go | 5 +- internal/render/portforward.go | 31 +- internal/render/pv.go | 57 ++- internal/render/pv_test.go | 9 +- internal/render/pvc.go | 29 +- internal/render/pvc_test.go | 5 +- internal/render/rbac.go | 19 +- internal/render/reference.go | 17 +- internal/render/reference_test.go | 5 +- internal/render/ro.go | 19 +- internal/render/ro_test.go | 5 +- internal/render/rob.go | 25 +- internal/render/rob_test.go | 5 +- internal/render/row.go | 231 --------- internal/render/row_event_test.go | 539 -------------------- internal/render/rs.go | 33 +- internal/render/rs_test.go | 5 +- internal/render/sa.go | 21 +- internal/render/sa_test.go | 5 +- internal/render/sc.go | 25 +- internal/render/sc_test.go | 5 +- internal/render/screen_dump.go | 21 +- internal/render/screen_dump_test.go | 5 +- internal/render/secret.go | 58 +++ internal/render/sts.go | 33 +- internal/render/sts_test.go | 5 +- internal/render/subject.go | 21 +- internal/render/svc.go | 29 +- internal/render/svc_test.go | 7 +- internal/render/table_data.go | 150 ------ internal/render/table_data_test.go | 396 --------------- internal/render/testdata/p1.json | 146 ++++++ internal/render/workload.go | 37 +- internal/ui/action.go | 139 +++++- internal/ui/action_test.go | 4 +- internal/ui/app.go | 30 +- internal/ui/app_test.go | 4 +- internal/ui/config.go | 27 +- internal/ui/config_test.go | 6 +- internal/ui/menu_test.go | 6 +- internal/ui/padding.go | 18 +- internal/ui/padding_test.go | 105 ++-- internal/ui/select_table.go | 2 +- internal/ui/table.go | 248 ++++++---- internal/ui/table_helper.go | 90 ---- internal/ui/table_helper_test.go | 22 - internal/ui/table_test.go | 62 +-- internal/ui/tree.go | 12 +- internal/ui/types.go | 19 +- internal/view/actions.go | 38 +- internal/view/alias.go | 4 +- internal/view/alias_test.go | 38 +- internal/view/app.go | 9 +- internal/view/app_test.go | 2 +- internal/view/browser.go | 103 ++-- internal/view/cluster_info.go | 7 +- internal/view/cm.go | 6 +- internal/view/command.go | 2 +- internal/view/container.go | 27 +- internal/view/context.go | 6 +- internal/view/cow.go | 12 +- internal/view/crd.go | 38 ++ internal/view/cronjob.go | 4 +- internal/view/details.go | 10 +- internal/view/dir.go | 8 +- internal/view/dp.go | 4 +- internal/view/ds.go | 4 +- internal/view/event.go | 4 +- internal/view/group.go | 4 +- internal/view/helm_chart.go | 4 +- internal/view/helm_history.go | 22 +- internal/view/help.go | 2 +- internal/view/helpers.go | 27 +- internal/view/helpers_test.go | 5 +- internal/view/image_extender.go | 6 +- internal/view/img_scan.go | 4 +- internal/view/live_view.go | 22 +- internal/view/log.go | 6 +- internal/view/log_test.go | 6 +- internal/view/logger.go | 10 +- internal/view/logs_extender.go | 4 +- internal/view/node.go | 12 +- internal/view/ns.go | 46 +- internal/view/pf.go | 4 +- internal/view/pf_extender.go | 4 +- internal/view/picker.go | 4 +- internal/view/pod.go | 38 +- internal/view/policy.go | 4 +- internal/view/popeye.go | 196 ++++---- internal/view/priorityclass.go | 6 +- internal/view/pulse.go | 14 +- internal/view/pvc.go | 4 +- internal/view/rbac.go | 6 +- internal/view/reference.go | 4 +- internal/view/registrar.go | 19 +- internal/view/restart_extender.go | 10 +- internal/view/rs.go | 4 +- internal/view/sa.go | 4 +- internal/view/sanitizer.go | 12 +- internal/view/scale_extender.go | 15 +- internal/view/secret.go | 4 +- internal/view/sts.go | 6 +- internal/view/svc.go | 4 +- internal/view/table.go | 6 +- internal/view/table_helper.go | 19 +- internal/view/table_int_test.go | 132 +++-- internal/view/types.go | 4 +- internal/view/user.go | 4 +- internal/view/value_extender.go | 11 +- internal/view/vul_extender.go | 4 +- internal/view/workload.go | 49 +- internal/view/xray.go | 46 +- internal/xray/pod.go | 1 - snap/snapcraft.yaml | 2 +- 235 files changed, 5583 insertions(+), 4556 deletions(-) create mode 100644 change_logs/release_v0.32.0.md create mode 100644 internal/config/data/context_int_test.go create mode 100644 internal/dao/cm.go create mode 100644 internal/helpers.go create mode 100644 internal/helpers_test.go rename internal/{render => model1}/color.go (74%) create mode 100644 internal/model1/color_test.go rename internal/{render => model1}/delta.go (97%) create mode 100644 internal/model1/delta_test.go create mode 100644 internal/model1/fields.go rename internal/{render => model1}/header.go (82%) rename internal/{render => model1}/header_test.go (53%) create mode 100644 internal/model1/helpers.go create mode 100644 internal/model1/helpers_test.go create mode 100644 internal/model1/row.go rename internal/{render => model1}/row_event.go (54%) create mode 100644 internal/model1/row_event_test.go rename internal/{render => model1}/row_test.go (78%) create mode 100644 internal/model1/rows.go create mode 100644 internal/model1/table_data.go create mode 100644 internal/model1/table_data_test.go create mode 100644 internal/model1/test_helper_test.go create mode 100644 internal/model1/types.go create mode 100644 internal/render/cm.go delete mode 100644 internal/render/color_test.go delete mode 100644 internal/render/delta_test.go delete mode 100644 internal/render/row.go delete mode 100644 internal/render/row_event_test.go create mode 100644 internal/render/secret.go delete mode 100644 internal/render/table_data.go delete mode 100644 internal/render/table_data_test.go create mode 100644 internal/render/testdata/p1.json create mode 100644 internal/view/crd.go diff --git a/Makefile b/Makefile index 2382ab3354..36c29177ce 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.31.9 +VERSION ?= v0.32.0 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.0.md b/change_logs/release_v0.32.0.md new file mode 100644 index 0000000000..e35c3b61f5 --- /dev/null +++ b/change_logs/release_v0.32.0.md @@ -0,0 +1,73 @@ + + +# Release v0.32.0 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +A lot of refactors, perf improvements (crossing fingers+toes!) and general spring cleaning items in this release. +Thus I expect a bit of `disturbance in the farce` given the major code churns, so please beware! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## A Word From Our Sponsors... + +To all the good folks below that opted to `pay it forward` and join our sponsorship program, I salute you!! + +* [Justin Reid](https://github.com/jmreid) +* [Danni](https://github.com/danninov) +* [Robert Krahn](https://github.com/rksm) +* [Hao Ke](https://github.com/kehao95) +* [PH](https://github.com/raphael-com-ph) + +> Sponsorship cancellations since the last release: **9!!** 🥹 + +--- + +## Resolved Issues + +* [#2569](https://github.com/derailed/k9s/issues/2569) k9s panics on start if the main config file (config.yml) is owned by root +* [#2568](https://github.com/derailed/k9s/issues/2568) kube context in running k9s is no longer sticky, during kubectx context switch +* [#2560](https://github.com/derailed/k9s/issues/2560) Namespace/Settings keeps resetting +* [#2557](https://github.com/derailed/k9s/issues/2557) [Feature]: Sort CRDs by their group +* [#1462](https://github.com/derailed/k9s/issues/1462) k9s running very slowly when opening namespace with 13k pods (maybe??) + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2564](https://github.com/derailed/k9s/pull/2564) Add everforest skins +* [#2558](https://github.com/derailed/k9s/pull/2558) feat: sort by role in node list view +* [#2554](https://github.com/derailed/k9s/pull/2554) Added context to the debug command for debug-container plugin +* [#2554](https://github.com/derailed/k9s/pull/2554) Correctly respect the KUBECACHEDIR env var +* [#2546](https://github.com/derailed/k9s/pull/2546) Use configured log fgColor to print log markers + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 2ffc3ea1d1..5181c8bd7a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -134,7 +134,7 @@ func loadConfiguration() (*config.Config, error) { errs = errors.Join(errs, err) } - if err := k9sCfg.Load(config.AppConfigFile); err != nil { + if err := k9sCfg.Load(config.AppConfigFile, false); err != nil { errs = errors.Join(errs, err) } k9sCfg.K9s.Override(k9sFlags) @@ -151,7 +151,7 @@ func loadConfiguration() (*config.Config, error) { } log.Info().Msg("✅ Kubernetes connectivity") - if err := k9sCfg.Save(); err != nil { + if err := k9sCfg.Save(false); err != nil { log.Error().Err(err).Msg("Config save") errs = errors.Join(errs, err) } diff --git a/internal/client/client.go b/internal/client/client.go index f76467a297..e12f900530 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -212,64 +212,26 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { return info, nil } -func (a *APIClient) IsValidNamespace(ns string) bool { - if IsClusterWide(ns) || ns == NotNamespaced { - return true - } - - ok, err := a.CanI(ClusterScope, "v1/namespaces", "", []string{ListVerb}) - if ok && err == nil { - nn, _ := a.ValidNamespaceNames() - _, ok = nn[ns] - return ok - } - - ok, err = a.isValidNamespace(ns) - if ok && err == nil { - return ok - } - log.Warn().Err(err).Msgf("namespace validation failed for: %q", ns) - - return false -} - -func (a *APIClient) cachedNamespaceNames() NamespaceNames { - cns, ok := a.cache.Get(cacheNSKey) - if !ok { - return make(NamespaceNames) +func (a *APIClient) IsValidNamespace(n string) bool { + ok, err := a.isValidNamespace(n) + if err != nil { + log.Warn().Err(err).Msgf("namespace validation failed for: %q", n) } - return cns.(NamespaceNames) + return ok } func (a *APIClient) isValidNamespace(n string) (bool, error) { if IsClusterWide(n) || n == NotNamespaced { return true, nil } - - if a == nil { - return false, errors.New("invalid client") - } - - cnss := a.cachedNamespaceNames() - if _, ok := cnss[n]; ok { - return true, nil - } - - dial, err := a.Dial() - if err != nil { - return false, err - } - ctx, cancel := context.WithTimeout(context.Background(), a.config.CallTimeout()) - defer cancel() - _, err = dial.CoreV1().Namespaces().Get(ctx, n, metav1.GetOptions{}) + nn, err := a.ValidNamespaceNames() if err != nil { return false, err } - cnss[n] = struct{}{} - a.cache.Add(cacheNSKey, cnss, cacheExpiry) + _, ok := nn[n] - return true, nil + return ok, nil } // ValidNamespaceNames returns all available namespaces. @@ -283,6 +245,12 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { return nss, nil } } + + ok, err := a.CanI(ClusterScope, "v1/namespaces", "", []string{ListVerb}) + if !ok || err != nil { + return nil, fmt.Errorf("user not authorized to list all namespaces") + } + dial, err := a.Dial() if err != nil { return nil, err diff --git a/internal/client/config.go b/internal/client/config.go index 0dafc59e64..65c3d6d777 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -20,7 +20,7 @@ const ( defaultCallTimeoutDuration time.Duration = 15 * time.Second // UsePersistentConfig caches client config to avoid reloads. - UsePersistentConfig = false + UsePersistentConfig = true ) // Config tracks a kubernetes configuration. diff --git a/internal/config/alias.go b/internal/config/alias.go index 35cd8e788c..be53f4027f 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -4,7 +4,9 @@ package config import ( + "errors" "fmt" + "io/fs" "os" "sync" @@ -136,7 +138,7 @@ func (a *Aliases) LoadFile(path string) error { if path == "" { return nil } - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } diff --git a/internal/config/config.go b/internal/config/config.go index 0390bf5e6a..5f7c2471d1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,7 @@ package config import ( "errors" "fmt" + "io/fs" "os" "github.com/derailed/k9s/internal/client" @@ -52,14 +53,13 @@ func (c *Config) ContextAliasesPath() string { } // ContextPluginsPath returns a context specific plugins file spec. -func (c *Config) ContextPluginsPath() string { +func (c *Config) ContextPluginsPath() (string, error) { ct, err := c.K9s.ActiveContext() if err != nil { - log.Error().Err(err).Msgf("active context load failed") - return "" + return "", err } - return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName) + return AppContextPluginsFile(ct.GetClusterName(), c.K9s.activeContextName), nil } // Refine the configuration based on cli args. @@ -209,9 +209,9 @@ func (c *Config) Merge(c1 *Config) { } // Load loads K9s configuration from file. -func (c *Config) Load(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := c.Save(); err != nil { +func (c *Config) Load(path string, force bool) error { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { + if err := c.Save(force); err != nil { return err } } @@ -234,12 +234,12 @@ func (c *Config) Load(path string) error { } // Save configuration to disk. -func (c *Config) Save() error { +func (c *Config) Save(force bool) error { c.Validate() - if err := c.K9s.Save(); err != nil { + if err := c.K9s.Save(force); err != nil { return err } - if _, err := os.Stat(AppConfigFile); os.IsNotExist(err) { + if _, err := os.Stat(AppConfigFile); errors.Is(err, fs.ErrNotExist) { return c.SaveFile(AppConfigFile) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 10c66cb670..3910eb5c74 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,6 +4,7 @@ package config_test import ( + "errors" "fmt" "os" "path/filepath" @@ -58,7 +59,7 @@ func TestConfigSave(t *testing.T) { c.K9s.Override(u.k9sFlags) assert.NoError(t, c.Refine(u.flags, u.k9sFlags, client.NewConfig(u.flags))) } - assert.NoError(t, c.Save()) + assert.NoError(t, c.Save(true)) bb, err := os.ReadFile(config.AppConfigFile) assert.NoError(t, err) ee, err := os.ReadFile("testdata/configs/default.yaml") @@ -265,16 +266,19 @@ func TestContextAliasesPath(t *testing.T) { func TestContextPluginsPath(t *testing.T) { uu := map[string]struct { - ct string - e string + ct, e string + err error }{ - "empty": {}, + "empty": { + err: errors.New(`no context found for: ""`), + }, "happy": { ct: "ct-1-1", e: "/tmp/test/cl-1/ct-1-1/plugins.yaml", }, "not-exists": { - ct: "fred", + ct: "fred", + err: errors.New(`no context found for: "fred"`), }, } @@ -283,7 +287,11 @@ func TestContextPluginsPath(t *testing.T) { t.Run(k, func(t *testing.T) { c := mock.NewMockConfig() _, _ = c.K9s.ActivateContext(u.ct) - assert.Equal(t, u.e, c.ContextPluginsPath()) + s, err := c.ContextPluginsPath() + if err != nil { + assert.Equal(t, u.err, err) + } + assert.Equal(t, u.e, s) }) } } @@ -309,7 +317,7 @@ Invalid type. Expected: boolean, given: string`, u := uu[k] t.Run(k, func(t *testing.T) { cfg := config.NewConfig(nil) - if err := cfg.Load(u.f); err != nil { + if err := cfg.Load(u.f, true); err != nil { assert.Equal(t, u.err, err.Error()) } }) @@ -520,14 +528,14 @@ func TestConfigValidate(t *testing.T) { cfg := mock.NewMockConfig() cfg.SetConnection(mock.NewMockConnection()) - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) cfg.Validate() } func TestConfigLoad(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) assert.Equal(t, 2, cfg.K9s.RefreshRate) assert.Equal(t, int64(200), cfg.K9s.Logger.TailCount) assert.Equal(t, 2000, cfg.K9s.Logger.BufferSize) @@ -536,13 +544,13 @@ func TestConfigLoad(t *testing.T) { func TestConfigLoadCrap(t *testing.T) { cfg := mock.NewMockConfig() - assert.NotNil(t, cfg.Load("testdata/configs/k9s_not_there.yaml")) + assert.NotNil(t, cfg.Load("testdata/configs/k9s_not_there.yaml", true)) } func TestConfigSaveFile(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) cfg.K9s.RefreshRate = 100 cfg.K9s.ReadOnly = true @@ -561,7 +569,7 @@ func TestConfigSaveFile(t *testing.T) { func TestConfigReset(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) cfg.Reset() cfg.Validate() diff --git a/internal/config/data/config.go b/internal/config/data/config.go index 130bf15b36..a03020f809 100644 --- a/internal/config/data/config.go +++ b/internal/config/data/config.go @@ -26,6 +26,15 @@ func NewConfig(ct *api.Context) *Config { } } +func (c *Config) Merge(c1 *Config) { + if c1 == nil { + return + } + if c.Context != nil && c1.Context != nil { + c.Context.merge(c1.Context) + } +} + // Validate ensures config is in norms. func (c *Config) Validate(conn client.Connection, ks KubeSettings) { c.mx.Lock() diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 32a777918d..591f072f7b 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -56,6 +56,13 @@ func NewContextFromKubeConfig(ks KubeSettings) (*Context, error) { return NewContextFromConfig(ct), nil } +func (c *Context) merge(old *Context) { + if old == nil { + return + } + c.Namespace.merge(old.Namespace) + +} func (c *Context) GetClusterName() string { c.mx.RLock() defer c.mx.RUnlock() diff --git a/internal/config/data/context_int_test.go b/internal/config/data/context_int_test.go new file mode 100644 index 0000000000..1a37ff99a5 --- /dev/null +++ b/internal/config/data/context_int_test.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package data + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_contextMerge(t *testing.T) { + uu := map[string]struct { + c1, c2, e *Context + }{ + "empty": {}, + "nil": { + c1: &Context{ + Namespace: &Namespace{ + Active: "ns1", + Favorites: []string{"ns1", "ns2", "ns3"}, + }, + }, + e: &Context{ + Namespace: &Namespace{ + Active: "ns1", + Favorites: []string{"ns1", "ns2", "ns3"}, + }, + }, + }, + "deltas": { + c1: &Context{ + Namespace: &Namespace{ + Active: "ns1", + Favorites: []string{"ns1", "ns2", "ns3"}, + }, + }, + c2: &Context{ + Namespace: &Namespace{ + Active: "ns10", + Favorites: []string{"ns10", "ns11", "ns12"}, + }, + }, + e: &Context{ + Namespace: &Namespace{ + Active: "ns1", + Favorites: []string{"ns1", "ns2", "ns3", "ns10", "ns11", "ns12"}, + }, + }, + }, + "deltas-locked": { + c1: &Context{ + Namespace: &Namespace{ + Active: "ns1", + LockFavorites: true, + Favorites: []string{"ns1", "ns2", "ns3"}, + }, + }, + c2: &Context{ + Namespace: &Namespace{ + Active: "ns10", + Favorites: []string{"ns10", "ns11", "ns12"}, + }, + }, + e: &Context{ + Namespace: &Namespace{ + Active: "ns1", + LockFavorites: true, + Favorites: []string{"ns1", "ns2", "ns3"}, + }, + }, + }, + } + + for k, u := range uu { + t.Run(k, func(t *testing.T) { + u.c1.merge(u.c2) + assert.Equal(t, u.e, u.c1) + }) + } +} diff --git a/internal/config/data/dir.go b/internal/config/data/dir.go index b945706f24..3b045578f9 100644 --- a/internal/config/data/dir.go +++ b/internal/config/data/dir.go @@ -6,6 +6,7 @@ package data import ( "errors" "fmt" + "io/fs" "os" "path/filepath" "sync" @@ -34,20 +35,21 @@ func (d *Dir) Load(n string, ct *api.Context) (*Config, error) { if ct == nil { return nil, errors.New("api.Context must not be nil") } - var ( - path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) - cfg *Config - err error - ) - if f, e := os.Stat(path); os.IsNotExist(e) || f.Size() == 0 { + var path = filepath.Join(d.root, SanitizeContextSubpath(ct.Cluster, n), MainConfigFile) + + f, err := os.Stat(path) + if errors.Is(err, fs.ErrPermission) { + return nil, err + } + if errors.Is(err, fs.ErrNotExist) || (f != nil && f.Size() == 0) { log.Debug().Msgf("Context config not found! Generating... %q", path) - cfg, err = d.genConfig(path, ct) - } else { - log.Debug().Msgf("Found existing context config: %q", path) - cfg, err = d.loadConfig(path) + return d.genConfig(path, ct) + } + if err != nil { + return nil, err } - return cfg, err + return d.loadConfig(path) } func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { @@ -60,6 +62,10 @@ func (d *Dir) genConfig(path string, ct *api.Context) (*Config, error) { } func (d *Dir) Save(path string, c *Config) error { + if cfg, err := d.loadConfig(path); err == nil { + c.Merge(cfg) + } + d.mx.Lock() defer d.mx.Unlock() diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go index 59f043870c..ae6cc6e9c6 100644 --- a/internal/config/data/helpers.go +++ b/internal/config/data/helpers.go @@ -4,6 +4,8 @@ package data import ( + "errors" + "io/fs" "os" "path/filepath" "regexp" @@ -38,7 +40,7 @@ func EnsureDirPath(path string, mod os.FileMode) error { // EnsureFullPath ensures a directory exist from the given path. func EnsureFullPath(path string, mod os.FileMode) error { - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { if err = os.MkdirAll(path, mod); err != nil { return err } diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 1926289460..57f9d3e678 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -38,6 +38,21 @@ func NewActiveNamespace(n string) *Namespace { } } +func (n *Namespace) merge(old *Namespace) { + n.mx.Lock() + defer n.mx.Unlock() + + if n.LockFavorites { + return + } + for _, fav := range old.Favorites { + if InList(n.Favorites, fav) { + continue + } + n.Favorites = append(n.Favorites, fav) + } +} + // Validate validates a namespace is setup correctly. func (n *Namespace) Validate(c client.Connection) { n.mx.RLock() diff --git a/internal/config/files.go b/internal/config/files.go index b4b1e02bdb..2b246d5172 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -5,6 +5,8 @@ package config import ( _ "embed" + "errors" + "io/fs" "os" "path/filepath" @@ -238,7 +240,7 @@ func EnsureBenchmarksCfgFile(cluster, context string) (string, error) { if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { return "", err } - if _, err := os.Stat(f); os.IsNotExist(err) { + if _, err := os.Stat(f); errors.Is(err, fs.ErrNotExist) { return f, os.WriteFile(f, benchmarkTpl, data.DefaultFileMod) } @@ -251,7 +253,7 @@ func EnsureAliasesCfgFile() (string, error) { if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { return "", err } - if _, err := os.Stat(f); os.IsNotExist(err) { + if _, err := os.Stat(f); errors.Is(err, fs.ErrNotExist) { return f, os.WriteFile(f, aliasesTpl, data.DefaultFileMod) } @@ -264,7 +266,7 @@ func EnsureHotkeysCfgFile() (string, error) { if err := data.EnsureDirPath(f, data.DefaultDirMod); err != nil { return "", err } - if _, err := os.Stat(f); os.IsNotExist(err) { + if _, err := os.Stat(f); errors.Is(err, fs.ErrNotExist) { return f, os.WriteFile(f, hotkeysTpl, data.DefaultFileMod) } diff --git a/internal/config/hotkey.go b/internal/config/hotkey.go index 91801eb8b7..65a651e40a 100644 --- a/internal/config/hotkey.go +++ b/internal/config/hotkey.go @@ -4,7 +4,9 @@ package config import ( + "errors" "fmt" + "io/fs" "os" "github.com/derailed/k9s/internal/config/data" @@ -38,7 +40,7 @@ func (h HotKeys) Load(path string) error { if err := h.LoadHotKeys(AppHotKeysFile); err != nil { return err } - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } @@ -47,7 +49,7 @@ func (h HotKeys) Load(path string) error { // LoadHotKeys loads plugins from a given file. func (h HotKeys) LoadHotKeys(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } bb, err := os.ReadFile(path) diff --git a/internal/config/k9s.go b/internal/config/k9s.go index a1b45558b9..953fb7ca3b 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -4,7 +4,10 @@ package config import ( + "errors" "fmt" + "io/fs" + "os" "path/filepath" "sync" @@ -67,7 +70,7 @@ func (k *K9s) resetConnection(conn client.Connection) { } // Save saves the k9s config to disk. -func (k *K9s) Save() error { +func (k *K9s) Save(force bool) error { if k.getActiveConfig() == nil { log.Warn().Msgf("Save failed. no active config detected") return nil @@ -77,8 +80,11 @@ func (k *K9s) Save() error { data.SanitizeContextSubpath(k.activeConfig.Context.GetClusterName(), k.getActiveContextName()), data.MainConfigFile, ) + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) || force { + return k.dir.Save(path, k.getActiveConfig()) + } - return k.dir.Save(path, k.getActiveConfig()) + return nil } // Merge merges k9s configs. diff --git a/internal/config/k9s_int_test.go b/internal/config/k9s_int_test.go index 0bee7fcce3..f950502e57 100644 --- a/internal/config/k9s_int_test.go +++ b/internal/config/k9s_int_test.go @@ -119,7 +119,7 @@ func Test_screenDumpDirOverride(t *testing.T) { u := uu[k] t.Run(k, func(t *testing.T) { cfg := NewConfig(nil) - assert.NoError(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.NoError(t, cfg.Load("testdata/configs/k9s.yaml", true)) cfg.K9s.manualScreenDumpDir = &u.dir assert.Equal(t, u.e, cfg.K9s.AppScreenDumpDir()) diff --git a/internal/config/k9s_test.go b/internal/config/k9s_test.go index 69e76189c8..d74c59c39d 100644 --- a/internal/config/k9s_test.go +++ b/internal/config/k9s_test.go @@ -136,13 +136,13 @@ func TestContextScreenDumpDir(t *testing.T) { _, err := cfg.K9s.ActivateContext("ct-1-1") assert.NoError(t, err) - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) assert.Equal(t, "/tmp/k9s-test/screen-dumps/cl-1/ct-1-1", cfg.K9s.ContextScreenDumpDir()) } func TestAppScreenDumpDir(t *testing.T) { cfg := mock.NewMockConfig() - assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml")) + assert.Nil(t, cfg.Load("testdata/configs/k9s.yaml", true)) assert.Equal(t, "/tmp/k9s-test/screen-dumps", cfg.K9s.AppScreenDumpDir()) } diff --git a/internal/config/mock/test_helpers.go b/internal/config/mock/test_helpers.go index c5a3b69a75..eae5709d43 100644 --- a/internal/config/mock/test_helpers.go +++ b/internal/config/mock/test_helpers.go @@ -4,7 +4,9 @@ package mock import ( + "errors" "fmt" + "io/fs" "os" "strings" @@ -21,7 +23,7 @@ import ( ) func EnsureDir(d string) error { - if _, err := os.Stat(d); os.IsNotExist(err) { + if _, err := os.Stat(d); errors.Is(err, fs.ErrNotExist) { return os.MkdirAll(d, 0700) } if err := os.RemoveAll(d); err != nil { diff --git a/internal/config/plugin.go b/internal/config/plugin.go index e0992fe5d6..e57aa53a85 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -6,6 +6,7 @@ package config import ( "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -94,7 +95,7 @@ func (p Plugins) loadPluginDir(dir string) error { } func (p *Plugins) load(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } bb, err := os.ReadFile(path) diff --git a/internal/config/views.go b/internal/config/views.go index 1bab44c80d..cfa1b7caa1 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -4,8 +4,11 @@ package config import ( + "errors" "fmt" + "io/fs" "os" + "strings" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" @@ -25,6 +28,26 @@ type ViewSetting struct { SortColumn string `yaml:"sortColumn"` } +func (v *ViewSetting) HasCols() bool { + return len(v.Columns) > 0 +} + +func (v *ViewSetting) IsBlank() bool { + return v == nil || len(v.Columns) == 0 +} + +func (v *ViewSetting) SortCol() (string, bool, error) { + if v == nil || v.SortColumn == "" { + return "", false, fmt.Errorf("no sort column specified") + } + tt := strings.Split(v.SortColumn, ":") + if len(tt) < 2 { + return "", false, fmt.Errorf("invalid sort column spec: %q. must be col-name:asc|desc", v.SortColumn) + } + + return tt[0], tt[1] == "desc", nil +} + // CustomView represents a collection of view customization. type CustomView struct { Views map[string]ViewSetting `yaml:"views"` @@ -48,7 +71,7 @@ func (v *CustomView) Reset() { // Load loads view configurations. func (v *CustomView) Load(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return nil } bb, err := os.ReadFile(path) diff --git a/internal/dao/cluster.go b/internal/dao/cluster.go index 6dcc8d25dd..dab4fbf158 100644 --- a/internal/dao/cluster.go +++ b/internal/dao/cluster.go @@ -38,7 +38,7 @@ var ( _ RefScanner = (*DaemonSet)(nil) _ RefScanner = (*Job)(nil) _ RefScanner = (*CronJob)(nil) - _ RefScanner = (*Pod)(nil) + // _ RefScanner = (*Pod)(nil) ) func scanners() map[string]RefScanner { @@ -48,7 +48,7 @@ func scanners() map[string]RefScanner { "apps/v1/daemonsets": &DaemonSet{}, "batch/v1/jobs": &Job{}, "batch/v1/cronjobs": &CronJob{}, - "v1/pods": &Pod{}, + // "v1/pods": &Pod{}, } } diff --git a/internal/dao/cm.go b/internal/dao/cm.go new file mode 100644 index 0000000000..0910d70495 --- /dev/null +++ b/internal/dao/cm.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package dao + +var ( + _ Accessor = (*ConfigMap)(nil) +) + +// ConfigMap represents a configmap resource. +type ConfigMap struct { + Resource +} diff --git a/internal/dao/crd.go b/internal/dao/crd.go index 16f06076d7..5f8455a27c 100644 --- a/internal/dao/crd.go +++ b/internal/dao/crd.go @@ -3,15 +3,6 @@ package dao -import ( - "context" - - "github.com/derailed/k9s/internal" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" -) - var ( _ Accessor = (*CustomResourceDefinition)(nil) _ Nuker = (*CustomResourceDefinition)(nil) @@ -21,28 +12,3 @@ var ( type CustomResourceDefinition struct { Resource } - -// IsHappy check for happy deployments. -func (c *CustomResourceDefinition) IsHappy(crd v1.CustomResourceDefinition) bool { - versions := make([]string, 0, 3) - for _, v := range crd.Spec.Versions { - if v.Served && !v.Deprecated { - versions = append(versions, v.Name) - break - } - } - - return len(versions) > 0 -} - -// List returns a collection of nodes. -func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtime.Object, error) { - strLabel, ok := ctx.Value(internal.KeyLabels).(string) - labelSel := labels.Everything() - if sel, e := labels.ConvertSelectorToLabelsMap(strLabel); ok && e == nil { - labelSel = sel.AsSelector() - } - - const gvr = "apiextensions.k8s.io/v1/customresourcedefinitions" - return c.getFactory().List(gvr, "-", false, labelSel) -} diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 4f88d4a02a..f3273399e9 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -49,11 +49,6 @@ func (d *Deployment) ListImages(ctx context.Context, fqn string) ([]string, erro return render.ExtractImages(&dp.Spec.Template.Spec), nil } -// IsHappy check for happy deployments. -func (d *Deployment) IsHappy(dp appsv1.Deployment) bool { - return dp.Status.Replicas == dp.Status.AvailableReplicas -} - // Scale a Deployment. func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error { ns, n := client.Namespaced(path) diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 0e2d9430a0..b0bf8c0b69 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -51,11 +51,6 @@ func (d *DaemonSet) ListImages(ctx context.Context, fqn string) ([]string, error return render.ExtractImages(&ds.Spec.Template.Spec), nil } -// IsHappy check for happy deployments. -func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool { - return ds.Status.DesiredNumberScheduled == ds.Status.CurrentNumberScheduled -} - // Restart a DaemonSet rollout. func (d *DaemonSet) Restart(ctx context.Context, path string) error { o, err := d.getFactory().Get("apps/v1/daemonsets", path, true, labels.Everything()) @@ -140,7 +135,7 @@ func podLogs(ctx context.Context, sel map[string]string, opts *LogOptions) ([]Lo } opts.MultiPods = true - po := Pod{} + var po Pod po.Init(f, client.NewGVR("v1/pods")) outs := make([]LogChan, 0, len(oo)) diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index f4818f4863..552d31378f 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -6,47 +6,65 @@ package dao import ( "bytes" "errors" + "fmt" "math" - "regexp" + "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/printers" ) -const defaultServiceAccount = "default" - -var ( - inverseRx = regexp.MustCompile(`\A\!`) - fuzzyRx = regexp.MustCompile(`\A-f\s?([\w-]+)\b`) +const ( + defaultServiceAccount = "default" + defaultContainerAnnotation = "kubectl.kubernetes.io/default-container" ) -func inList(ll []string, s string) bool { - for _, l := range ll { - if l == s { - return true +// GetDefaultContainer returns a container name if specified in an annotation. +func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) { + defaultContainer, ok := m.Annotations[defaultContainerAnnotation] + if !ok { + return "", false + } + + for _, container := range spec.Containers { + if container.Name == defaultContainer { + return defaultContainer, true } } - return false + log.Warn().Msg(defaultContainer + " container not found. " + defaultContainerAnnotation + " annotation will be ignored") + + return "", false } -// IsInverseSelector checks if inverse char has been provided. -func IsInverseSelector(s string) bool { - if s == "" { - return false +func extractFQN(o runtime.Object) string { + u, ok := o.(*unstructured.Unstructured) + if !ok { + log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o)) + return client.NA } - return inverseRx.MatchString(s) + + return FQN(u.GetNamespace(), u.GetName()) } -// HasFuzzySelector checks if query is fuzzy. -func HasFuzzySelector(s string) (string, bool) { - mm := fuzzyRx.FindStringSubmatch(s) - if len(mm) != 2 { - return "", false +// FQN returns a fully qualified resource name. +func FQN(ns, n string) string { + if ns == "" { + return n } + return ns + "/" + n +} - return mm[1], true +func inList(ll []string, s string) bool { + for _, l := range ll { + if l == s { + return true + } + } + return false } func toPerc(v1, v2 float64) float64 { diff --git a/internal/dao/log_items.go b/internal/dao/log_items.go index d8cd1707cf..898c0c842f 100644 --- a/internal/dao/log_items.go +++ b/internal/dao/log_items.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + "github.com/derailed/k9s/internal" "github.com/sahilm/fuzzy" ) @@ -174,7 +175,7 @@ func (l *LogItems) Filter(index int, q string, showTime bool) ([]int, [][]int, e if q == "" { return nil, nil, nil } - if f, ok := HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { mm, ii := l.fuzzyFilter(index, f, showTime) return mm, ii, nil } @@ -200,7 +201,7 @@ func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) ([]int, [][]i func (l *LogItems) filterLogs(index int, q string, showTime bool) ([]int, [][]int, error) { var invert bool - if IsInverseSelector(q) { + if internal.IsInverseSelector(q) { invert = true q = q[1:] } diff --git a/internal/dao/ns.go b/internal/dao/ns.go index 4daec5f9bc..33a63dced0 100644 --- a/internal/dao/ns.go +++ b/internal/dao/ns.go @@ -3,27 +3,11 @@ package dao -import ( - "context" - - "k8s.io/apimachinery/pkg/runtime" -) - var ( - _ Accessor = (*Pod)(nil) + _ Accessor = (*Namespace)(nil) ) // Namespace represents a namespace resource. type Namespace struct { - Generic -} - -// List returns a collection of namespaces. -func (n *Namespace) List(ctx context.Context, ns string) ([]runtime.Object, error) { - oo, err := n.Generic.List(ctx, ns) - if err != nil { - return nil, err - } - - return oo, nil + Resource } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index e9c6e09f46..4b03d66ed7 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -37,9 +37,8 @@ var ( ) const ( - logRetryCount = 20 - logRetryWait = 1 * time.Second - defaultContainerAnnotation = "kubectl.kubernetes.io/default-container" + logRetryCount = 20 + logRetryWait = 1 * time.Second ) // Pod represents a pod resource. @@ -47,17 +46,6 @@ type Pod struct { Resource } -// IsHappy check for happy deployments. -func (p *Pod) IsHappy(po v1.Pod) bool { - for _, c := range po.Status.Conditions { - if c.Status == v1.ConditionFalse { - return false - } - } - - return true -} - // Get returns a resource instance if found, else an error. func (p *Pod) Get(ctx context.Context, path string) (runtime.Object, error) { o, err := p.Resource.Get(ctx, path) @@ -425,24 +413,6 @@ func MetaFQN(m metav1.ObjectMeta) string { return FQN(m.Namespace, m.Name) } -// FQN returns a fully qualified resource name. -func FQN(ns, n string) string { - if ns == "" { - return n - } - return ns + "/" + n -} - -func extractFQN(o runtime.Object) string { - u, ok := o.(*unstructured.Unstructured) - if !ok { - log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o)) - return client.NA - } - - return FQN(u.GetNamespace(), u.GetName()) -} - // GetPodSpec returns a pod spec given a resource. func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) { pod, err := p.GetInstance(path) @@ -500,21 +470,6 @@ func (p *Pod) isControlled(path string) (string, bool, error) { return "", false, nil } -// GetDefaultContainer returns a container name if specified in an annotation. -func GetDefaultContainer(m metav1.ObjectMeta, spec v1.PodSpec) (string, bool) { - defaultContainer, ok := m.Annotations[defaultContainerAnnotation] - if ok { - for _, container := range spec.Containers { - if container.Name == defaultContainer { - return defaultContainer, true - } - } - log.Warn().Msg(defaultContainer + " container not found. " + defaultContainerAnnotation + " annotation will be ignored") - } - - return "", false -} - func (p *Pod) Sanitize(ctx context.Context, ns string) (int, error) { oo, err := p.Resource.List(ctx, ns) if err != nil { diff --git a/internal/dao/popeye.go b/internal/dao/popeye.go index ec1aa801d3..7cb08e9a27 100644 --- a/internal/dao/popeye.go +++ b/internal/dao/popeye.go @@ -3,7 +3,7 @@ package dao -// !!BOZO!! +// !!BOZO!! Popeye // import ( // "bytes" // "context" diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 4060d4e9b0..3aaf1bf5ac 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -89,29 +89,32 @@ func NewMeta() *Meta { // Customize here for non resource types or types with metrics or logs. func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { m := Accessors{ - client.NewGVR("workloads"): &Workload{}, - client.NewGVR("contexts"): &Context{}, - client.NewGVR("containers"): &Container{}, - client.NewGVR("scans"): &ImageScan{}, - client.NewGVR("screendumps"): &ScreenDump{}, - client.NewGVR("benchmarks"): &Benchmark{}, - client.NewGVR("portforwards"): &PortForward{}, - client.NewGVR("v1/services"): &Service{}, - client.NewGVR("v1/pods"): &Pod{}, - client.NewGVR("v1/nodes"): &Node{}, - client.NewGVR("apps/v1/deployments"): &Deployment{}, - client.NewGVR("apps/v1/daemonsets"): &DaemonSet{}, - client.NewGVR("apps/v1/statefulsets"): &StatefulSet{}, - client.NewGVR("apps/v1/replicasets"): &ReplicaSet{}, - client.NewGVR("batch/v1/cronjobs"): &CronJob{}, - client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, - client.NewGVR("batch/v1/jobs"): &Job{}, - client.NewGVR("v1/namespaces"): &Namespace{}, - // !!BOZO!! + client.NewGVR("workloads"): &Workload{}, + client.NewGVR("contexts"): &Context{}, + client.NewGVR("containers"): &Container{}, + client.NewGVR("scans"): &ImageScan{}, + client.NewGVR("screendumps"): &ScreenDump{}, + client.NewGVR("benchmarks"): &Benchmark{}, + client.NewGVR("portforwards"): &PortForward{}, + client.NewGVR("dir"): &Dir{}, + client.NewGVR("v1/services"): &Service{}, + client.NewGVR("v1/pods"): &Pod{}, + client.NewGVR("v1/nodes"): &Node{}, + client.NewGVR("v1/namespaces"): &Namespace{}, + client.NewGVR("v1/configmap"): &ConfigMap{}, + client.NewGVR("v1/secrets"): &Secret{}, + client.NewGVR("apps/v1/deployments"): &Deployment{}, + client.NewGVR("apps/v1/daemonsets"): &DaemonSet{}, + client.NewGVR("apps/v1/statefulsets"): &StatefulSet{}, + client.NewGVR("apps/v1/replicasets"): &ReplicaSet{}, + client.NewGVR("batch/v1/cronjobs"): &CronJob{}, + client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, + client.NewGVR("batch/v1/jobs"): &Job{}, + client.NewGVR("helm"): &HelmChart{}, + client.NewGVR("helm-history"): &HelmHistory{}, + client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions"): &CustomResourceDefinition{}, + // !!BOZO!! Popeye //client.NewGVR("popeye"): &Popeye{}, - client.NewGVR("helm"): &HelmChart{}, - client.NewGVR("helm-history"): &HelmHistory{}, - client.NewGVR("dir"): &Dir{}, } r, ok := m[gvr] @@ -369,6 +372,7 @@ func loadPreferred(f Factory, m ResourceMetas) error { if err != nil { return err } + dial.Invalidate() rr, err := dial.ServerPreferredResources() if err != nil { log.Debug().Err(err).Msgf("Failed to load preferred resources") diff --git a/internal/dao/secret.go b/internal/dao/secret.go index 8cc47868c6..49dcaa3000 100644 --- a/internal/dao/secret.go +++ b/internal/dao/secret.go @@ -15,13 +15,13 @@ import ( // Secret represents a secret K8s resource. type Secret struct { - Table + Resource decode bool } // Describe describes a secret that can be encoded or decoded. func (s *Secret) Describe(path string) (string, error) { - encodedDescription, err := s.Table.Describe(path) + encodedDescription, err := s.Generic.Describe(path) if err != nil { return "", err @@ -51,13 +51,13 @@ func (s *Secret) Decode(encodedDescription, path string) (string, error) { dataEndIndex := strings.Index(encodedDescription, "====") if dataEndIndex == -1 { - return "", fmt.Errorf("Unable to find data section in secret description") + return "", fmt.Errorf("unable to find data section in secret description") } dataEndIndex += 4 if dataEndIndex >= len(encodedDescription) { - return "", fmt.Errorf("Data section in secret description is invalid") + return "", fmt.Errorf("data section in secret description is invalid") } // Remove the encoded part from k8s's describe API diff --git a/internal/dao/sts.go b/internal/dao/sts.go index f562adef7c..0137640957 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -50,11 +50,6 @@ func (s *StatefulSet) ListImages(ctx context.Context, fqn string) ([]string, err return render.ExtractImages(&sts.Spec.Template.Spec), nil } -// IsHappy check for happy sts. -func (s *StatefulSet) IsHappy(sts appsv1.StatefulSet) bool { - return sts.Status.Replicas == sts.Status.ReadyReplicas -} - // Scale a StatefulSet. func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) error { ns, n := client.Namespaced(path) diff --git a/internal/dao/table.go b/internal/dao/table.go index c2569da183..6936772f82 100644 --- a/internal/dao/table.go +++ b/internal/dao/table.go @@ -16,7 +16,9 @@ import ( "k8s.io/client-go/rest" ) -// BOZO!! Figure out how to convert to table def and use factory. +const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" + +var genScheme = runtime.NewScheme() // Table retrieves K8s resources as tabular data. type Table struct { @@ -25,19 +27,19 @@ type Table struct { // Get returns a given resource. func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { - a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) - _, codec := t.codec() - - c, err := t.getClient() + f, p := t.codec() + c, err := t.getClient(f) if err != nil { return nil, err } + ns, n := client.Namespaced(path) + a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) req := c.Get(). SetHeader("Accept", a). Name(n). Resource(t.gvr.R()). - VersionedParams(&metav1.TableOptions{}, codec) + VersionedParams(&metav1.TableOptions{}, p) if ns != client.ClusterScope { req = req.Namespace(ns) } @@ -48,18 +50,24 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { // List all Resources in a given namespace. func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { labelSel, _ := ctx.Value(internal.KeyLabels).(string) - a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) - _, codec := t.codec() + fieldSel, _ := ctx.Value(internal.KeyFields).(string) - c, err := t.getClient() + f, p := t.codec() + c, err := t.getClient(f) if err != nil { return nil, err } + a := fmt.Sprintf(gvFmt, metav1.SchemeGroupVersion.Version, metav1.GroupName) o, err := c.Get(). SetHeader("Accept", a). Namespace(ns). Resource(t.gvr.R()). - VersionedParams(&metav1.ListOptions{LabelSelector: labelSel}, codec). + VersionedParams(&metav1.ListOptions{ + LabelSelector: labelSel, + FieldSelector: fieldSel, + ResourceVersion: "0", + ResourceVersionMatch: v1.ResourceVersionMatchNotOlderThan, + }, p). Do(ctx).Get() if err != nil { return nil, err @@ -71,9 +79,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { // ---------------------------------------------------------------------------- // Helpers... -const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" - -func (t *Table) getClient() (*rest.RESTClient, error) { +func (t *Table) getClient(f serializer.CodecFactory) (*rest.RESTClient, error) { cfg, err := t.Client().RestConfig() if err != nil { return nil, err @@ -84,8 +90,7 @@ func (t *Table) getClient() (*rest.RESTClient, error) { if t.gvr.G() == "" { cfg.APIPath = "/api" } - codec, _ := t.codec() - cfg.NegotiatedSerializer = codec.WithoutConversion() + cfg.NegotiatedSerializer = f.WithoutConversion() crRestClient, err := rest.RESTClientFor(cfg) if err != nil { @@ -96,11 +101,12 @@ func (t *Table) getClient() (*rest.RESTClient, error) { } func (t *Table) codec() (serializer.CodecFactory, runtime.ParameterCodec) { - scheme := runtime.NewScheme() + var tt metav1.Table + opts := metav1.TableOptions{IncludeObject: v1.IncludeObject} gv := t.gvr.GV() - metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1.Table{}, &metav1.TableOptions{IncludeObject: v1.IncludeObject}) - scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{IncludeObject: v1.IncludeObject}) + metav1.AddToGroupVersion(genScheme, gv) + genScheme.AddKnownTypes(gv, &tt, &opts) + genScheme.AddKnownTypes(metav1.SchemeGroupVersion, &tt, &opts) - return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) + return serializer.NewCodecFactory(genScheme), runtime.NewParameterCodec(genScheme) } diff --git a/internal/helpers.go b/internal/helpers.go new file mode 100644 index 0000000000..aa1d43e50f --- /dev/null +++ b/internal/helpers.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package internal + +import ( + "regexp" + "strings" + + "github.com/derailed/k9s/internal/view/cmd" +) + +var ( + inverseRx = regexp.MustCompile(`\A\!`) + fuzzyRx = regexp.MustCompile(`\A-f\s?([\w-]+)\b`) + labelRx = regexp.MustCompile(`\A\-l`) +) + +// Helpers... + +// IsInverseSelector checks if inverse char has been provided. +func IsInverseSelector(s string) bool { + if s == "" { + return false + } + return inverseRx.MatchString(s) +} + +// IsLabelSelector checks if query is a label query. +func IsLabelSelector(s string) bool { + if labelRx.MatchString(s) { + return true + } + + return !strings.Contains(s, " ") && cmd.ToLabels(s) != nil +} + +// IsFuzzySelector checks if query is fuzzy. +func IsFuzzySelector(s string) (string, bool) { + mm := fuzzyRx.FindStringSubmatch(s) + if len(mm) != 2 { + return "", false + } + + return mm[1], true +} diff --git a/internal/helpers_test.go b/internal/helpers_test.go new file mode 100644 index 0000000000..7ac6bc9985 --- /dev/null +++ b/internal/helpers_test.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package internal_test + +import ( + "testing" + + "github.com/derailed/k9s/internal" + "github.com/stretchr/testify/assert" +) + +func TestIsLabelSelector(t *testing.T) { + uu := map[string]struct { + s string + ok bool + }{ + "empty": {s: ""}, + "cool": {s: "-l app=fred,env=blee", ok: true}, + "no-flag": {s: "app=fred,env=blee", ok: true}, + "no-space": {s: "-lapp=fred,env=blee", ok: true}, + "wrong-flag": {s: "-f app=fred,env=blee"}, + "missing-key": {s: "=fred"}, + "missing-val": {s: "fred="}, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.ok, internal.IsLabelSelector(u.s)) + }) + } +} diff --git a/internal/model/cluster.go b/internal/model/cluster.go index 3182a0e13c..e598217e92 100644 --- a/internal/model/cluster.go +++ b/internal/model/cluster.go @@ -108,7 +108,7 @@ func (c *Cluster) Metrics(ctx context.Context, mx *client.ClusterMetrics) error } } if nn == nil { - return errors.New("Unable to fetch nodes list") + return errors.New("unable to fetch nodes list") } if len(nn.Items) > 0 { c.cache.Add(clusterNodesKey, nn, clusterCacheExpiry) diff --git a/internal/model/cluster_info.go b/internal/model/cluster_info.go index 5f1653be38..62f6e7f1dd 100644 --- a/internal/model/cluster_info.go +++ b/internal/model/cluster_info.go @@ -143,8 +143,6 @@ func (c *ClusterInfo) Refresh() { var mx client.ClusterMetrics if err := c.cluster.Metrics(ctx, &mx); err == nil { data.Cpu, data.Mem, data.Ephemeral = mx.PercCPU, mx.PercMEM, mx.PercEphemeral - } else { - log.Warn().Err(err).Msgf("Cluster metrics failed") } } data.K9sVer = c.version diff --git a/internal/model/describe.go b/internal/model/describe.go index 55b525bfb4..2d5d845a62 100644 --- a/internal/model/describe.go +++ b/internal/model/describe.go @@ -12,6 +12,7 @@ import ( "time" backoff "github.com/cenkalti/backoff/v4" + "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/rs/zerolog/log" @@ -66,7 +67,7 @@ func (d *Describe) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return d.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index 3cf71c23de..0f1891bb97 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/health" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -123,14 +124,14 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, if !ok { return nil, fmt.Errorf("expecting a meta table but got %T", oo[0]) } - rows := make(render.Rows, len(table.Rows)) - re, _ := meta.Renderer.(Generic) + rows := make(model1.Rows, len(table.Rows)) + re, _ := meta.Renderer.(model1.Generic) re.SetTable(ns, table) for i, row := range table.Rows { if err := re.Render(row, ns, &rows[i]); err != nil { return nil, err } - if !render.Happy(ns, re.Header(ns), rows[i]) { + if !model1.IsValid(ns, re.Header(ns), rows[i]) { c.Inc(health.S2) continue } @@ -140,12 +141,12 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, return c, nil } c.Total(int64(len(oo))) - rr, re := make(render.Rows, len(oo)), meta.Renderer + rr, re := make(model1.Rows, len(oo)), meta.Renderer for i, o := range oo { if err := re.Render(o, ns, &rr[i]); err != nil { return nil, err } - if !render.Happy(ns, re.Header(ns), rr[i]) { + if !model1.IsValid(ns, re.Header(ns), rr[i]) { c.Inc(health.S2) continue } diff --git a/internal/model/registry.go b/internal/model/registry.go index 55db15f832..4ad382da5a 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -82,7 +82,7 @@ var Registry = map[string]ResourceMeta{ DAO: &dao.Alias{}, Renderer: &render.Alias{}, }, - // !!BOZO!! + // !!BOZO!! Popeye //"popeye": { // DAO: &dao.Popeye{}, // Renderer: &render.Popeye{}, @@ -102,8 +102,17 @@ var Registry = map[string]ResourceMeta{ TreeRenderer: &xray.Pod{}, }, "v1/namespaces": { + DAO: &dao.Namespace{}, Renderer: &render.Namespace{}, }, + "v1/secrets": { + DAO: &dao.Secret{}, + Renderer: &render.Secret{}, + }, + "v1/configmaps": { + DAO: &dao.ConfigMap{}, + Renderer: &render.ConfigMap{}, + }, "v1/nodes": { DAO: &dao.Node{}, Renderer: &render.Node{}, @@ -113,6 +122,10 @@ var Registry = map[string]ResourceMeta{ Renderer: &render.Service{}, TreeRenderer: &xray.Service{}, }, + "v1/events": { + DAO: &dao.Table{}, + Renderer: &render.Event{}, + }, "v1/serviceaccounts": { Renderer: &render.ServiceAccount{}, }, @@ -122,14 +135,6 @@ var Registry = map[string]ResourceMeta{ "v1/persistentvolumeclaims": { Renderer: &render.PersistentVolumeClaim{}, }, - "v1/events": { - DAO: &dao.Table{}, - Renderer: &render.Event{}, - }, - "v1/secrets": { - DAO: &dao.Secret{}, - Renderer: &render.Generic{}, - }, // Apps... "apps/v1/deployments": { @@ -169,6 +174,7 @@ var Registry = map[string]ResourceMeta{ // CRDs... "apiextensions.k8s.io/v1/customresourcedefinitions": { + DAO: &dao.CustomResourceDefinition{}, Renderer: &render.CustomResourceDefinition{}, }, diff --git a/internal/model/rev_values.go b/internal/model/rev_values.go index 08badab6d5..a0b8cb98c5 100644 --- a/internal/model/rev_values.go +++ b/internal/model/rev_values.go @@ -10,6 +10,7 @@ import ( "time" backoff "github.com/cenkalti/backoff/v4" + "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/rs/zerolog/log" @@ -84,7 +85,7 @@ func (v *RevValues) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return v.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) diff --git a/internal/model/stack.go b/internal/model/stack.go index b660a1ab1b..55221b8560 100644 --- a/internal/model/stack.go +++ b/internal/model/stack.go @@ -112,6 +112,7 @@ func (s *Stack) Pop() (Component, bool) { s.mx.Lock() { c = s.components[len(s.components)-1] + c.Stop() s.components = s.components[:len(s.components)-1] } s.mx.Unlock() diff --git a/internal/model/table.go b/internal/model/table.go index 6259382799..ce848724ab 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -14,7 +14,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -25,7 +25,7 @@ const initRefreshRate = 300 * time.Millisecond // TableListener represents a table model listener. type TableListener interface { // TableDataChanged notifies the model data changed. - TableDataChanged(*render.TableData) + TableDataChanged(*model1.TableData) // TableLoadFailed notifies the load failed. TableLoadFailed(error) @@ -34,21 +34,20 @@ type TableListener interface { // Table represents a table model. type Table struct { gvr client.GVR - namespace string - data *render.TableData + data *model1.TableData listeners []TableListener inUpdate int32 refreshRate time.Duration instance string - mx sync.RWMutex labelFilter string + mx sync.RWMutex } // NewTable returns a new table model. func NewTable(gvr client.GVR) *Table { return &Table{ gvr: gvr, - data: render.NewTableData(), + data: model1.NewTableData(gvr), refreshRate: 2 * time.Second, } } @@ -141,18 +140,17 @@ func (t *Table) Delete(ctx context.Context, path string, propagation *metav1.Del // GetNamespace returns the model namespace. func (t *Table) GetNamespace() string { - return t.namespace + return t.data.GetNamespace() } // SetNamespace sets up model namespace. func (t *Table) SetNamespace(ns string) { - t.namespace = ns - t.data.Clear() + t.data.Reset(ns) } // InNamespace checks if current namespace matches desired namespace. func (t *Table) InNamespace(ns string) bool { - return len(t.data.RowEvents) > 0 && t.namespace == ns + return t.data.GetNamespace() == ns && !t.data.Empty() } // SetRefreshRate sets model refresh duration. @@ -162,7 +160,7 @@ func (t *Table) SetRefreshRate(d time.Duration) { // ClusterWide checks if resource is scope for all namespaces. func (t *Table) ClusterWide() bool { - return client.IsClusterWide(t.namespace) + return client.IsClusterWide(t.data.GetNamespace()) } // Empty returns true if no model data. @@ -170,13 +168,13 @@ func (t *Table) Empty() bool { return t.data.Empty() } -// Count returns the row count. -func (t *Table) Count() int { - return t.data.Count() +// RowCount returns the row count. +func (t *Table) RowCount() int { + return t.data.RowCount() } // Peek returns model data. -func (t *Table) Peek() *render.TableData { +func (t *Table) Peek() *model1.TableData { t.mx.RLock() defer t.mx.RUnlock() @@ -184,8 +182,6 @@ func (t *Table) Peek() *render.TableData { } func (t *Table) updater(ctx context.Context) { - defer log.Debug().Msgf("TABLE-UPDATER canceled -- %q", t.gvr) - bf := backoff.NewExponentialBackOff() bf.InitialInterval, bf.MaxElapsedTime = initRefreshRate, maxReaderRetryInterval rate := initRefreshRate @@ -199,7 +195,7 @@ func (t *Table) updater(ctx context.Context) { return t.refresh(ctx) }, backoff.WithContext(bf, ctx)) if err != nil { - log.Error().Err(err).Msgf("Retry failed") + log.Warn().Err(err).Msgf("reconciler exited") t.fireTableLoadFailed(err) return } @@ -229,24 +225,25 @@ func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, err } a.Init(factory, t.gvr) - ns := client.CleanseNamespace(t.namespace) - if client.IsClusterScoped(t.namespace) { + t.mx.RLock() + ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) + t.mx.RUnlock() + + ns := client.CleanseNamespace(t.data.GetNamespace()) + if client.IsClusterScoped(ns) { ns = client.BlankNamespace } - ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) return a.List(ctx, ns) } func (t *Table) reconcile(ctx context.Context) error { - t.mx.Lock() - defer t.mx.Unlock() - meta := resourceMeta(t.gvr) - ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) var ( oo []runtime.Object err error ) + meta := resourceMeta(t.gvr) + ctx = context.WithValue(ctx, internal.KeyLabels, t.labelFilter) if t.instance == "" { oo, err = t.list(ctx, meta.DAO) } else { @@ -257,41 +254,10 @@ func (t *Table) reconcile(ctx context.Context) error { return err } - var rows render.Rows - if len(oo) > 0 { - if meta.Renderer.IsGeneric() { - table, ok := oo[0].(*metav1.Table) - if !ok { - return fmt.Errorf("expecting a meta table but got %T", oo[0]) - } - rows = make(render.Rows, len(table.Rows)) - if err := genericHydrate(t.namespace, table, rows, meta.Renderer); err != nil { - return err - } - } else { - rows = make(render.Rows, len(oo)) - if err := hydrate(t.namespace, oo, rows, meta.Renderer); err != nil { - return err - } - } - } - - // if labelSelector in place might as well clear the model data. - sel, ok := ctx.Value(internal.KeyLabels).(string) - if ok && sel != "" { - t.data.Clear() - } - t.data.Update(rows) - t.data.SetHeader(t.namespace, meta.Renderer.Header(t.namespace)) - - if len(t.data.Header) == 0 { - return fmt.Errorf("fail to list resource %s", t.gvr) - } - - return nil + return t.data.Reconcile(ctx, meta.Renderer, oo) } -func (t *Table) fireTableChanged(data *render.TableData) { +func (t *Table) fireTableChanged(data *model1.TableData) { var ll []TableListener t.mx.RLock() ll = t.listeners @@ -312,43 +278,3 @@ func (t *Table) fireTableLoadFailed(err error) { l.TableLoadFailed(err) } } - -// ---------------------------------------------------------------------------- -// Helpers... - -func hydrate(ns string, oo []runtime.Object, rr render.Rows, re Renderer) error { - for i, o := range oo { - if err := re.Render(o, ns, &rr[i]); err != nil { - return err - } - } - - return nil -} - -// Generic represents a generic resource. -type Generic interface { - // SetTable sets up the resource tabular definition. - SetTable(ns string, table *metav1.Table) - - // Header returns a resource header. - Header(ns string) render.Header - - // Render renders the resource. - Render(o interface{}, ns string, row *render.Row) error -} - -func genericHydrate(ns string, table *metav1.Table, rr render.Rows, re Renderer) error { - gr, ok := re.(Generic) - if !ok { - return fmt.Errorf("expecting generic renderer but got %T", re) - } - gr.SetTable(ns, table) - for i, row := range table.Rows { - if err := gr.Render(row, ns, &rr[i]); err != nil { - return err - } - } - - return nil -} diff --git a/internal/model/table_int_test.go b/internal/model/table_int_test.go index 522cd18c4c..ea7c03904e 100644 --- a/internal/model/table_int_test.go +++ b/internal/model/table_int_test.go @@ -13,11 +13,11 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/watch" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/informers" @@ -35,9 +35,9 @@ func TestTableReconcile(t *testing.T) { err := ta.reconcile(ctx) assert.Nil(t, err) data := ta.Peek() - assert.Equal(t, 23, len(data.Header)) - assert.Equal(t, 1, len(data.RowEvents)) - assert.Equal(t, client.NamespaceAll, data.Namespace) + assert.Equal(t, 23, data.HeaderCount()) + assert.Equal(t, 1, data.RowCount()) + assert.Equal(t, client.NamespaceAll, data.GetNamespace()) } func TestTableList(t *testing.T) { @@ -66,12 +66,10 @@ func TestTableGet(t *testing.T) { } func TestTableMeta(t *testing.T) { - pd := dao.Pod{} - pd.Init(makeFactory(), client.NewGVR("v1/pods")) uu := map[string]struct { gvr string accessor dao.Accessor - renderer Renderer + renderer model1.Renderer }{ "generic": { gvr: "containers", @@ -84,9 +82,9 @@ func TestTableMeta(t *testing.T) { renderer: &render.Node{}, }, "table": { - gvr: "v1/configmaps", + gvr: "v1/events", accessor: &dao.Table{}, - renderer: &render.Generic{}, + renderer: &render.Event{}, }, } @@ -100,44 +98,6 @@ func TestTableMeta(t *testing.T) { } } -func TestTableHydrate(t *testing.T) { - oo := []runtime.Object{ - &render.PodWithMetrics{Raw: load(t, "p1")}, - } - rr := make([]render.Row, 1) - - assert.Nil(t, hydrate("blee", oo, rr, render.Pod{})) - assert.Equal(t, 1, len(rr)) - assert.Equal(t, 23, len(rr[0].Fields)) -} - -func TestTableGenericHydrate(t *testing.T) { - raw := raw(t, "p1") - tt := metav1beta1.Table{ - ColumnDefinitions: []metav1beta1.TableColumnDefinition{ - {Name: "c1"}, - {Name: "c2"}, - }, - Rows: []metav1beta1.TableRow{ - { - Cells: []interface{}{"fred", 10}, - Object: runtime.RawExtension{Raw: raw}, - }, - { - Cells: []interface{}{"blee", 20}, - Object: runtime.RawExtension{Raw: raw}, - }, - }, - } - rr := make([]render.Row, 2) - re := render.Generic{} - re.SetTable("blee", &tt) - - assert.Nil(t, genericHydrate("blee", &tt, rr, &re)) - assert.Equal(t, 2, len(rr)) - assert.Equal(t, 3, len(rr[0].Fields)) -} - // ---------------------------------------------------------------------------- // Helpers... @@ -162,12 +122,6 @@ func load(t *testing.T, n string) *unstructured.Unstructured { return &o } -func raw(t *testing.T, n string) []byte { - raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n)) - assert.Nil(t, err) - return raw -} - // ---------------------------------------------------------------------------- func makeFactory() testFactory { diff --git a/internal/model/table_test.go b/internal/model/table_test.go index ec636b9ed9..5417101778 100644 --- a/internal/model/table_test.go +++ b/internal/model/table_test.go @@ -14,7 +14,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/watch" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -36,9 +36,9 @@ func TestTableRefresh(t *testing.T) { ctx = context.WithValue(ctx, internal.KeyWithMetrics, false) assert.NoError(t, ta.Refresh(ctx)) data := ta.Peek() - assert.Equal(t, 23, len(data.Header)) - assert.Equal(t, 1, len(data.RowEvents)) - assert.Equal(t, client.NamespaceAll, data.Namespace) + assert.Equal(t, 23, data.HeaderCount()) + assert.Equal(t, 1, data.RowCount()) + assert.Equal(t, client.NamespaceAll, data.GetNamespace()) assert.Equal(t, 1, l.count) assert.Equal(t, 0, l.errs) } @@ -75,7 +75,7 @@ type tableListener struct { count, errs int } -func (l *tableListener) TableDataChanged(*render.TableData) { +func (l *tableListener) TableDataChanged(*model1.TableData) { l.count++ } diff --git a/internal/model/text.go b/internal/model/text.go index 64e4d4f90a..b50c8680af 100644 --- a/internal/model/text.go +++ b/internal/model/text.go @@ -6,7 +6,7 @@ package model import ( "strings" - "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal" "github.com/sahilm/fuzzy" ) @@ -111,7 +111,7 @@ func (t *Text) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return t.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) diff --git a/internal/model/types.go b/internal/model/types.go index b8ec0b6c84..e729b32204 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -9,7 +9,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" "github.com/sahilm/fuzzy" "k8s.io/apimachinery/pkg/runtime" @@ -100,21 +100,6 @@ type Filterer interface { SetLabelFilter(map[string]string) } -// Renderer represents a resource renderer. -type Renderer interface { - // IsGeneric identifies a generic handler. - IsGeneric() bool - - // Render converts raw resources to tabular data. - Render(o interface{}, ns string, row *render.Row) error - - // Header returns the resource header. - Header(ns string) render.Header - - // ColorerFunc returns a row colorer function. - ColorerFunc() render.ColorerFunc -} - // Cruder performs crud operations. type Cruder interface { // List returns a collection of resources. @@ -149,6 +134,6 @@ type TreeRenderer interface { // ResourceMeta represents model info about a resource. type ResourceMeta struct { DAO dao.Accessor - Renderer Renderer + Renderer model1.Renderer TreeRenderer TreeRenderer } diff --git a/internal/model/values.go b/internal/model/values.go index 25870d5a3b..eafe098d30 100644 --- a/internal/model/values.go +++ b/internal/model/values.go @@ -11,6 +11,7 @@ import ( "time" backoff "github.com/cenkalti/backoff/v4" + "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/rs/zerolog/log" @@ -113,7 +114,7 @@ func (v *Values) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return v.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) diff --git a/internal/model/yaml.go b/internal/model/yaml.go index 7e7dd1a243..e476e27288 100644 --- a/internal/model/yaml.go +++ b/internal/model/yaml.go @@ -74,7 +74,7 @@ func (y *YAML) filter(q string, lines []string) fuzzy.Matches { if q == "" { return nil } - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return y.fuzzyFilter(strings.TrimSpace(f), lines) } return rxFilter(q, lines) diff --git a/internal/render/color.go b/internal/model1/color.go similarity index 74% rename from internal/render/color.go rename to internal/model1/color.go index 816c1e39f7..f570a0409e 100644 --- a/internal/render/color.go +++ b/internal/model1/color.go @@ -1,11 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render +package model1 -import ( - "github.com/derailed/tcell/v2" -) +import "github.com/derailed/tcell/v2" var ( // ModColor row modified color. @@ -33,12 +31,9 @@ var ( CompletedColor tcell.Color ) -// ColorerFunc represents a resource row colorer. -type ColorerFunc func(ns string, h Header, re RowEvent) tcell.Color - // DefaultColorer set the default table row colors. -func DefaultColorer(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { +func DefaultColorer(ns string, h Header, re *RowEvent) tcell.Color { + if !IsValid(ns, h, re.Row) { return ErrColor } diff --git a/internal/model1/color_test.go b/internal/model1/color_test.go new file mode 100644 index 0000000000..985bab95a9 --- /dev/null +++ b/internal/model1/color_test.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/model1" + "github.com/derailed/tcell/v2" + "github.com/stretchr/testify/assert" +) + +func TestDefaultColorer(t *testing.T) { + uu := map[string]struct { + re model1.RowEvent + e tcell.Color + }{ + "add": { + model1.RowEvent{ + Kind: model1.EventAdd, + }, + model1.AddColor, + }, + "update": { + model1.RowEvent{ + Kind: model1.EventUpdate, + }, + model1.ModColor, + }, + "delete": { + model1.RowEvent{ + Kind: model1.EventDelete, + }, + model1.KillColor, + }, + "no-change": { + model1.RowEvent{ + Kind: model1.EventUnchanged, + }, + model1.StdColor, + }, + "invalid": { + model1.RowEvent{ + Kind: model1.EventUnchanged, + Row: model1.Row{ + Fields: model1.Fields{"", "", "blah"}, + }, + }, + model1.ErrColor, + }, + } + + h := model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "VALID"}, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, model1.DefaultColorer("", h, &u.re)) + }) + } +} diff --git a/internal/render/delta.go b/internal/model1/delta.go similarity index 97% rename from internal/render/delta.go rename to internal/model1/delta.go index bdc95aa1c0..3d3e32dc84 100644 --- a/internal/render/delta.go +++ b/internal/model1/delta.go @@ -1,11 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render +package model1 -import ( - "reflect" -) +import "reflect" // DeltaRow represents a collection of row deltas between old and new row. type DeltaRow []string diff --git a/internal/model1/delta_test.go b/internal/model1/delta_test.go new file mode 100644 index 0000000000..a809660c4c --- /dev/null +++ b/internal/model1/delta_test.go @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/model1" + "github.com/stretchr/testify/assert" +) + +func TestDeltaLabelize(t *testing.T) { + uu := map[string]struct { + o model1.Row + n model1.Row + e model1.DeltaRow + }{ + "same": { + o: model1.Row{ + Fields: model1.Fields{"a", "b", "blee=fred,doh=zorg"}, + }, + n: model1.Row{ + Fields: model1.Fields{"a", "b", "blee=fred1,doh=zorg"}, + }, + e: model1.DeltaRow{"", "", "fred", "zorg"}, + }, + } + + hh := model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, + } + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + d := model1.NewDeltaRow(u.o, u.n, hh) + d = d.Labelize([]int{0, 1}, 2) + assert.Equal(t, u.e, d) + }) + } +} + +func TestDeltaCustomize(t *testing.T) { + uu := map[string]struct { + r1, r2 model1.Row + cols []int + e model1.DeltaRow + }{ + "same": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + cols: []int{0, 1, 2}, + e: model1.DeltaRow{"", "", ""}, + }, + "empty": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + e: model1.DeltaRow{}, + }, + "diff-full": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a1", "b1", "c1"}, + }, + cols: []int{0, 1, 2}, + e: model1.DeltaRow{"a", "b", "c"}, + }, + "diff-reverse": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a1", "b1", "c1"}, + }, + cols: []int{2, 1, 0}, + e: model1.DeltaRow{"c", "b", "a"}, + }, + "diff-skip": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a1", "b1", "c1"}, + }, + cols: []int{2, 0}, + e: model1.DeltaRow{"c", "a"}, + }, + "diff-missing": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a1", "b1", "c1"}, + }, + cols: []int{2, 10, 0}, + e: model1.DeltaRow{"c", "", "a"}, + }, + "diff-negative": { + r1: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + r2: model1.Row{ + Fields: model1.Fields{"a1", "b1", "c1"}, + }, + cols: []int{2, -1, 0}, + e: model1.DeltaRow{"c", "", "a"}, + }, + } + + hh := model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, + } + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + d := model1.NewDeltaRow(u.r1, u.r2, hh) + out := make(model1.DeltaRow, len(u.cols)) + d.Customize(u.cols, out) + assert.Equal(t, u.e, out) + }) + } +} + +func TestDeltaNew(t *testing.T) { + uu := map[string]struct { + o model1.Row + n model1.Row + blank bool + e model1.DeltaRow + }{ + "same": { + o: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + n: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + blank: true, + e: model1.DeltaRow{"", "", ""}, + }, + "diff": { + o: model1.Row{ + Fields: model1.Fields{"a1", "b", "c"}, + }, + n: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + e: model1.DeltaRow{"a1", "", ""}, + }, + "diff2": { + o: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + n: model1.Row{ + Fields: model1.Fields{"a", "b1", "c"}, + }, + e: model1.DeltaRow{"", "b", ""}, + }, + "diffLast": { + o: model1.Row{ + Fields: model1.Fields{"a", "b", "c"}, + }, + n: model1.Row{ + Fields: model1.Fields{"a", "b", "c1"}, + }, + e: model1.DeltaRow{"", "", "c"}, + }, + } + + hh := model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, + } + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + d := model1.NewDeltaRow(u.o, u.n, hh) + assert.Equal(t, u.e, d) + assert.Equal(t, u.blank, d.IsBlank()) + }) + } +} + +func TestDeltaBlank(t *testing.T) { + uu := map[string]struct { + r model1.DeltaRow + e bool + }{ + "empty": { + r: model1.DeltaRow{}, + e: true, + }, + "blank": { + r: model1.DeltaRow{"", "", ""}, + e: true, + }, + "notblank": { + r: model1.DeltaRow{"", "", "z"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.r.IsBlank()) + }) + } +} + +func TestDeltaDiff(t *testing.T) { + uu := map[string]struct { + d1, d2 model1.DeltaRow + ageCol int + e bool + }{ + "empty": { + d1: model1.DeltaRow{"f1", "f2", "f3"}, + ageCol: 2, + e: true, + }, + "same": { + d1: model1.DeltaRow{"f1", "f2", "f3"}, + d2: model1.DeltaRow{"f1", "f2", "f3"}, + ageCol: -1, + }, + "diff": { + d1: model1.DeltaRow{"f1", "f2", "f3"}, + d2: model1.DeltaRow{"f1", "f2", "f13"}, + ageCol: -1, + e: true, + }, + "diff-age-first": { + d1: model1.DeltaRow{"f1", "f2", "f3"}, + d2: model1.DeltaRow{"f1", "f2", "f13"}, + ageCol: 0, + e: true, + }, + "diff-age-last": { + d1: model1.DeltaRow{"f1", "f2", "f3"}, + d2: model1.DeltaRow{"f1", "f2", "f13"}, + ageCol: 2, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.d1.Diff(u.d2, u.ageCol)) + }) + } +} diff --git a/internal/model1/fields.go b/internal/model1/fields.go new file mode 100644 index 0000000000..9242d54f3b --- /dev/null +++ b/internal/model1/fields.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import "reflect" + +// Fields represents a collection of row fields. +type Fields []string + +// Customize returns a subset of fields. +func (f Fields) Customize(cols []int, out Fields) { + for i, c := range cols { + if c < 0 { + out[i] = NAValue + continue + } + if c < len(f) { + out[i] = f[c] + } + } +} + +// Diff returns true if fields differ or false otherwise. +func (f Fields) Diff(ff Fields, ageCol int) bool { + if ageCol < 0 { + return !reflect.DeepEqual(f[:len(f)-1], ff[:len(ff)-1]) + } + if !reflect.DeepEqual(f[:ageCol], ff[:ageCol]) { + return true + } + return !reflect.DeepEqual(f[ageCol+1:], ff[ageCol+1:]) +} + +// Clone returns a copy of the fields. +func (f Fields) Clone() Fields { + cp := make(Fields, len(f)) + copy(cp, f) + + return cp +} diff --git a/internal/render/header.go b/internal/model1/header.go similarity index 82% rename from internal/render/header.go rename to internal/model1/header.go index 5ac9bcd2fb..798f1d92ce 100644 --- a/internal/render/header.go +++ b/internal/model1/header.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render +package model1 import ( "reflect" @@ -34,18 +34,24 @@ func (h HeaderColumn) Clone() HeaderColumn { // Header represents a table header. type Header []HeaderColumn +func (h Header) Clear() Header { + h = h[:0] + + return h +} + // Clone duplicates a header. func (h Header) Clone() Header { - header := make(Header, len(h)) - for i, c := range h { - header[i] = c.Clone() + he := make(Header, 0, len(h)) + for _, h := range h { + he = append(he, h.Clone()) } - return header + return he } // Labelize returns a new Header based on labels. -func (h Header) Labelize(cols []int, labelCol int, rr RowEvents) Header { +func (h Header) Labelize(cols []int, labelCol int, rr *RowEvents) Header { header := make(Header, 0, len(cols)+1) for _, c := range cols { header = append(header, h[c]) @@ -63,8 +69,8 @@ func (h Header) MapIndices(cols []string, wide bool) []int { ii := make([]int, 0, len(cols)) cc := make(map[int]struct{}, len(cols)) for _, col := range cols { - idx := h.IndexOf(col, true) - if idx < 0 { + idx, ok := h.IndexOf(col, true) + if !ok { log.Warn().Msgf("Column %q not found on resource", col) } ii, cc[idx] = append(ii, idx), struct{}{} @@ -90,13 +96,10 @@ func (h Header) Customize(cols []string, wide bool) Header { cc := make(Header, 0, len(h)) xx := make(map[int]struct{}, len(h)) for _, c := range cols { - idx := h.IndexOf(c, true) - if idx == -1 { + idx, ok := h.IndexOf(c, true) + if !ok { log.Warn().Msgf("Column %s is not available on this resource", c) - col := HeaderColumn{ - Name: c, - } - cc = append(cc, col) + cc = append(cc, HeaderColumn{Name: c}) continue } xx[idx] = struct{}{} @@ -129,8 +132,8 @@ func (h Header) Diff(header Header) bool { return !reflect.DeepEqual(h, header) } -// Columns return header as a collection of strings. -func (h Header) Columns(wide bool) []string { +// ColumnNames return header col names +func (h Header) ColumnNames(wide bool) []string { if len(h) == 0 { return nil } @@ -147,7 +150,9 @@ func (h Header) Columns(wide bool) []string { // HasAge returns true if table has an age column. func (h Header) HasAge() bool { - return h.IndexOf(ageCol, true) != -1 + _, ok := h.IndexOf(ageCol, true) + + return ok } // IsMetricsCol checks if given column index represents metrics. @@ -177,22 +182,17 @@ func (h Header) IsCapacityCol(col int) bool { return h[col].Capacity } -// ValidColIndex returns the valid col index or -1 if none. -func (h Header) ValidColIndex() int { - return h.IndexOf("VALID", true) -} - // IndexOf returns the col index or -1 if none. -func (h Header) IndexOf(colName string, includeWide bool) int { +func (h Header) IndexOf(colName string, includeWide bool) (int, bool) { for i, c := range h { if c.Wide && !includeWide { continue } if c.Name == colName { - return i + return i, true } } - return -1 + return -1, false } // Dump for debugging. diff --git a/internal/render/header_test.go b/internal/model1/header_test.go similarity index 53% rename from internal/render/header_test.go rename to internal/model1/header_test.go index 8c82a141dc..3d1d62f321 100644 --- a/internal/render/header_test.go +++ b/internal/model1/header_test.go @@ -1,18 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render_test +package model1_test import ( "testing" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/stretchr/testify/assert" ) func TestHeaderMapIndices(t *testing.T) { uu := map[string]struct { - h1 render.Header + h1 model1.Header cols []string wide bool e []int @@ -50,15 +50,16 @@ func TestHeaderMapIndices(t *testing.T) { func TestHeaderIndexOf(t *testing.T) { uu := map[string]struct { - h render.Header - name string - wide bool - e int + h model1.Header + name string + wide, ok bool + e int }{ "shown": { h: makeHeader(), name: "A", e: 0, + ok: true, }, "hidden": { h: makeHeader(), @@ -70,23 +71,26 @@ func TestHeaderIndexOf(t *testing.T) { name: "B", wide: true, e: 1, + ok: true, }, } for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.h.IndexOf(u.name, u.wide)) + idx, ok := u.h.IndexOf(u.name, u.wide) + assert.Equal(t, u.ok, ok) + assert.Equal(t, u.e, idx) }) } } func TestHeaderCustomize(t *testing.T) { uu := map[string]struct { - h render.Header + h model1.Header cols []string wide bool - e render.Header + e model1.Header }{ "default": { h: makeHeader(), @@ -98,58 +102,58 @@ func TestHeaderCustomize(t *testing.T) { e: makeHeader(), }, "reverse": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, cols: []string{"C", "A"}, - e: render.Header{ - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "A"}, + e: model1.Header{ + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "A"}, }, }, "reverse-wide": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, cols: []string{"C", "A"}, wide: true, - e: render.Header{ - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, + e: model1.Header{ + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, }, }, "toggle-wide": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, cols: []string{"C", "B"}, wide: true, - e: render.Header{ - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "B", Wide: false}, - render.HeaderColumn{Name: "A", Wide: true}, + e: model1.Header{ + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "B", Wide: false}, + model1.HeaderColumn{Name: "A", Wide: true}, }, }, "missing": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, cols: []string{"BLEE", "A"}, wide: true, - e: render.Header{ - render.HeaderColumn{Name: "BLEE"}, - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C", Wide: true}, + e: model1.Header{ + model1.HeaderColumn{Name: "BLEE"}, + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C", Wide: true}, }, }, } @@ -164,7 +168,7 @@ func TestHeaderCustomize(t *testing.T) { func TestHeaderDiff(t *testing.T) { uu := map[string]struct { - h1, h2 render.Header + h1, h2 model1.Header e bool }{ "same": { @@ -177,37 +181,37 @@ func TestHeaderDiff(t *testing.T) { e: true, }, "differ-wide": { - h1: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h1: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, - h2: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + h2: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, e: true, }, "differ-order": { - h1: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, + h1: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, }, - h2: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "B", Wide: true}, + h2: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "B", Wide: true}, }, e: true, }, "differ-name": { - h1: render.Header{ - render.HeaderColumn{Name: "A"}, + h1: model1.Header{ + model1.HeaderColumn{Name: "A"}, }, - h2: render.Header{ - render.HeaderColumn{Name: "B"}, + h2: model1.Header{ + model1.HeaderColumn{Name: "B"}, }, e: true, }, @@ -223,17 +227,17 @@ func TestHeaderDiff(t *testing.T) { func TestHeaderHasAge(t *testing.T) { uu := map[string]struct { - h render.Header + h model1.Header age, e bool }{ "no-age": { - h: render.Header{}, + h: model1.Header{}, }, "age": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "AGE", Time: true}, + h: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, }, e: true, age: true, @@ -249,41 +253,14 @@ func TestHeaderHasAge(t *testing.T) { } } -func TestHeaderValidColIndex(t *testing.T) { - uu := map[string]struct { - h render.Header - e int - }{ - "none": { - h: render.Header{}, - e: -1, - }, - "valid": { - h: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "VALID", Wide: true}, - }, - e: 2, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.h.ValidColIndex()) - }) - } -} - func TestHeaderColumns(t *testing.T) { uu := map[string]struct { - h render.Header + h model1.Header wide bool e []string }{ "empty": { - h: render.Header{}, + h: model1.Header{}, }, "regular": { h: makeHeader(), @@ -299,17 +276,17 @@ func TestHeaderColumns(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.h.Columns(u.wide)) + assert.Equal(t, u.e, u.h.ColumnNames(u.wide)) }) } } func TestHeaderClone(t *testing.T) { uu := map[string]struct { - h render.Header + h model1.Header }{ "empty": { - h: render.Header{}, + h: model1.Header{}, }, "full": { h: makeHeader(), @@ -332,10 +309,10 @@ func TestHeaderClone(t *testing.T) { // ---------------------------------------------------------------------------- // Helpers... -func makeHeader() render.Header { - return render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, +func makeHeader() model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B", Wide: true}, + model1.HeaderColumn{Name: "C"}, } } diff --git a/internal/model1/helpers.go b/internal/model1/helpers.go new file mode 100644 index 0000000000..fb0ac597a7 --- /dev/null +++ b/internal/model1/helpers.go @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import ( + "fmt" + "math" + "sort" + "strings" + + "github.com/fvbommel/sortorder" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func Hydrate(ns string, oo []runtime.Object, rr Rows, re Renderer) error { + for i, o := range oo { + if err := re.Render(o, ns, &rr[i]); err != nil { + return err + } + } + + return nil +} + +func GenericHydrate(ns string, table *metav1.Table, rr Rows, re Renderer) error { + gr, ok := re.(Generic) + if !ok { + return fmt.Errorf("expecting generic renderer but got %T", re) + } + gr.SetTable(ns, table) + for i, row := range table.Rows { + if err := gr.Render(row, ns, &rr[i]); err != nil { + return err + } + } + + return nil +} + +// IsValid returns true if resource is valid, false otherwise. +func IsValid(ns string, h Header, r Row) bool { + if len(r.Fields) == 0 { + return true + } + idx, ok := h.IndexOf("VALID", true) + if !ok || idx >= len(r.Fields) { + return true + } + + return strings.TrimSpace(r.Fields[idx]) == "" +} + +func sortLabels(m map[string]string) (keys, vals []string) { + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vals = append(vals, m[k]) + } + + return +} + +// Converts labels string to map. +func labelize(labels string) map[string]string { + ll := strings.Split(labels, ",") + data := make(map[string]string, len(ll)) + for _, l := range ll { + tokens := strings.Split(l, "=") + if len(tokens) == 2 { + data[tokens[0]] = tokens[1] + } + } + + return data +} + +func durationToSeconds(duration string) int64 { + if len(duration) == 0 { + return 0 + } + if duration == NAValue { + return math.MaxInt64 + } + + num := make([]rune, 0, 5) + var n, m int64 + for _, r := range duration { + switch r { + case 'y': + m = 365 * 24 * 60 * 60 + case 'd': + m = 24 * 60 * 60 + case 'h': + m = 60 * 60 + case 'm': + m = 60 + case 's': + m = 1 + default: + num = append(num, r) + continue + } + n, num = n+runesToNum(num)*m, num[:0] + } + + return n +} + +func runesToNum(rr []rune) int64 { + var r int64 + var m int64 = 1 + for i := len(rr) - 1; i >= 0; i-- { + v := int64(rr[i] - '0') + r += v * m + m *= 10 + } + + return r +} + +func capacityToNumber(capacity string) int64 { + quantity := resource.MustParse(capacity) + return quantity.Value() +} + +// Less return true if c1 <= c2. +func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool { + var less bool + switch { + case isNumber: + less = lessNumber(v1, v2) + case isDuration: + less = lessDuration(v1, v2) + case isCapacity: + less = lessCapacity(v1, v2) + default: + less = sortorder.NaturalLess(v1, v2) + } + if v1 == v2 { + return sortorder.NaturalLess(id1, id2) + } + + return less +} + +func lessDuration(s1, s2 string) bool { + d1, d2 := durationToSeconds(s1), durationToSeconds(s2) + return d1 <= d2 +} + +func lessCapacity(s1, s2 string) bool { + c1, c2 := capacityToNumber(s1), capacityToNumber(s2) + + return c1 <= c2 +} + +func lessNumber(s1, s2 string) bool { + v1, v2 := strings.Replace(s1, ",", "", -1), strings.Replace(s2, ",", "", -1) + + return sortorder.NaturalLess(v1, v2) +} diff --git a/internal/model1/helpers_test.go b/internal/model1/helpers_test.go new file mode 100644 index 0000000000..3e6a04272f --- /dev/null +++ b/internal/model1/helpers_test.go @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSortLabels(t *testing.T) { + uu := map[string]struct { + labels string + e [][]string + }{ + "simple": { + labels: "a=b,c=d", + e: [][]string{ + {"a", "c"}, + {"b", "d"}, + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + hh, vv := sortLabels(labelize(u.labels)) + assert.Equal(t, u.e[0], hh) + assert.Equal(t, u.e[1], vv) + }) + } +} + +func TestLabelize(t *testing.T) { + uu := map[string]struct { + labels string + e map[string]string + }{ + "simple": { + labels: "a=b,c=d", + e: map[string]string{"a": "b", "c": "d"}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, labelize(u.labels)) + }) + } +} + +func TestDurationToSecond(t *testing.T) { + uu := map[string]struct { + s string + e int64 + }{ + "seconds": {s: "22s", e: 22}, + "minutes": {s: "22m", e: 1320}, + "hours": {s: "12h", e: 43200}, + "days": {s: "3d", e: 259200}, + "day_hour": {s: "3d9h", e: 291600}, + "day_hour_minute": {s: "2d22h3m", e: 252180}, + "day_hour_minute_seconds": {s: "2d22h3m50s", e: 252230}, + "year": {s: "3y", e: 94608000}, + "year_day": {s: "1y2d", e: 31708800}, + "n/a": {s: NAValue, e: math.MaxInt64}, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, durationToSeconds(u.s)) + }) + } +} + +func BenchmarkDurationToSecond(b *testing.B) { + t := "2d22h3m50s" + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + durationToSeconds(t) + } +} diff --git a/internal/model1/row.go b/internal/model1/row.go new file mode 100644 index 0000000000..bda6a86065 --- /dev/null +++ b/internal/model1/row.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +// Row represents a collection of columns. +type Row struct { + ID string + Fields Fields +} + +// NewRow returns a new row with initialized fields. +func NewRow(size int) Row { + return Row{Fields: make([]string, size)} +} + +// Labelize returns a new row based on labels. +func (r Row) Labelize(cols []int, labelCol int, labels []string) Row { + out := NewRow(len(cols) + len(labels)) + for _, col := range cols { + out.Fields = append(out.Fields, r.Fields[col]) + } + m := labelize(r.Fields[labelCol]) + for _, label := range labels { + out.Fields = append(out.Fields, m[label]) + } + + return out +} + +// Customize returns a row subset based on given col indices. +func (r Row) Customize(cols []int) Row { + out := NewRow(len(cols)) + r.Fields.Customize(cols, out.Fields) + out.ID = r.ID + + return out +} + +// Diff returns true if row differ or false otherwise. +func (r Row) Diff(ro Row, ageCol int) bool { + if r.ID != ro.ID { + return true + } + return r.Fields.Diff(ro.Fields, ageCol) +} + +// Clone copies a row. +func (r Row) Clone() Row { + return Row{ + ID: r.ID, + Fields: r.Fields.Clone(), + } +} + +// Len returns the length of the row. +func (r Row) Len() int { + return len(r.Fields) +} + +// ---------------------------------------------------------------------------- + +// RowSorter sorts rows. +type RowSorter struct { + Rows Rows + Index int + IsNumber bool + IsDuration bool + IsCapacity bool + Asc bool +} + +func (s RowSorter) Len() int { + return len(s.Rows) +} + +func (s RowSorter) Swap(i, j int) { + s.Rows[i], s.Rows[j] = s.Rows[j], s.Rows[i] +} + +func (s RowSorter) Less(i, j int) bool { + v1, v2 := s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index] + id1, id2 := s.Rows[i].ID, s.Rows[j].ID + less := Less(s.IsNumber, s.IsDuration, s.IsCapacity, id1, id2, v1, v2) + if s.Asc { + return less + } + return !less +} + +// ---------------------------------------------------------------------------- +// Helpers... diff --git a/internal/render/row_event.go b/internal/model1/row_event.go similarity index 54% rename from internal/render/row_event.go rename to internal/model1/row_event.go index 7248371db0..628ddab057 100644 --- a/internal/render/row_event.go +++ b/internal/model1/row_event.go @@ -1,28 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render +package model1 import ( + "fmt" "sort" ) -const ( - // EventUnchanged notifies listener resource has not changed. - EventUnchanged ResEvent = 1 << iota - - // EventAdd notifies listener of a resource was added. - EventAdd - - // EventUpdate notifies listener of a resource updated. - EventUpdate - - // EventDelete notifies listener of a resource was deleted. - EventDelete - - // EventClear the stack was reset. - EventClear -) +type ReRangeFn func(int, RowEvent) bool // ResEvent represents a resource event. type ResEvent int @@ -103,13 +89,58 @@ func (r RowEvent) Diff(re RowEvent, ageCol int) bool { // ---------------------------------------------------------------------------- +type reIndex map[string]int + // RowEvents a collection of row events. -type RowEvents []RowEvent +type RowEvents struct { + events []RowEvent + index reIndex +} + +func NewRowEvents(size int) *RowEvents { + return &RowEvents{ + events: make([]RowEvent, 0, size), + index: make(reIndex, size), + } +} + +func NewRowEventsWithEvts(ee ...RowEvent) *RowEvents { + re := NewRowEvents(len(ee)) + for _, e := range ee { + re.Add(e) + } + + return re +} + +func (r *RowEvents) reindex() { + for i, e := range r.events { + r.index[e.Row.ID] = i + } +} + +func (r *RowEvents) At(i int) (RowEvent, bool) { + if i < 0 || i > len(r.events) { + return RowEvent{}, false + } + + return r.events[i], true +} + +func (r *RowEvents) Set(i int, re RowEvent) { + r.events[i] = re + r.index[re.Row.ID] = i +} + +func (r *RowEvents) Add(re RowEvent) { + r.events = append(r.events, re) + r.index[re.Row.ID] = len(r.events) - 1 +} // ExtractHeaderLabels extract header labels. -func (r RowEvents) ExtractHeaderLabels(labelCol int) []string { +func (r *RowEvents) ExtractHeaderLabels(labelCol int) []string { ll := make([]string, 0, 10) - for _, re := range r { + for _, re := range r.events { ll = append(ll, re.ExtractHeaderLabels(labelCol)...) } @@ -117,32 +148,32 @@ func (r RowEvents) ExtractHeaderLabels(labelCol int) []string { } // Labelize converts labels into a row event. -func (r RowEvents) Labelize(cols []int, labelCol int, labels []string) RowEvents { - out := make(RowEvents, 0, len(r)) - for _, re := range r { +func (r *RowEvents) Labelize(cols []int, labelCol int, labels []string) *RowEvents { + out := make([]RowEvent, 0, len(r.events)) + for _, re := range r.events { out = append(out, re.Labelize(cols, labelCol, labels)) } - return out + return NewRowEventsWithEvts(out...) } // Customize returns custom row events based on columns layout. -func (r RowEvents) Customize(cols []int) RowEvents { - ee := make(RowEvents, 0, len(cols)) - for _, re := range r { +func (r *RowEvents) Customize(cols []int) *RowEvents { + ee := make([]RowEvent, 0, len(cols)) + for _, re := range r.events { ee = append(ee, re.Customize(cols)) } - return ee + + return NewRowEventsWithEvts(ee...) } // Diff returns true if the event changed. -func (r RowEvents) Diff(re RowEvents, ageCol int) bool { - if len(r) != len(re) { +func (r *RowEvents) Diff(re *RowEvents, ageCol int) bool { + if len(r.events) != len(re.events) { return true } - - for i := range r { - if r[i].Diff(re[i], ageCol) { + for i := range r.events { + if r.events[i].Diff(re.events[i], ageCol) { return true } } @@ -150,53 +181,80 @@ func (r RowEvents) Diff(re RowEvents, ageCol int) bool { return false } -// Clone returns a rowevents deep copy. -func (r RowEvents) Clone() RowEvents { - res := make(RowEvents, len(r)) - for i, re := range r { - res[i] = re.Clone() +// Clone returns a deep copy. +func (r *RowEvents) Clone() *RowEvents { + re := make([]RowEvent, 0, len(r.events)) + for _, e := range r.events { + re = append(re, e.Clone()) } - return res + return NewRowEventsWithEvts(re...) } // Upsert add or update a row if it exists. -func (r RowEvents) Upsert(re RowEvent) RowEvents { +func (r *RowEvents) Upsert(re RowEvent) { if idx, ok := r.FindIndex(re.Row.ID); ok { - r[idx] = re + r.events[idx] = re } else { - r = append(r, re) + r.Add(re) } - return r } // Delete removes an element by id. -func (r RowEvents) Delete(id string) RowEvents { - victim, ok := r.FindIndex(id) +func (r *RowEvents) Delete(fqn string) error { + victim, ok := r.FindIndex(fqn) if !ok { - return r + return fmt.Errorf("unable to delete row with fqn: %q", fqn) } - return append(r[0:victim], r[victim+1:]...) + r.events = append(r.events[0:victim], r.events[victim+1:]...) + delete(r.index, fqn) + r.reindex() + + return nil +} + +func (r *RowEvents) Len() int { + return len(r.events) +} + +func (r *RowEvents) Empty() bool { + return len(r.events) == 0 } // Clear delete all row events. -func (r RowEvents) Clear() RowEvents { - return RowEvents{} +func (r *RowEvents) Clear() { + r.events = r.events[:0] + for k := range r.index { + delete(r.index, k) + } } -// FindIndex locates a row index by id. Returns false is not found. -func (r RowEvents) FindIndex(id string) (int, bool) { - for i, re := range r { - if re.Row.ID == id { - return i, true +func (r *RowEvents) Range(f ReRangeFn) { + for i, e := range r.events { + if !f(i, e) { + return } } +} + +func (r *RowEvents) Get(id string) (RowEvent, bool) { + i, ok := r.index[id] + if !ok { + return RowEvent{}, false + } + + return r.At(i) +} + +// FindIndex locates a row index by id. Returns false is not found. +func (r *RowEvents) FindIndex(id string) (int, bool) { + i, ok := r.index[id] - return 0, false + return i, ok } // Sort rows based on column index and order. -func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, asc bool) { +func (r *RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, asc bool) { if sortCol == -1 { return } @@ -211,13 +269,14 @@ func (r RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, IsCapacity: isCapacity, } sort.Sort(t) + r.reindex() } // ---------------------------------------------------------------------------- // RowEventSorter sorts row events by a given colon. type RowEventSorter struct { - Events RowEvents + Events *RowEvents Index int NS string IsNumber bool @@ -227,16 +286,16 @@ type RowEventSorter struct { } func (r RowEventSorter) Len() int { - return len(r.Events) + return len(r.Events.events) } func (r RowEventSorter) Swap(i, j int) { - r.Events[i], r.Events[j] = r.Events[j], r.Events[i] + r.Events.events[i], r.Events.events[j] = r.Events.events[j], r.Events.events[i] } func (r RowEventSorter) Less(i, j int) bool { - f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields - id1, id2 := r.Events[i].Row.ID, r.Events[j].Row.ID + f1, f2 := r.Events.events[i].Row.Fields, r.Events.events[j].Row.Fields + id1, id2 := r.Events.events[i].Row.ID, r.Events.events[j].Row.ID less := Less(r.IsNumber, r.IsDuration, r.IsCapacity, id1, id2, f1[r.Index], f2[r.Index]) if r.Asc { return less diff --git a/internal/model1/row_event_test.go b/internal/model1/row_event_test.go new file mode 100644 index 0000000000..9ca9322980 --- /dev/null +++ b/internal/model1/row_event_test.go @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1_test + +import ( + "testing" + "time" + + "github.com/derailed/k9s/internal/model1" + "github.com/stretchr/testify/assert" +) + +func TestRowEventCustomize(t *testing.T) { + uu := map[string]struct { + re1, e model1.RowEvent + cols []int + }{ + "empty": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{}}, + }, + }, + "full": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + cols: []int{0, 1, 2}, + }, + "deltas": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + Deltas: model1.DeltaRow{"a", "b", "c"}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + Deltas: model1.DeltaRow{"a", "b", "c"}, + }, + cols: []int{0, 1, 2}, + }, + "deltas-skip": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + Deltas: model1.DeltaRow{"a", "b", "c"}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"3", "1"}}, + Deltas: model1.DeltaRow{"c", "a"}, + }, + cols: []int{2, 0}, + }, + "reverse": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"3", "2", "1"}}, + }, + cols: []int{2, 1, 0}, + }, + "skip": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"3", "1"}}, + }, + cols: []int{2, 0}, + }, + "miss": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"3", "", "1"}}, + }, + cols: []int{2, 10, 0}, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.re1.Customize(u.cols)) + }) + } +} + +func TestRowEventDiff(t *testing.T) { + uu := map[string]struct { + re1, re2 model1.RowEvent + e bool + }{ + "same": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + re2: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + }, + "diff-kind": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + re2: model1.RowEvent{ + Kind: model1.EventDelete, + Row: model1.Row{ID: "B", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: true, + }, + "diff-delta": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + Deltas: model1.DeltaRow{"1", "2", "3"}, + }, + re2: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + Deltas: model1.DeltaRow{"10", "2", "3"}, + }, + e: true, + }, + "diff-id": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + re2: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "B", Fields: model1.Fields{"1", "2", "3"}}, + }, + e: true, + }, + "diff-field": { + re1: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}, + }, + re2: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ID: "A", Fields: model1.Fields{"10", "2", "3"}}, + }, + e: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.re1.Diff(u.re2, -1)) + }) + } +} + +func TestRowEventsDiff(t *testing.T) { + uu := map[string]struct { + re1, re2 *model1.RowEvents + ageCol int + e bool + }{ + "same": { + re1: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re2: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + ageCol: -1, + }, + "diff-len": { + re1: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re2: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + ageCol: -1, + e: true, + }, + "diff-id": { + re1: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re2: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "D", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + ageCol: -1, + e: true, + }, + "diff-order": { + re1: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re2: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + ageCol: -1, + e: true, + }, + "diff-withAge": { + re1: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re2: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "13"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + ageCol: 1, + e: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.re1.Diff(u.re2, u.ageCol)) + }) + } +} + +func TestRowEventsUpsert(t *testing.T) { + uu := map[string]struct { + ee, e *model1.RowEvents + re model1.RowEvent + }{ + "add": { + ee: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + re: model1.RowEvent{ + Row: model1.Row{ID: "D", Fields: model1.Fields{"f1", "f2", "f3"}}, + }, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "D", Fields: model1.Fields{"f1", "f2", "f3"}}}, + ), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.ee.Upsert(u.re) + assert.Equal(t, u.e, u.ee) + }) + } +} + +func TestRowEventsCustomize(t *testing.T) { + uu := map[string]struct { + re, e *model1.RowEvents + cols []int + }{ + "same": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + cols: []int{0, 1, 2}, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + }, + "reverse": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + cols: []int{2, 1, 0}, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"3", "2", "1"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"3", "2", "0"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"3", "2", "10"}}}, + ), + }, + "skip": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + cols: []int{1, 0}, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"2", "1"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"2", "0"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"2", "10"}}}, + ), + }, + "missing": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + cols: []int{1, 0, 4}, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"2", "1", ""}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"2", "0", ""}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"2", "10", ""}}}, + ), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.re.Customize(u.cols)) + }) + } +} + +func TestRowEventsDelete(t *testing.T) { + uu := map[string]struct { + re, e *model1.RowEvents + id string + }{ + "first": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + id: "A", + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + }, + "middle": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + id: "B", + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + }, + "last": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + id: "C", + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + ), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.NoError(t, u.re.Delete(u.id)) + assert.Equal(t, u.e, u.re) + }) + } +} + +func TestRowEventsSort(t *testing.T) { + uu := map[string]struct { + re, e *model1.RowEvents + col int + duration, num, asc bool + capacity bool + }{ + "age_time": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", testTime().String()}}}, + ), + col: 2, + asc: true, + duration: true, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", testTime().String()}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}}, + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}}, + ), + }, + "col0": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + col: 0, + asc: true, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "B", Fields: model1.Fields{"0", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "A", Fields: model1.Fields{"1", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "C", Fields: model1.Fields{"10", "2", "3"}}}, + ), + }, + "id_preserve": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "ns1/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/C", Fields: model1.Fields{"C", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/C", Fields: model1.Fields{"C", "2", "3"}}}, + ), + col: 1, + asc: true, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "ns1/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/C", Fields: model1.Fields{"C", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/C", Fields: model1.Fields{"C", "2", "3"}}}, + ), + }, + "capacity": { + re: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "ns1/B", Fields: model1.Fields{"B", "2", "3", "1Gi"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/A", Fields: model1.Fields{"A", "2", "3", "1.1G"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/C", Fields: model1.Fields{"C", "2", "3", "0.5Ti"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/B", Fields: model1.Fields{"B", "2", "3", "12e6"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/A", Fields: model1.Fields{"A", "2", "3", "1234"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/C", Fields: model1.Fields{"C", "2", "3", "0.1Ei"}}}, + ), + col: 3, + asc: true, + capacity: true, + e: model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "ns2/A", Fields: model1.Fields{"A", "2", "3", "1234"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/B", Fields: model1.Fields{"B", "2", "3", "12e6"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/B", Fields: model1.Fields{"B", "2", "3", "1Gi"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/A", Fields: model1.Fields{"A", "2", "3", "1.1G"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/C", Fields: model1.Fields{"C", "2", "3", "0.5Ti"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/C", Fields: model1.Fields{"C", "2", "3", "0.1Ei"}}}, + ), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.re.Sort("", u.col, u.duration, u.num, u.capacity, u.asc) + assert.Equal(t, u.e, u.re) + }) + } +} + +func TestRowEventsClone(t *testing.T) { + uu := map[string]struct { + r *model1.RowEvents + }{ + "empty": { + r: model1.NewRowEventsWithEvts(), + }, + "full": { + r: makeRowEvents(), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + c := u.r.Clone() + assert.Equal(t, u.r.Len(), c.Len()) + if !u.r.Empty() { + r, ok := u.r.At(0) + assert.True(t, ok) + r.Row.Fields[0] = "blee" + cr, ok := c.At(0) + assert.True(t, ok) + assert.Equal(t, "A", cr.Row.Fields[0]) + } + }) + } +} + +// Helpers... + +func makeRowEvents() *model1.RowEvents { + return model1.NewRowEventsWithEvts( + model1.RowEvent{Row: model1.Row{ID: "ns1/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns1/C", Fields: model1.Fields{"C", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/A", Fields: model1.Fields{"A", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/B", Fields: model1.Fields{"B", "2", "3"}}}, + model1.RowEvent{Row: model1.Row{ID: "ns2/C", Fields: model1.Fields{"C", "2", "3"}}}, + ) +} diff --git a/internal/render/row_test.go b/internal/model1/row_test.go similarity index 78% rename from internal/render/row_test.go rename to internal/model1/row_test.go index f1d5a5bc0a..d206083165 100644 --- a/internal/render/row_test.go +++ b/internal/model1/row_test.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s -package render_test +package model1_test import ( "fmt" @@ -9,12 +9,12 @@ import ( "testing" "time" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/stretchr/testify/assert" ) func BenchmarkRowCustomize(b *testing.B) { - row := render.Row{ID: "fred", Fields: render.Fields{"f1", "f2", "f3"}} + row := model1.Row{ID: "fred", Fields: model1.Fields{"f1", "f2", "f3"}} cols := []int{0, 1, 2} b.ReportAllocs() b.ResetTimer() @@ -25,36 +25,36 @@ func BenchmarkRowCustomize(b *testing.B) { func TestFieldCustomize(t *testing.T) { uu := map[string]struct { - fields render.Fields + fields model1.Fields cols []int - e render.Fields + e model1.Fields }{ "empty": { - fields: render.Fields{}, + fields: model1.Fields{}, cols: []int{0, 1, 2}, - e: render.Fields{"", "", ""}, + e: model1.Fields{"", "", ""}, }, "no-cols": { - fields: render.Fields{"f1", "f2", "f3"}, + fields: model1.Fields{"f1", "f2", "f3"}, cols: []int{}, - e: render.Fields{}, + e: model1.Fields{}, }, "reverse": { - fields: render.Fields{"f1", "f2", "f3"}, + fields: model1.Fields{"f1", "f2", "f3"}, cols: []int{1, 0}, - e: render.Fields{"f2", "f1"}, + e: model1.Fields{"f2", "f1"}, }, "missing": { - fields: render.Fields{"f1", "f2", "f3"}, + fields: model1.Fields{"f1", "f2", "f3"}, cols: []int{10, 0}, - e: render.Fields{"", "f1"}, + e: model1.Fields{"", "f1"}, }, } for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - ff := make(render.Fields, len(u.cols)) + ff := make(model1.Fields, len(u.cols)) u.fields.Customize(u.cols, ff) assert.Equal(t, u.e, ff) }) @@ -62,7 +62,7 @@ func TestFieldCustomize(t *testing.T) { } func TestFieldClone(t *testing.T) { - f := render.Fields{"a", "b", "c"} + f := model1.Fields{"a", "b", "c"} f1 := f.Clone() assert.True(t, reflect.DeepEqual(f, f1)) @@ -71,24 +71,24 @@ func TestFieldClone(t *testing.T) { func TestRowlabelize(t *testing.T) { uu := map[string]struct { - row render.Row + row model1.Row cols []int - e render.Row + e model1.Row }{ "empty": { - row: render.Row{}, + row: model1.Row{}, cols: []int{0, 1, 2}, - e: render.Row{ID: "", Fields: render.Fields{"", "", ""}}, + e: model1.Row{ID: "", Fields: model1.Fields{"", "", ""}}, }, "no-cols-no-data": { - row: render.Row{}, + row: model1.Row{}, cols: []int{}, - e: render.Row{ID: "", Fields: render.Fields{}}, + e: model1.Row{ID: "", Fields: model1.Fields{}}, }, "no-cols-data": { - row: render.Row{ID: "fred", Fields: render.Fields{"f1", "f2", "f3"}}, + row: model1.Row{ID: "fred", Fields: model1.Fields{"f1", "f2", "f3"}}, cols: []int{}, - e: render.Row{ID: "fred", Fields: render.Fields{}}, + e: model1.Row{ID: "fred", Fields: model1.Fields{}}, }, } @@ -103,24 +103,24 @@ func TestRowlabelize(t *testing.T) { func TestRowCustomize(t *testing.T) { uu := map[string]struct { - row render.Row + row model1.Row cols []int - e render.Row + e model1.Row }{ "empty": { - row: render.Row{}, + row: model1.Row{}, cols: []int{0, 1, 2}, - e: render.Row{ID: "", Fields: render.Fields{"", "", ""}}, + e: model1.Row{ID: "", Fields: model1.Fields{"", "", ""}}, }, "no-cols-no-data": { - row: render.Row{}, + row: model1.Row{}, cols: []int{}, - e: render.Row{ID: "", Fields: render.Fields{}}, + e: model1.Row{ID: "", Fields: model1.Fields{}}, }, "no-cols-data": { - row: render.Row{ID: "fred", Fields: render.Fields{"f1", "f2", "f3"}}, + row: model1.Row{ID: "fred", Fields: model1.Fields{"f1", "f2", "f3"}}, cols: []int{}, - e: render.Row{ID: "fred", Fields: render.Fields{}}, + e: model1.Row{ID: "fred", Fields: model1.Fields{}}, }, } @@ -135,49 +135,49 @@ func TestRowCustomize(t *testing.T) { func TestRowsDelete(t *testing.T) { uu := map[string]struct { - rows render.Rows + rows model1.Rows id string - e render.Rows + e model1.Rows }{ "first": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, id: "a", - e: render.Rows{ + e: model1.Rows{ {ID: "b", Fields: []string{"albert", "blee"}}, }, }, "last": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, id: "b", - e: render.Rows{ + e: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, }, }, "middle": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, {ID: "c", Fields: []string{"fred", "zorg"}}, }, id: "b", - e: render.Rows{ + e: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "c", Fields: []string{"fred", "zorg"}}, }, }, "missing": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, id: "zorg", - e: render.Rows{ + e: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, @@ -195,29 +195,29 @@ func TestRowsDelete(t *testing.T) { func TestRowsUpsert(t *testing.T) { uu := map[string]struct { - rows render.Rows - row render.Row - e render.Rows + rows model1.Rows + row model1.Row + e model1.Rows }{ "add": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, - row: render.Row{ID: "c", Fields: []string{"f1", "f2"}}, - e: render.Rows{ + row: model1.Row{ID: "c", Fields: []string{"f1", "f2"}}, + e: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, {ID: "c", Fields: []string{"f1", "f2"}}, }, }, "update": { - rows: render.Rows{ + rows: model1.Rows{ {ID: "a", Fields: []string{"blee", "duh"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, - row: render.Row{ID: "a", Fields: []string{"f1", "f2"}}, - e: render.Rows{ + row: model1.Row{ID: "a", Fields: []string{"f1", "f2"}}, + e: model1.Rows{ {ID: "a", Fields: []string{"f1", "f2"}}, {ID: "b", Fields: []string{"albert", "blee"}}, }, @@ -235,69 +235,69 @@ func TestRowsUpsert(t *testing.T) { func TestRowsSortText(t *testing.T) { uu := map[string]struct { - rows render.Rows + rows model1.Rows col int asc, num bool - e render.Rows + e model1.Rows }{ "plainAsc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"blee", "duh"}}, {Fields: []string{"albert", "blee"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"albert", "blee"}}, {Fields: []string{"blee", "duh"}}, }, }, "plainDesc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"blee", "duh"}}, {Fields: []string{"albert", "blee"}}, }, col: 0, asc: false, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"blee", "duh"}}, {Fields: []string{"albert", "blee"}}, }, }, "numericAsc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10", "duh"}}, {Fields: []string{"1", "blee"}}, }, col: 0, num: true, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"1", "blee"}}, {Fields: []string{"10", "duh"}}, }, }, "numericDesc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10", "duh"}}, {Fields: []string{"1", "blee"}}, }, col: 0, num: true, asc: false, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"10", "duh"}}, {Fields: []string{"1", "blee"}}, }, }, "composite": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"blee-duh", "duh"}}, {Fields: []string{"blee", "blee"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"blee", "blee"}}, {Fields: []string{"blee-duh", "duh"}}, }, @@ -315,54 +315,54 @@ func TestRowsSortText(t *testing.T) { func TestRowsSortDuration(t *testing.T) { uu := map[string]struct { - rows render.Rows + rows model1.Rows col int asc bool - e render.Rows + e model1.Rows }{ "fred": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"2m24s", "blee"}}, {Fields: []string{"2m12s", "duh"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"2m12s", "duh"}}, {Fields: []string{"2m24s", "blee"}}, }, }, "years": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{testTime().Add(-365 * 24 * time.Hour).String(), "blee"}}, {Fields: []string{testTime().String(), "duh"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{testTime().String(), "duh"}}, {Fields: []string{testTime().Add(-365 * 24 * time.Hour).String(), "blee"}}, }, }, "durationAsc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}}, {Fields: []string{testTime().String(), "blee"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{testTime().String(), "blee"}}, {Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}}, }, }, "durationDesc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}}, {Fields: []string{testTime().String(), "blee"}}, }, col: 0, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{testTime().Add(10 * time.Second).String(), "duh"}}, {Fields: []string{testTime().String(), "blee"}}, }, @@ -380,31 +380,31 @@ func TestRowsSortDuration(t *testing.T) { func TestRowsSortMetrics(t *testing.T) { uu := map[string]struct { - rows render.Rows + rows model1.Rows col int asc bool - e render.Rows + e model1.Rows }{ "metricAsc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10m", "duh"}}, {Fields: []string{"1m", "blee"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"1m", "blee"}}, {Fields: []string{"10m", "duh"}}, }, }, "metricDesc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, col: 1, asc: false, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, @@ -422,31 +422,31 @@ func TestRowsSortMetrics(t *testing.T) { func TestRowsSortCapacity(t *testing.T) { uu := map[string]struct { - rows render.Rows + rows model1.Rows col int asc bool - e render.Rows + e model1.Rows }{ "capacityAsc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10Gi", "duh"}}, {Fields: []string{"10G", "blee"}}, }, col: 0, asc: true, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"10G", "blee"}}, {Fields: []string{"10Gi", "duh"}}, }, }, "capacityDesc": { - rows: render.Rows{ + rows: model1.Rows{ {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, col: 1, asc: false, - e: render.Rows{ + e: model1.Rows{ {Fields: []string{"10000m", "1000Mi"}}, {Fields: []string{"1m", "50Mi"}}, }, @@ -514,7 +514,7 @@ func TestLess(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, render.Less(u.isNumber, u.isDuration, u.isCapacity, u.id1, u.id2, u.v1, u.v2)) + assert.Equal(t, u.e, model1.Less(u.isNumber, u.isDuration, u.isCapacity, u.id1, u.id2, u.v1, u.v2)) }) } } diff --git a/internal/model1/rows.go b/internal/model1/rows.go new file mode 100644 index 0000000000..4085cb9b08 --- /dev/null +++ b/internal/model1/rows.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import "sort" + +// Rows represents a collection of rows. +type Rows []Row + +// Delete removes an element by id. +func (rr Rows) Delete(id string) Rows { + idx, ok := rr.Find(id) + if !ok { + return rr + } + + if idx == 0 { + return rr[1:] + } + if idx+1 == len(rr) { + return rr[:len(rr)-1] + } + + return append(rr[:idx], rr[idx+1:]...) +} + +// Upsert adds a new item. +func (rr Rows) Upsert(r Row) Rows { + idx, ok := rr.Find(r.ID) + if !ok { + return append(rr, r) + } + rr[idx] = r + + return rr +} + +// Find locates a row by id. Returns false is not found. +func (rr Rows) Find(id string) (int, bool) { + for i, r := range rr { + if r.ID == id { + return i, true + } + } + + return 0, false +} + +// Sort rows based on column index and order. +func (rr Rows) Sort(col int, asc, isNum, isDur, isCapacity bool) { + t := RowSorter{ + Rows: rr, + Index: col, + IsNumber: isNum, + IsDuration: isDur, + IsCapacity: isCapacity, + Asc: asc, + } + sort.Sort(t) +} diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go new file mode 100644 index 0000000000..2b8cd096fe --- /dev/null +++ b/internal/model1/table_data.go @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import ( + "context" + "errors" + "fmt" + "regexp" + "strings" + "sync" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + "github.com/rs/zerolog/log" + "github.com/sahilm/fuzzy" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type ( + // SortFn represent a function that can sort columnar data. + SortFn func(rows Rows, sortCol SortColumn) + + // SortColumn represents a sortable column. + SortColumn struct { + Name string + ASC bool + } +) + +const spacer = " " + +type FilterOpts struct { + Toast bool + Filter string + Invert bool +} + +// TableData tracks a K8s resource for tabular display. +type TableData struct { + header Header + rowEvents *RowEvents + namespace string + gvr client.GVR + mx sync.RWMutex +} + +// NewTableData returns a new table. +func NewTableData(gvr client.GVR) *TableData { + return &TableData{ + gvr: gvr, + rowEvents: NewRowEvents(10), + } +} + +func NewTableDataFull(gvr client.GVR, ns string, h Header, re *RowEvents) *TableData { + t := NewTableDataWithRows(gvr, h, re) + t.namespace = ns + + return t +} + +func NewTableDataWithRows(gvr client.GVR, h Header, re *RowEvents) *TableData { + t := NewTableData(gvr) + t.header, t.rowEvents = h, re + + return t +} + +func NewTableDataFromTable(td *TableData) *TableData { + t := NewTableData(td.gvr) + t.header = td.header + t.rowEvents = td.rowEvents + t.namespace = td.namespace + + return t +} + +func (t *TableData) AddRow(re RowEvent) { + t.rowEvents.Add(re) +} + +func (t *TableData) SetRow(idx int, re RowEvent) { + t.rowEvents.Set(idx, re) +} + +func (t *TableData) FindRow(id string) (RowEvent, bool) { + return t.rowEvents.Get(id) +} + +func (t *TableData) RowAt(idx int) (RowEvent, bool) { + return t.rowEvents.At(idx) +} + +func (t *TableData) RowsRange(f ReRangeFn) { + t.rowEvents.Range(f) +} + +func (t *TableData) Sort(sc SortColumn) { + col, idx := t.HeadCol(sc.Name, false) + if idx < 0 { + return + } + t.rowEvents.Sort( + t.GetNamespace(), + idx, + col.Time, + col.MX, + col.Capacity, + sc.ASC, + ) +} + +func (t *TableData) Header() Header { + return t.header +} + +// HeaderCount returns the number of header cols. +func (t *TableData) HeaderCount() int { + t.mx.RLock() + defer t.mx.RUnlock() + + return len(t.header) +} + +func (t *TableData) HeadCol(n string, w bool) (HeaderColumn, int) { + idx, ok := t.header.IndexOf(n, w) + if !ok { + return HeaderColumn{}, -1 + } + + return t.header[idx], idx +} + +func (t *TableData) Filter(f FilterOpts) *TableData { + td := NewTableDataFromTable(t) + + if f.Toast { + td.rowEvents = t.filterToast() + } + if f.Filter == "" || internal.IsLabelSelector(f.Filter) { + return td + } + if f, ok := internal.IsFuzzySelector(f.Filter); ok { + td.rowEvents = t.fuzzyFilter(f) + return td + } + rr, err := t.rxFilter(f.Filter, internal.IsInverseSelector(f.Filter)) + if err == nil { + td.rowEvents = rr + } else { + log.Error().Err(err).Msg("rx filter failed") + } + + return td +} + +func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) { + if inverse { + q = q[1:] + } + rx, err := regexp.Compile(`(?i)(` + q + `)`) + if err != nil { + return nil, fmt.Errorf("invalid rx filter %q: %w", q, err) + } + + ageIndex, ok := t.header.IndexOf("AGE", true) + + rr := NewRowEvents(t.RowCount() / 2) + t.rowEvents.Range(func(_ int, re RowEvent) bool { + ff := re.Row.Fields + if ok && ageIndex+1 <= len(ff) { + ff = append(ff[0:ageIndex], ff[ageIndex+1:]...) + } + fields := strings.Join(ff, spacer) + if (inverse && !rx.MatchString(fields)) || + ((!inverse) && rx.MatchString(fields)) { + rr.Add(re) + } + return true + }) + + return rr, nil +} + +func (t *TableData) fuzzyFilter(q string) *RowEvents { + q = strings.TrimSpace(q) + ss := make([]string, 0, t.RowCount()/2) + t.rowEvents.Range(func(_ int, re RowEvent) bool { + ss = append(ss, re.Row.ID) + return true + }) + + mm := fuzzy.Find(q, ss) + rr := NewRowEvents(t.RowCount() / 2) + for _, m := range mm { + re, ok := t.rowEvents.At(m.Index) + if !ok { + log.Error().Msgf("unable to find event for index in fuzzfilter: %d", m.Index) + continue + } + rr.Add(re) + } + + return rr +} + +func (t *TableData) filterToast() *RowEvents { + idx, ok := t.header.IndexOf("VALID", true) + if !ok { + return nil + } + + rr := NewRowEvents(10) + t.rowEvents.Range(func(_ int, re RowEvent) bool { + if re.Row.Fields[idx] != "" { + rr.Add(re) + } + return true + }) + + return rr +} + +func (t *TableData) GetNamespace() string { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.namespace +} + +func (t *TableData) Reset(ns string) { + t.mx.Lock() + { + t.namespace = ns + } + t.mx.Unlock() + + t.Clear() +} + +func (t *TableData) Reconcile(ctx context.Context, r Renderer, oo []runtime.Object) error { + var rows Rows + + if len(oo) > 0 { + if r.IsGeneric() { + table, ok := oo[0].(*metav1.Table) + if !ok { + return fmt.Errorf("expecting a meta table but got %T", oo[0]) + } + rows = make(Rows, len(table.Rows)) + if err := GenericHydrate(t.namespace, table, rows, r); err != nil { + return err + } + } else { + rows = make(Rows, len(oo)) + if err := Hydrate(t.namespace, oo, rows, r); err != nil { + return err + } + } + } + + t.Update(rows) + t.SetHeader(t.namespace, r.Header(t.namespace)) + if t.HeaderCount() == 0 { + return fmt.Errorf("fail to list resource %s", t.gvr) + } + + return nil +} + +// Empty checks if there are no entries. +func (t *TableData) Empty() bool { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.rowEvents.Empty() +} + +func (t *TableData) SetRowEvents(re *RowEvents) { + t.rowEvents = re +} + +func (t *TableData) GetRowEvents() *RowEvents { + return t.rowEvents +} + +// RowCount returns the number of rows. +func (t *TableData) RowCount() int { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.rowEvents.Len() +} + +// IndexOfHeader return the index of the header. +func (t *TableData) IndexOfHeader(h string) (int, bool) { + return t.header.IndexOf(h, false) +} + +// Labelize prints out specific label columns. +func (t *TableData) Labelize(labels []string) *TableData { + idx, ok := t.header.IndexOf("LABELS", true) + if !ok { + return t + } + cols := []int{0, 1} + if client.IsNamespaced(t.namespace) { + cols = cols[1:] + } + data := TableData{ + namespace: t.namespace, + header: t.header.Labelize(cols, idx, t.rowEvents), + } + data.rowEvents = t.rowEvents.Labelize(cols, idx, labels) + + return &data +} + +// Customize returns a new model with customized column layout. +func (t *TableData) Customize(vs *config.ViewSetting, sc SortColumn, manual, wide bool) (*TableData, SortColumn) { + if vs.IsBlank() { + if sc.Name != "" { + return t, sc + } + psc, err := t.sortCol(vs) + if err == nil { + return t, psc + } + return t, sc + } + + cols := vs.Columns + cdata := TableData{ + gvr: t.gvr, + namespace: t.namespace, + header: t.header.Customize(cols, wide), + } + ids := t.header.MapIndices(cols, wide) + cdata.rowEvents = t.rowEvents.Customize(ids) + if manual || vs == nil { + return &cdata, sc + } + psc, err := cdata.sortCol(vs) + if err != nil { + return &cdata, sc + } + + return &cdata, psc +} + +func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) { + var psc SortColumn + + if t.HeaderCount() == 0 { + return psc, errors.New("no header found") + } + name, order, _ := vs.SortCol() + if _, ok := t.header.IndexOf(name, false); ok { + psc.Name, psc.ASC = name, order + return psc, nil + } + if client.IsAllNamespaces(t.GetNamespace()) { + if _, ok := t.header.IndexOf("NAMESPACE", false); ok { + psc.Name = "NAMESPACE" + } else if _, ok := t.header.IndexOf("NAME", false); ok { + psc.Name = "NAME" + } + } else { + if _, ok := t.header.IndexOf("NAME", false); ok { + psc.Name = "NAME" + } else { + psc.Name = t.header[0].Name + } + } + + return psc, nil +} + +// Clear clears out the entire table. +func (t *TableData) Clear() { + t.mx.Lock() + defer t.mx.Unlock() + + t.header = t.header.Clear() + t.rowEvents.Clear() +} + +// Clone returns a copy of the table. +func (t *TableData) Clone() *TableData { + t.mx.RLock() + defer t.mx.RUnlock() + + return &TableData{ + header: t.header.Clone(), + rowEvents: t.rowEvents.Clone(), + namespace: t.namespace, + } +} + +func (t *TableData) ColumnNames(w bool) []string { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.header.ColumnNames(w) +} + +// GetHeader returns table header. +func (t *TableData) GetHeader() Header { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.header +} + +// SetHeader sets table header. +func (t *TableData) SetHeader(ns string, h Header) { + t.mx.Lock() + defer t.mx.Unlock() + + t.namespace, t.header = ns, h +} + +// Update computes row deltas and update the table data. +func (t *TableData) Update(rows Rows) { + empty := t.Empty() + kk := make(map[string]struct{}, len(rows)) + var blankDelta DeltaRow + t.mx.Lock() + { + for _, row := range rows { + kk[row.ID] = struct{}{} + if empty { + t.rowEvents.Add(NewRowEvent(EventAdd, row)) + continue + } + if index, ok := t.rowEvents.FindIndex(row.ID); ok { + ev, ok := t.rowEvents.At(index) + if !ok { + continue + } + delta := NewDeltaRow(ev.Row, row, t.header) + if delta.IsBlank() { + ev.Kind, ev.Deltas, ev.Row = EventUnchanged, blankDelta, row + t.rowEvents.Set(index, ev) + } else { + t.rowEvents.Set(index, NewRowEventWithDeltas(row, delta)) + } + continue + } + t.rowEvents.Add(NewRowEvent(EventAdd, row)) + } + } + t.mx.Unlock() + + if !empty { + t.Delete(kk) + } +} + +// Delete removes items in cache that are no longer valid. +func (t *TableData) Delete(newKeys map[string]struct{}) { + t.mx.Lock() + { + victims := make([]string, 0, 10) + t.rowEvents.Range(func(_ int, e RowEvent) bool { + if _, ok := newKeys[e.Row.ID]; !ok { + victims = append(victims, e.Row.ID) + } else { + delete(newKeys, e.Row.ID) + } + return true + }) + for _, id := range victims { + if err := t.rowEvents.Delete(id); err != nil { + log.Error().Err(err).Msgf("table delete failed: %q", id) + } + } + } + t.mx.Unlock() +} + +// Diff checks if two tables are equal. +func (t *TableData) Diff(t2 *TableData) bool { + if t2 == nil || t.namespace != t2.namespace || t.header.Diff(t2.header) { + return true + } + idx, ok := t.header.IndexOf("AGE", true) + if !ok { + idx = -1 + } + return t.rowEvents.Diff(t2.rowEvents, idx) +} diff --git a/internal/model1/table_data_test.go b/internal/model1/table_data_test.go new file mode 100644 index 0000000000..fc338a5649 --- /dev/null +++ b/internal/model1/table_data_test.go @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import ( + "testing" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" +) + +func init() { + zerolog.SetGlobalLevel(zerolog.FatalLevel) +} + +func TestTableDataCustomize(t *testing.T) { + uu := map[string]struct { + t1, e *TableData + vs config.ViewSetting + sc SortColumn + wide, manual bool + }{ + "same": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + vs: config.ViewSetting{Columns: []string{"A", "B", "C"}}, + e: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + }, + "wide-col": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B", Wide: true}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + vs: config.ViewSetting{Columns: []string{"A", "B", "C"}}, + e: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B", Wide: false}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + }, + "wide": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B", Wide: true}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + wide: true, + vs: config.ViewSetting{Columns: []string{"A", "C"}}, + e: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "C"}, + HeaderColumn{Name: "B", Wide: true}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "3", "2"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "3", "2"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "3", "2"}}}, + ), + ), + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + td, _ := u.t1.Customize(&u.vs, u.sc, u.manual, u.wide) + assert.Equal(t, u.e, td) + }) + } +} + +func TestTableDataDiff(t *testing.T) { + uu := map[string]struct { + t1, t2 *TableData + e bool + }{ + "empty": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + e: true, + }, + "same": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + t2: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + }, + "ns-diff": { + t1: NewTableDataFull( + client.NewGVR("test"), + "ns1", + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + t2: NewTableDataFull( + client.NewGVR("test"), + "ns-2", + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + e: true, + }, + "header-diff": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "D"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + t2: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + e: true, + }, + "row-diff": { + t1: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + ), + t2: NewTableDataWithRows( + client.NewGVR("test"), + Header{ + HeaderColumn{Name: "A"}, + HeaderColumn{Name: "B"}, + HeaderColumn{Name: "C"}, + }, + NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"100", "2", "3"}}}, + ), + ), + e: true, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.t1.Diff(u.t2)) + }) + } +} + +func TestTableDataUpdate(t *testing.T) { + uu := map[string]struct { + re, e *RowEvents + rr Rows + }{ + "no-change": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + rr: Rows{ + Row{ID: "A", Fields: Fields{"1", "2", "3"}}, + Row{ID: "B", Fields: Fields{"0", "2", "3"}}, + Row{ID: "C", Fields: Fields{"10", "2", "3"}}, + }, + e: NewRowEventsWithEvts( + RowEvent{Kind: EventUnchanged, Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + }, + "add": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + rr: Rows{ + Row{ID: "A", Fields: Fields{"1", "2", "3"}}, + Row{ID: "B", Fields: Fields{"0", "2", "3"}}, + Row{ID: "C", Fields: Fields{"10", "2", "3"}}, + Row{ID: "D", Fields: Fields{"10", "2", "3"}}, + }, + e: NewRowEventsWithEvts( + RowEvent{Kind: EventUnchanged, Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + RowEvent{Kind: EventAdd, Row: Row{ID: "D", Fields: Fields{"10", "2", "3"}}}, + ), + }, + "delete": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + rr: Rows{ + Row{ID: "A", Fields: Fields{"1", "2", "3"}}, + Row{ID: "C", Fields: Fields{"10", "2", "3"}}, + }, + e: NewRowEventsWithEvts( + RowEvent{Kind: EventUnchanged, Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + }, + "update": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + rr: Rows{ + Row{ID: "A", Fields: Fields{"10", "2", "3"}}, + Row{ID: "B", Fields: Fields{"0", "2", "3"}}, + Row{ID: "C", Fields: Fields{"10", "2", "3"}}, + }, + e: NewRowEventsWithEvts( + RowEvent{ + Kind: EventUpdate, + Row: Row{ID: "A", Fields: Fields{"10", "2", "3"}}, + Deltas: DeltaRow{"1", "", ""}, + }, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Kind: EventUnchanged, Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + }, + } + + var table TableData + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + table.SetRowEvents(u.re) + table.Update(u.rr) + assert.Equal(t, u.e, table.GetRowEvents()) + }) + } +} + +func TestTableDataDelete(t *testing.T) { + uu := map[string]struct { + re, e *RowEvents + kk map[string]struct{} + }{ + "ordered": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + kk: map[string]struct{}{"A": {}, "C": {}}, + e: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + }, + "unordered": { + re: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + RowEvent{Row: Row{ID: "D", Fields: Fields{"10", "2", "3"}}}, + ), + kk: map[string]struct{}{"C": {}, "A": {}}, + e: NewRowEventsWithEvts( + RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}}, + RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}}, + ), + }, + } + + var table TableData + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + table.SetRowEvents(u.re) + table.Delete(u.kk) + assert.Equal(t, u.e, table.GetRowEvents()) + }) + } +} diff --git a/internal/model1/test_helper_test.go b/internal/model1/test_helper_test.go new file mode 100644 index 0000000000..42350b333a --- /dev/null +++ b/internal/model1/test_helper_test.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1_test + +import ( + "fmt" + "time" +) + +func testTime() time.Time { + t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") + if err != nil { + fmt.Println("TestTime Failed", err) + } + return t +} diff --git a/internal/model1/types.go b/internal/model1/types.go new file mode 100644 index 0000000000..2fc32ad278 --- /dev/null +++ b/internal/model1/types.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package model1 + +import ( + "github.com/derailed/tcell/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + NAValue = "na" + + // EventUnchanged notifies listener resource has not changed. + EventUnchanged ResEvent = 1 << iota + + // EventAdd notifies listener of a resource was added. + EventAdd + + // EventUpdate notifies listener of a resource updated. + EventUpdate + + // EventDelete notifies listener of a resource was deleted. + EventDelete + + // EventClear the stack was reset. + EventClear +) + +// DecoratorFunc decorates a string. +type DecoratorFunc func(string) string + +// ColorerFunc represents a resource row colorer. +type ColorerFunc func(ns string, h Header, re *RowEvent) tcell.Color + +// Renderer represents a resource renderer. +type Renderer interface { + // IsGeneric identifies a generic handler. + IsGeneric() bool + + // Render converts raw resources to tabular data. + Render(o interface{}, ns string, row *Row) error + + // Header returns the resource header. + Header(ns string) Header + + // ColorerFunc returns a row colorer function. + ColorerFunc() ColorerFunc +} + +// Generic represents a generic resource. +type Generic interface { + // SetTable sets up the resource tabular definition. + SetTable(ns string, table *metav1.Table) + + // Header returns a resource header. + Header(ns string) Header + + // Render renders the resource. + Render(o interface{}, ns string, row *Row) error +} diff --git a/internal/render/alias.go b/internal/render/alias.go index ce8f386d05..592296f36c 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -18,17 +19,17 @@ type Alias struct { } // Header returns a header row. -func (Alias) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "RESOURCE"}, - HeaderColumn{Name: "COMMAND"}, - HeaderColumn{Name: "API-GROUP"}, +func (Alias) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "RESOURCE"}, + model1.HeaderColumn{Name: "COMMAND"}, + model1.HeaderColumn{Name: "API-GROUP"}, } } // Render renders a K8s resource to screen. // BOZO!! Pass in a row with pre-alloc fields?? -func (Alias) Render(o interface{}, ns string, r *Row) error { +func (Alias) Render(o interface{}, ns string, r *model1.Row) error { a, ok := o.(AliasRes) if !ok { return fmt.Errorf("expected AliasRes, but got %T", o) diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go index 85e61f86ef..af85a3f69c 100644 --- a/internal/render/alias_test.go +++ b/internal/render/alias_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" @@ -14,30 +15,30 @@ import ( func TestAliasColorer(t *testing.T) { var a render.Alias - h := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + h := model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, } - r := render.Row{ID: "g/v/r", Fields: render.Fields{"r", "blee", "g"}} + r := model1.Row{ID: "g/v/r", Fields: model1.Fields{"r", "blee", "g"}} uu := map[string]struct { ns string - re render.RowEvent + re model1.RowEvent e tcell.Color }{ "addAll": { ns: client.NamespaceAll, - re: render.RowEvent{Kind: render.EventAdd, Row: r}, + re: model1.RowEvent{Kind: model1.EventAdd, Row: r}, e: tcell.ColorBlue, }, "deleteAll": { ns: client.NamespaceAll, - re: render.RowEvent{Kind: render.EventDelete, Row: r}, + re: model1.RowEvent{Kind: model1.EventDelete, Row: r}, e: tcell.ColorGray, }, "updateAll": { ns: client.NamespaceAll, - re: render.RowEvent{Kind: render.EventUpdate, Row: r}, + re: model1.RowEvent{Kind: model1.EventUpdate, Row: r}, e: tcell.ColorDefault, }, } @@ -45,16 +46,16 @@ func TestAliasColorer(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, a.ColorerFunc()(u.ns, h, u.re)) + assert.Equal(t, u.e, a.ColorerFunc()(u.ns, h, &u.re)) }) } } func TestAliasHeader(t *testing.T) { - h := render.Header{ - render.HeaderColumn{Name: "RESOURCE"}, - render.HeaderColumn{Name: "COMMAND"}, - render.HeaderColumn{Name: "API-GROUP"}, + h := model1.Header{ + model1.HeaderColumn{Name: "RESOURCE"}, + model1.HeaderColumn{Name: "COMMAND"}, + model1.HeaderColumn{Name: "API-GROUP"}, } var a render.Alias @@ -70,9 +71,9 @@ func TestAliasRender(t *testing.T) { Aliases: []string{"a", "b", "c"}, } - var r render.Row + var r model1.Row assert.Nil(t, a.Render(o, "fred/v1/blee", &r)) - assert.Equal(t, render.Row{ID: "fred/v1/blee", Fields: render.Fields{"blee", "a,b,c", "fred"}}, r) + assert.Equal(t, model1.Row{ID: "fred/v1/blee", Fields: model1.Fields{"blee", "a,b,c", "fred"}}, r) } func BenchmarkAlias(b *testing.B) { @@ -85,7 +86,7 @@ func BenchmarkAlias(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - var r render.Row + var r model1.Row _ = a.Render(o, "aliases", &r) } } diff --git a/internal/render/base.go b/internal/render/base.go index 65e66b44bb..003fe6a860 100644 --- a/internal/render/base.go +++ b/internal/render/base.go @@ -3,6 +3,10 @@ package render +import ( + "github.com/derailed/k9s/internal/model1" +) + // DecoratorFunc decorates a string. type DecoratorFunc func(string) string @@ -19,11 +23,11 @@ func (Base) IsGeneric() bool { } // ColorerFunc colors a resource row. -func (Base) ColorerFunc() ColorerFunc { - return DefaultColorer +func (Base) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Happy returns true if resource is happy, false otherwise. -func (Base) Happy(_ string, _ Row) bool { +func (Base) Happy(string, *model1.Row) bool { return true } diff --git a/internal/render/benchmark.go b/internal/render/benchmark.go index d8a3c75490..ded85e3170 100644 --- a/internal/render/benchmark.go +++ b/internal/render/benchmark.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "github.com/derailed/tview" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,33 +34,34 @@ type Benchmark struct { } // ColorerFunc colors a resource row. -func (b Benchmark) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { - return ErrColor +func (b Benchmark) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + if !model1.IsValid(ns, h, re.Row) { + return model1.ErrColor } + return tcell.ColorPaleGreen } } // Header returns a header row. -func (Benchmark) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "TIME"}, - HeaderColumn{Name: "REQ/S", Align: tview.AlignRight}, - HeaderColumn{Name: "2XX", Align: tview.AlignRight}, - HeaderColumn{Name: "4XX/5XX", Align: tview.AlignRight}, - HeaderColumn{Name: "REPORT"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Benchmark) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "TIME"}, + model1.HeaderColumn{Name: "REQ/S", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "2XX", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "4XX/5XX", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "REPORT"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (b Benchmark) Render(o interface{}, ns string, r *Row) error { +func (b Benchmark) Render(o interface{}, ns string, r *model1.Row) error { bench, ok := o.(BenchInfo) if !ok { return fmt.Errorf("no benchmarks available %T", o) @@ -71,7 +73,7 @@ func (b Benchmark) Render(o interface{}, ns string, r *Row) error { } r.ID = bench.Path - r.Fields = make(Fields, len(b.Header(ns))) + r.Fields = make(model1.Fields, len(b.Header(ns))) if err := b.initRow(r.Fields, bench.File); err != nil { return err } @@ -82,7 +84,7 @@ func (b Benchmark) Render(o interface{}, ns string, r *Row) error { } // Happy returns true if resource is happy, false otherwise. -func (Benchmark) diagnose(ns string, ff Fields) error { +func (Benchmark) diagnose(ns string, ff model1.Fields) error { statusCol := 3 if !client.IsAllNamespaces(ns) { statusCol-- @@ -109,7 +111,7 @@ func (Benchmark) readFile(file string) (string, error) { return string(data), nil } -func (b Benchmark) initRow(row Fields, f os.FileInfo) error { +func (b Benchmark) initRow(row model1.Fields, f os.FileInfo) error { tokens := strings.Split(f.Name(), "_") if len(tokens) < 2 { return fmt.Errorf("invalid file name %s", f.Name()) @@ -122,7 +124,7 @@ func (b Benchmark) initRow(row Fields, f os.FileInfo) error { return nil } -func (b Benchmark) augmentRow(fields Fields, data string) { +func (b Benchmark) augmentRow(fields model1.Fields, data string) { if len(data) == 0 { return } diff --git a/internal/render/benchmark_int_test.go b/internal/render/benchmark_int_test.go index bd296e23e6..a7d4a387c6 100644 --- a/internal/render/benchmark_int_test.go +++ b/internal/render/benchmark_int_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + "github.com/derailed/k9s/internal/model1" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) @@ -18,23 +19,23 @@ func init() { func TestAugmentRow(t *testing.T) { uu := map[string]struct { file string - e Fields + e model1.Fields }{ "cool": { "testdata/b1.txt", - Fields{"pass", "3.3544", "29.8116", "100", "0"}, + model1.Fields{"pass", "3.3544", "29.8116", "100", "0"}, }, "2XX": { "testdata/b4.txt", - Fields{"pass", "3.3544", "29.8116", "160", "0"}, + model1.Fields{"pass", "3.3544", "29.8116", "160", "0"}, }, "4XX/5XX": { "testdata/b2.txt", - Fields{"pass", "3.3544", "29.8116", "100", "12"}, + model1.Fields{"pass", "3.3544", "29.8116", "100", "12"}, }, "toast": { "testdata/b3.txt", - Fields{"fail", "2.3688", "35.4606", "0", "0"}, + model1.Fields{"fail", "2.3688", "35.4606", "0", "0"}, }, } @@ -44,7 +45,7 @@ func TestAugmentRow(t *testing.T) { data, err := os.ReadFile(u.file) assert.Nil(t, err) - fields := make(Fields, 8) + fields := make(model1.Fields, 8) b := Benchmark{} b.augmentRow(fields, string(data)) assert.Equal(t, u.e, fields[2:7]) diff --git a/internal/render/cm.go b/internal/render/cm.go new file mode 100644 index 0000000000..f6158efbeb --- /dev/null +++ b/internal/render/cm.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "strconv" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// ConfigMap renders a K8s ConfigMap to screen. +type ConfigMap struct { + Base +} + +// Header returns a header rbw. +func (ConfigMap) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "DATA"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, + } +} + +// Render renders a K8s resource to screen. +func (n ConfigMap) Render(o interface{}, _ string, r *model1.Row) error { + raw, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expected ConfigMap, but got %T", o) + } + var cm v1.ConfigMap + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cm) + if err != nil { + return err + } + + r.ID = client.FQN(cm.Namespace, cm.Name) + r.Fields = model1.Fields{ + cm.Namespace, + cm.Name, + strconv.Itoa(len(cm.Data)), + "", + ToAge(cm.GetCreationTimestamp()), + } + + return nil +} diff --git a/internal/render/color_test.go b/internal/render/color_test.go deleted file mode 100644 index baa8c5ff49..0000000000 --- a/internal/render/color_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/render" - "github.com/derailed/tcell/v2" - "github.com/stretchr/testify/assert" -) - -func TestDefaultColorer(t *testing.T) { - uu := map[string]struct { - re render.RowEvent - e tcell.Color - }{ - "add": { - render.RowEvent{ - Kind: render.EventAdd, - }, - render.AddColor, - }, - "update": { - render.RowEvent{ - Kind: render.EventUpdate, - }, - render.ModColor, - }, - "delete": { - render.RowEvent{ - Kind: render.EventDelete, - }, - render.KillColor, - }, - "no-change": { - render.RowEvent{ - Kind: render.EventUnchanged, - }, - render.StdColor, - }, - "invalid": { - render.RowEvent{ - Kind: render.EventUnchanged, - Row: render.Row{ - Fields: render.Fields{"", "", "blah"}, - }, - }, - render.ErrColor, - }, - } - - h := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "VALID"}, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, render.DefaultColorer("", h, u.re)) - }) - } -} diff --git a/internal/render/container.go b/internal/render/container.go index b9bd968e1b..35f700f267 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "github.com/derailed/tview" v1 "k8s.io/api/core/v1" @@ -43,60 +44,58 @@ type Container struct { } // ColorerFunc colors a resource row. -func (c Container) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { - return ErrColor - } +func (c Container) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) - stateCol := h.IndexOf("STATE", true) - if stateCol == -1 { - return DefaultColorer(ns, h, re) + idx, ok := h.IndexOf("STATE", true) + if !ok { + return c } - switch strings.TrimSpace(re.Row.Fields[stateCol]) { + switch strings.TrimSpace(re.Row.Fields[idx]) { case Pending: - return PendingColor + return model1.PendingColor case ContainerCreating, PodInitializing: - return AddColor + return model1.AddColor case Terminating, Initialized: - return HighlightColor + return model1.HighlightColor case Completed: - return CompletedColor + return model1.CompletedColor case Running: - return DefaultColorer(ns, h, re) + return c default: - return ErrColor + return model1.ErrColor } } } // Header returns a header row. -func (Container) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "PF"}, - HeaderColumn{Name: "IMAGE"}, - HeaderColumn{Name: "READY"}, - HeaderColumn{Name: "STATE"}, - HeaderColumn{Name: "INIT"}, - HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, - HeaderColumn{Name: "PROBES(L:R)"}, - HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight}, - HeaderColumn{Name: "MEM/R:L", Align: tview.AlignRight}, - HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "PORTS"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Container) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "PF"}, + model1.HeaderColumn{Name: "IMAGE"}, + model1.HeaderColumn{Name: "READY"}, + model1.HeaderColumn{Name: "STATE"}, + model1.HeaderColumn{Name: "INIT"}, + model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "PROBES(L:R)"}, + model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "MEM/R:L", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "PORTS"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (c Container) Render(o interface{}, name string, r *Row) error { +func (c Container) Render(o interface{}, name string, r *model1.Row) error { co, ok := o.(ContainerRes) if !ok { return fmt.Errorf("expected ContainerRes, but got %T", o) @@ -109,7 +108,7 @@ func (c Container) Render(o interface{}, name string, r *Row) error { } r.ID = co.Container.Name - r.Fields = Fields{ + r.Fields = model1.Fields{ co.Container.Name, "●", co.Container.Image, diff --git a/internal/render/container_test.go b/internal/render/container_test.go index e574df56e6..f790b3a541 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" @@ -26,10 +27,10 @@ func TestContainer(t *testing.T) { IsInit: false, Age: makeAge(), } - var r render.Row + var r model1.Row assert.Nil(t, c.Render(cres, "blee", &r)) assert.Equal(t, "fred", r.ID) - assert.Equal(t, render.Fields{ + assert.Equal(t, model1.Fields{ "fred", "●", "img", @@ -63,7 +64,7 @@ func BenchmarkContainerRender(b *testing.B) { IsInit: false, Age: makeAge(), } - var r render.Row + var r model1.Row b.ReportAllocs() b.ResetTimer() diff --git a/internal/render/context.go b/internal/render/context.go index 3c105e0f20..06a622ad62 100644 --- a/internal/render/context.go +++ b/internal/render/context.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime" @@ -20,11 +21,11 @@ type Context struct { } // ColorerFunc colors a resource row. -func (Context) ColorerFunc() ColorerFunc { - return func(ns string, h Header, r RowEvent) tcell.Color { - c := DefaultColorer(ns, h, r) +func (Context) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, r *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, r) if strings.Contains(strings.TrimSpace(r.Row.Fields[0]), "*") { - return HighlightColor + return model1.HighlightColor } return c @@ -32,17 +33,17 @@ func (Context) ColorerFunc() ColorerFunc { } // Header returns a header row. -func (Context) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "CLUSTER"}, - HeaderColumn{Name: "AUTHINFO"}, - HeaderColumn{Name: "NAMESPACE"}, +func (Context) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "CLUSTER"}, + model1.HeaderColumn{Name: "AUTHINFO"}, + model1.HeaderColumn{Name: "NAMESPACE"}, } } // Render renders a K8s resource to screen. -func (c Context) Render(o interface{}, _ string, r *Row) error { +func (c Context) Render(o interface{}, _ string, r *model1.Row) error { ctx, ok := o.(*NamedContext) if !ok { return fmt.Errorf("expected *NamedContext, but got %T", o) @@ -54,7 +55,7 @@ func (c Context) Render(o interface{}, _ string, r *Row) error { } r.ID = ctx.Name - r.Fields = Fields{ + r.Fields = model1.Fields{ name, ctx.Context.Cluster, ctx.Context.AuthInfo, diff --git a/internal/render/context_test.go b/internal/render/context_test.go index 4c20249ce5..1cdc3911f5 100644 --- a/internal/render/context_test.go +++ b/internal/render/context_test.go @@ -6,6 +6,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" "k8s.io/client-go/tools/clientcmd/api" @@ -20,7 +21,7 @@ func TestContextHeader(t *testing.T) { func TestContextRender(t *testing.T) { uu := map[string]struct { ctx *render.NamedContext - e render.Row + e model1.Row }{ "active": { ctx: &render.NamedContext{ @@ -33,9 +34,9 @@ func TestContextRender(t *testing.T) { }, Config: &config{}, }, - e: render.Row{ + e: model1.Row{ ID: "c1", - Fields: render.Fields{"c1", "c1", "u1", "ns1"}, + Fields: model1.Fields{"c1", "c1", "u1", "ns1"}, }, }, } @@ -44,7 +45,7 @@ func TestContextRender(t *testing.T) { for k := range uu { uc := uu[k] t.Run(k, func(t *testing.T) { - row := render.NewRow(4) + row := model1.NewRow(4) err := r.Render(uc.ctx, "", &row) assert.Nil(t, err) diff --git a/internal/render/cr.go b/internal/render/cr.go index 5a0a84fdbd..a148fc70b5 100644 --- a/internal/render/cr.go +++ b/internal/render/cr.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,16 +19,16 @@ type ClusterRole struct { } // Header returns a header rbw. -func (ClusterRole) Header(string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (ClusterRole) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (ClusterRole) Render(o interface{}, ns string, r *Row) error { +func (ClusterRole) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expecting clusterrole, but got %T", o) @@ -39,7 +40,7 @@ func (ClusterRole) Render(o interface{}, ns string, r *Row) error { } r.ID = client.FQN("-", cr.ObjectMeta.Name) - r.Fields = Fields{ + r.Fields = model1.Fields{ cr.Name, mapToStr(cr.Labels), ToAge(cr.GetCreationTimestamp()), diff --git a/internal/render/cr_test.go b/internal/render/cr_test.go index d6908a0ecc..d6d175311a 100644 --- a/internal/render/cr_test.go +++ b/internal/render/cr_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestClusterRoleRender(t *testing.T) { c := render.ClusterRole{} - r := render.NewRow(2) + r := model1.NewRow(2) assert.NoError(t, c.Render(load(t, "cr"), "-", &r)) assert.Equal(t, "-/blee", r.ID) - assert.Equal(t, render.Fields{"blee"}, r.Fields[:1]) + assert.Equal(t, model1.Fields{"blee"}, r.Fields[:1]) } diff --git a/internal/render/crb.go b/internal/render/crb.go index e051337dcf..8290973e6f 100644 --- a/internal/render/crb.go +++ b/internal/render/crb.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,19 +19,19 @@ type ClusterRoleBinding struct { } // Header returns a header rbw. -func (ClusterRoleBinding) Header(string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "CLUSTERROLE"}, - HeaderColumn{Name: "SUBJECT-KIND"}, - HeaderColumn{Name: "SUBJECTS"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (ClusterRoleBinding) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "CLUSTERROLE"}, + model1.HeaderColumn{Name: "SUBJECT-KIND"}, + model1.HeaderColumn{Name: "SUBJECTS"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error { +func (ClusterRoleBinding) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected ClusterRoleBinding, but got %T", o) @@ -44,7 +45,7 @@ func (ClusterRoleBinding) Render(o interface{}, ns string, r *Row) error { kind, ss := renderSubjects(crb.Subjects) r.ID = client.FQN("-", crb.ObjectMeta.Name) - r.Fields = Fields{ + r.Fields = model1.Fields{ crb.Name, crb.RoleRef.Name, kind, diff --git a/internal/render/crb_test.go b/internal/render/crb_test.go index 85aac27671..4b350a4c94 100644 --- a/internal/render/crb_test.go +++ b/internal/render/crb_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestClusterRoleBindingRender(t *testing.T) { c := render.ClusterRoleBinding{} - r := render.NewRow(5) + r := model1.NewRow(5) assert.NoError(t, c.Render(load(t, "crb"), "-", &r)) assert.Equal(t, "-/blee", r.ID) - assert.Equal(t, render.Fields{"blee", "blee", "User", "fernand"}, r.Fields[:4]) + assert.Equal(t, model1.Fields{"blee", "blee", "User", "fernand"}, r.Fields[:4]) } diff --git a/internal/render/crd.go b/internal/render/crd.go index bed197fbc3..fddeec1973 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/rs/zerolog/log" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,18 +22,22 @@ type CustomResourceDefinition struct { } // Header returns a header rbw. -func (CustomResourceDefinition) Header(string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VERSIONS"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (CustomResourceDefinition) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "GROUP"}, + model1.HeaderColumn{Name: "KIND"}, + model1.HeaderColumn{Name: "VERSIONS"}, + model1.HeaderColumn{Name: "SCOPE"}, + model1.HeaderColumn{Name: "ALIASES", Wide: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error { +func (c CustomResourceDefinition) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected CustomResourceDefinition, but got %T", o) @@ -44,7 +49,7 @@ func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error return err } - versions := make([]string, 0, 3) + versions := make([]string, 0, len(crd.Spec.Versions)) for _, v := range crd.Spec.Versions { if v.Served { n := v.Name @@ -55,15 +60,19 @@ func (c CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error } } if len(versions) == 0 { - log.Warn().Msgf("unable to assert CRD versions for %s", crd.GetName()) + log.Warn().Msgf("unable to assert CRD versions for %s", crd.Name) } - r.ID = client.FQN(client.ClusterScope, crd.GetName()) - r.Fields = Fields{ - crd.GetName(), + r.ID = client.MetaFQN(crd.ObjectMeta) + r.Fields = model1.Fields{ + crd.Spec.Names.Plural, + crd.Spec.Group, + crd.Spec.Names.Kind, naStrings(versions), + string(crd.Spec.Scope), + naStrings(crd.Spec.Names.ShortNames), mapToIfc(crd.GetLabels()), - AsStatus(c.diagnose(crd.GetName(), crd.Spec.Versions)), + AsStatus(c.diagnose(crd.Name, crd.Spec.Versions)), ToAge(crd.GetCreationTimestamp()), } diff --git a/internal/render/crd_test.go b/internal/render/crd_test.go index fd12851049..a88715ee47 100644 --- a/internal/render/crd_test.go +++ b/internal/render/crd_test.go @@ -6,15 +6,17 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestCustomResourceDefinitionRender(t *testing.T) { c := render.CustomResourceDefinition{} - r := render.NewRow(2) + r := model1.NewRow(2) assert.NoError(t, c.Render(load(t, "crd"), "", &r)) assert.Equal(t, "-/adapters.config.istio.io", r.ID) - assert.Equal(t, render.Fields{"adapters.config.istio.io"}, r.Fields[:1]) + assert.Equal(t, "adapters", r.Fields[0]) + assert.Equal(t, "config.istio.io", r.Fields[1]) } diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go index e75d74f8c8..bd1ce7c334 100644 --- a/internal/render/cronjob.go +++ b/internal/render/cronjob.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,29 +22,26 @@ type CronJob struct { } // Header returns a header row. -func (CronJob) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "SCHEDULE"}, - HeaderColumn{Name: "SUSPEND"}, - HeaderColumn{Name: "ACTIVE"}, - HeaderColumn{Name: "LAST_SCHEDULE", Time: true}, - HeaderColumn{Name: "SELECTOR", Wide: true}, - HeaderColumn{Name: "CONTAINERS", Wide: true}, - HeaderColumn{Name: "IMAGES", Wide: true}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (CronJob) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "SCHEDULE"}, + model1.HeaderColumn{Name: "SUSPEND"}, + model1.HeaderColumn{Name: "ACTIVE"}, + model1.HeaderColumn{Name: "LAST_SCHEDULE", Time: true}, + model1.HeaderColumn{Name: "SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "CONTAINERS", Wide: true}, + model1.HeaderColumn{Name: "IMAGES", Wide: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h - } // Render renders a K8s resource to screen. -func (c CronJob) Render(o interface{}, ns string, r *Row) error { +func (c CronJob) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected CronJob, but got %T", o) @@ -60,7 +58,7 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(cj.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ cj.Namespace, cj.Name, computeVulScore(cj.ObjectMeta, &cj.Spec.JobTemplate.Spec.Template.Spec), diff --git a/internal/render/cronjob_test.go b/internal/render/cronjob_test.go index ff11bd9bf8..34a77a96ce 100644 --- a/internal/render/cronjob_test.go +++ b/internal/render/cronjob_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestCronJobRender(t *testing.T) { c := render.CronJob{} - r := render.NewRow(6) + r := model1.NewRow(6) assert.NoError(t, c.Render(load(t, "cj"), "", &r)) assert.Equal(t, "default/hello", r.ID) - assert.Equal(t, render.Fields{"default", "hello", "0", "*/1 * * * *", "false", "0"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"default", "hello", "0", "*/1 * * * *", "false", "0"}, r.Fields[:6]) } diff --git a/internal/render/delta_test.go b/internal/render/delta_test.go deleted file mode 100644 index 08d8960c34..0000000000 --- a/internal/render/delta_test.go +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" -) - -func TestDeltaLabelize(t *testing.T) { - uu := map[string]struct { - o render.Row - n render.Row - e render.DeltaRow - }{ - "same": { - o: render.Row{ - Fields: render.Fields{"a", "b", "blee=fred,doh=zorg"}, - }, - n: render.Row{ - Fields: render.Fields{"a", "b", "blee=fred1,doh=zorg"}, - }, - e: render.DeltaRow{"", "", "fred", "zorg"}, - }, - } - - hh := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - d := render.NewDeltaRow(u.o, u.n, hh) - d = d.Labelize([]int{0, 1}, 2) - assert.Equal(t, u.e, d) - }) - } -} - -func TestDeltaCustomize(t *testing.T) { - uu := map[string]struct { - r1, r2 render.Row - cols []int - e render.DeltaRow - }{ - "same": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - cols: []int{0, 1, 2}, - e: render.DeltaRow{"", "", ""}, - }, - "empty": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - e: render.DeltaRow{}, - }, - "diff-full": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a1", "b1", "c1"}, - }, - cols: []int{0, 1, 2}, - e: render.DeltaRow{"a", "b", "c"}, - }, - "diff-reverse": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a1", "b1", "c1"}, - }, - cols: []int{2, 1, 0}, - e: render.DeltaRow{"c", "b", "a"}, - }, - "diff-skip": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a1", "b1", "c1"}, - }, - cols: []int{2, 0}, - e: render.DeltaRow{"c", "a"}, - }, - "diff-missing": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a1", "b1", "c1"}, - }, - cols: []int{2, 10, 0}, - e: render.DeltaRow{"c", "", "a"}, - }, - "diff-negative": { - r1: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - r2: render.Row{ - Fields: render.Fields{"a1", "b1", "c1"}, - }, - cols: []int{2, -1, 0}, - e: render.DeltaRow{"c", "", "a"}, - }, - } - - hh := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - d := render.NewDeltaRow(u.r1, u.r2, hh) - out := make(render.DeltaRow, len(u.cols)) - d.Customize(u.cols, out) - assert.Equal(t, u.e, out) - }) - } -} - -func TestDeltaNew(t *testing.T) { - uu := map[string]struct { - o render.Row - n render.Row - blank bool - e render.DeltaRow - }{ - "same": { - o: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - n: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - blank: true, - e: render.DeltaRow{"", "", ""}, - }, - "diff": { - o: render.Row{ - Fields: render.Fields{"a1", "b", "c"}, - }, - n: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - e: render.DeltaRow{"a1", "", ""}, - }, - "diff2": { - o: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - n: render.Row{ - Fields: render.Fields{"a", "b1", "c"}, - }, - e: render.DeltaRow{"", "b", ""}, - }, - "diffLast": { - o: render.Row{ - Fields: render.Fields{"a", "b", "c"}, - }, - n: render.Row{ - Fields: render.Fields{"a", "b", "c1"}, - }, - e: render.DeltaRow{"", "", "c"}, - }, - } - - hh := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - } - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - d := render.NewDeltaRow(u.o, u.n, hh) - assert.Equal(t, u.e, d) - assert.Equal(t, u.blank, d.IsBlank()) - }) - } -} - -func TestDeltaBlank(t *testing.T) { - uu := map[string]struct { - r render.DeltaRow - e bool - }{ - "empty": { - r: render.DeltaRow{}, - e: true, - }, - "blank": { - r: render.DeltaRow{"", "", ""}, - e: true, - }, - "notblank": { - r: render.DeltaRow{"", "", "z"}, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.r.IsBlank()) - }) - } -} - -func TestDeltaDiff(t *testing.T) { - uu := map[string]struct { - d1, d2 render.DeltaRow - ageCol int - e bool - }{ - "empty": { - d1: render.DeltaRow{"f1", "f2", "f3"}, - ageCol: 2, - e: true, - }, - "same": { - d1: render.DeltaRow{"f1", "f2", "f3"}, - d2: render.DeltaRow{"f1", "f2", "f3"}, - ageCol: -1, - }, - "diff": { - d1: render.DeltaRow{"f1", "f2", "f3"}, - d2: render.DeltaRow{"f1", "f2", "f13"}, - ageCol: -1, - e: true, - }, - "diff-age-first": { - d1: render.DeltaRow{"f1", "f2", "f3"}, - d2: render.DeltaRow{"f1", "f2", "f13"}, - ageCol: 0, - e: true, - }, - "diff-age-last": { - d1: render.DeltaRow{"f1", "f2", "f3"}, - d2: render.DeltaRow{"f1", "f2", "f13"}, - ageCol: 2, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.d1.Diff(u.d2, u.ageCol)) - }) - } -} diff --git a/internal/render/dir.go b/internal/render/dir.go index b444c8c7c4..8e076d4e2e 100644 --- a/internal/render/dir.go +++ b/internal/render/dir.go @@ -7,6 +7,7 @@ import ( "fmt" "os" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -21,22 +22,22 @@ func (Dir) IsGeneric() bool { } // ColorerFunc colors a resource row. -func (Dir) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (Dir) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorCadetBlue } } // Header returns a header row. -func (Dir) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, +func (Dir) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, } } // Render renders a K8s resource to screen. // BOZO!! Pass in a row with pre-alloc fields?? -func (Dir) Render(o interface{}, ns string, r *Row) error { +func (Dir) Render(o interface{}, ns string, r *model1.Row) error { d, ok := o.(DirRes) if !ok { return fmt.Errorf("expected DirRes, but got %T", o) diff --git a/internal/render/dp.go b/internal/render/dp.go index 01eb96e69f..1444eeb99a 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" @@ -22,20 +23,18 @@ type Deployment struct { } // ColorerFunc colors a resource row. -func (d Deployment) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) - if !Happy(ns, h, re.Row) { - return ErrColor - } - rdCol := h.IndexOf("READY", true) - if rdCol == -1 { +func (d Deployment) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) + + idx, ok := h.IndexOf("READY", true) + if !ok { return c } - ready := strings.TrimSpace(re.Row.Fields[rdCol]) + ready := strings.TrimSpace(re.Row.Fields[idx]) tt := strings.Split(ready, "/") if len(tt) == 2 && tt[1] == "0" { - return PendingColor + return model1.PendingColor } return c @@ -43,24 +42,22 @@ func (d Deployment) ColorerFunc() ColorerFunc { } // Header returns a header row. -func (Deployment) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "READY", Align: tview.AlignRight}, - HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, - HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Deployment) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "READY", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h } // Render renders a K8s resource to screen. -func (d Deployment) Render(o interface{}, ns string, r *Row) error { +func (d Deployment) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Deployment, but got %T", o) @@ -73,7 +70,7 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(dp.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ dp.Namespace, dp.Name, computeVulScore(dp.ObjectMeta, &dp.Spec.Template.Spec), diff --git a/internal/render/dp_test.go b/internal/render/dp_test.go index 92653b1864..e4ecc4b145 100644 --- a/internal/render/dp_test.go +++ b/internal/render/dp_test.go @@ -6,22 +6,23 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestDpRender(t *testing.T) { c := render.Deployment{} - r := render.NewRow(7) + r := model1.NewRow(7) assert.Nil(t, c.Render(load(t, "dp"), "", &r)) assert.Equal(t, "icx/icx-db", r.ID) - assert.Equal(t, render.Fields{"icx", "icx-db", "0", "1/1", "1", "1"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"icx", "icx-db", "0", "1/1", "1", "1"}, r.Fields[:6]) } func BenchmarkDpRender(b *testing.B) { c := render.Deployment{} - r := render.NewRow(7) + r := model1.NewRow(7) o := load(b, "dp") b.ResetTimer() diff --git a/internal/render/ds.go b/internal/render/ds.go index d14a1569b5..b3f047aa7a 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -20,26 +21,24 @@ type DaemonSet struct { } // Header returns a header row. -func (DaemonSet) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, - HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, - HeaderColumn{Name: "READY", Align: tview.AlignRight}, - HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, - HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (DaemonSet) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "READY", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "UP-TO-DATE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "AVAILABLE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h } // Render renders a K8s resource to screen. -func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { +func (d DaemonSet) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected DaemonSet, but got %T", o) @@ -51,7 +50,7 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(ds.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ ds.Namespace, ds.Name, computeVulScore(ds.ObjectMeta, &ds.Spec.Template.Spec), diff --git a/internal/render/ds_test.go b/internal/render/ds_test.go index 5753bcb6a4..16598332da 100644 --- a/internal/render/ds_test.go +++ b/internal/render/ds_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestDaemonSetRender(t *testing.T) { c := render.DaemonSet{} - r := render.NewRow(9) + r := model1.NewRow(9) assert.NoError(t, c.Render(load(t, "ds"), "", &r)) assert.Equal(t, "kube-system/fluentd-gcp-v3.2.0", r.ID) - assert.Equal(t, render.Fields{"kube-system", "fluentd-gcp-v3.2.0", "0", "2", "2", "2", "2", "2"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"kube-system", "fluentd-gcp-v3.2.0", "0", "2", "2", "2", "2", "2"}, r.Fields[:8]) } diff --git a/internal/render/ep.go b/internal/render/ep.go index 15af70d1b4..9fa4bcc80d 100644 --- a/internal/render/ep.go +++ b/internal/render/ep.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,17 +21,17 @@ type Endpoints struct { } // Header returns a header row. -func (Endpoints) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "ENDPOINTS"}, - HeaderColumn{Name: "AGE", Time: true}, +func (Endpoints) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "ENDPOINTS"}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (e Endpoints) Render(o interface{}, ns string, r *Row) error { +func (e Endpoints) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Endpoints, but got %T", o) @@ -42,8 +43,8 @@ func (e Endpoints) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(ep.ObjectMeta) - r.Fields = make(Fields, 0, len(e.Header(ns))) - r.Fields = Fields{ + r.Fields = make(model1.Fields, 0, len(e.Header(ns))) + r.Fields = model1.Fields{ ep.Namespace, ep.Name, missing(toEPs(ep.Subsets)), diff --git a/internal/render/ep_test.go b/internal/render/ep_test.go index 620f87e0cf..f4359f3a7f 100644 --- a/internal/render/ep_test.go +++ b/internal/render/ep_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestEndpointsRender(t *testing.T) { c := render.Endpoints{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "ep"), "", &r)) assert.Equal(t, "default/dictionary1", r.ID) - assert.Equal(t, render.Fields{"default", "dictionary1", ""}, r.Fields[:3]) + assert.Equal(t, model1.Fields{"default", "dictionary1", ""}, r.Fields[:3]) } diff --git a/internal/render/ev.go b/internal/render/ev.go index f2e8b9b1f1..28e04f7923 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -22,14 +23,14 @@ func (*Event) IsGeneric() bool { } // ColorerFunc colors a resource row. -func (e *Event) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - reasonCol := h.IndexOf("REASON", true) - if reasonCol >= 0 && strings.TrimSpace(re.Row.Fields[reasonCol]) == "Killing" { - return KillColor +func (e *Event) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + idx, ok := h.IndexOf("REASON", true) + if ok && strings.TrimSpace(re.Row.Fields[idx]) == "Killing" { + return model1.KillColor } - return DefaultColorer(ns, h, re) + return model1.DefaultColorer(ns, h, re) } } @@ -46,14 +47,14 @@ var wideCols = map[string]struct{}{ "MESSAGE": {}, } -func (e *Event) Header(ns string) Header { +func (e *Event) Header(ns string) model1.Header { if e.table == nil { - return Header{} + return model1.Header{} } - hh := make(Header, 0, len(e.table.ColumnDefinitions)) - hh = append(hh, HeaderColumn{Name: "NAMESPACE"}) + hh := make(model1.Header, 0, len(e.table.ColumnDefinitions)) + hh = append(hh, model1.HeaderColumn{Name: "NAMESPACE"}) for _, h := range e.table.ColumnDefinitions { - header := HeaderColumn{Name: strings.ToUpper(h.Name)} + header := model1.HeaderColumn{Name: strings.ToUpper(h.Name)} if _, ok := ageCols[header.Name]; ok { header.Time = true } @@ -67,7 +68,7 @@ func (e *Event) Header(ns string) Header { } // Render renders a K8s resource to screen. -func (e *Event) Render(o interface{}, ns string, r *Row) error { +func (e *Event) Render(o interface{}, ns string, r *model1.Row) error { row, ok := o.(metav1.TableRow) if !ok { return fmt.Errorf("expecting a TableRow but got %T", o) @@ -81,7 +82,7 @@ func (e *Event) Render(o interface{}, ns string, r *Row) error { return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0]) } r.ID = client.FQN(nns, name) - r.Fields = make(Fields, 0, len(e.Header(ns))) + r.Fields = make(model1.Fields, 0, len(e.Header(ns))) r.Fields = append(r.Fields, nns) for _, o := range row.Cells { if o == nil { diff --git a/internal/render/ev_test.go b/internal/render/ev_test.go index ce7ba27c73..dbb0794b66 100644 --- a/internal/render/ev_test.go +++ b/internal/render/ev_test.go @@ -6,17 +6,17 @@ package render_test // BOZO!! // func TestEventRender(t *testing.T) { // c := render.Event{} -// r := render.NewRow(7) +// r := model1.NewRow(7) // c.Render(load(t, "ev"), "", &r) // assert.Equal(t, "default/hello-1567197780-mn4mv.15bfce150bd764dd", r.ID) -// assert.Equal(t, render.Fields{"default", "pod:hello-1567197780-mn4mv", "Normal", "Pulled", "kubelet", "1", `Successfully pulled image "blang/busybox-bash"`}, r.Fields[:7]) +// assert.Equal(t, model1.Fields{"default", "pod:hello-1567197780-mn4mv", "Normal", "Pulled", "kubelet", "1", `Successfully pulled image "blang/busybox-bash"`}, r.Fields[:7]) // } // func BenchmarkEventRender(b *testing.B) { // ev := load(b, "ev") // var re render.Event -// r := render.NewRow(7) +// r := model1.NewRow(7) // b.ResetTimer() // b.ReportAllocs() diff --git a/internal/render/generic.go b/internal/render/generic.go index fc89fdbefa..56f5523963 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -20,7 +21,7 @@ const ageTableCol = "Age" type Generic struct { Base table *metav1.Table - header Header + header model1.Header ageIndex int } @@ -35,38 +36,38 @@ func (g *Generic) SetTable(ns string, t *metav1.Table) { } // ColorerFunc colors a resource row. -func (*Generic) ColorerFunc() ColorerFunc { - return DefaultColorer +func (*Generic) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Header returns a header row. -func (g *Generic) Header(ns string) Header { +func (g *Generic) Header(ns string) model1.Header { if g.header != nil { return g.header } if g.table == nil { - return Header{} + return model1.Header{} } - h := make(Header, 0, len(g.table.ColumnDefinitions)) + h := make(model1.Header, 0, len(g.table.ColumnDefinitions)) if !client.IsClusterScoped(ns) { - h = append(h, HeaderColumn{Name: "NAMESPACE"}) + h = append(h, model1.HeaderColumn{Name: "NAMESPACE"}) } for i, c := range g.table.ColumnDefinitions { if c.Name == ageTableCol { g.ageIndex = i continue } - h = append(h, HeaderColumn{Name: strings.ToUpper(c.Name)}) + h = append(h, model1.HeaderColumn{Name: strings.ToUpper(c.Name)}) } if g.ageIndex > 0 { - h = append(h, HeaderColumn{Name: "AGE", Time: true}) + h = append(h, model1.HeaderColumn{Name: "AGE", Time: true}) } return h } // Render renders a K8s resource to screen. -func (g *Generic) Render(o interface{}, ns string, r *Row) error { +func (g *Generic) Render(o interface{}, ns string, r *model1.Row) error { row, ok := o.(metav1.TableRow) if !ok { return fmt.Errorf("expecting a TableRow but got %T", o) @@ -80,7 +81,7 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error { return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0]) } r.ID = client.FQN(nns, name) - r.Fields = make(Fields, 0, len(g.Header(ns))) + r.Fields = make(model1.Fields, 0, len(g.Header(ns))) if !client.IsClusterScoped(ns) { r.Fields = append(r.Fields, nns) } diff --git a/internal/render/generic_test.go b/internal/render/generic_test.go index 851aaa7290..a71807622d 100644 --- a/internal/render/generic_test.go +++ b/internal/render/generic_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" @@ -18,65 +19,65 @@ func TestGenericRender(t *testing.T) { ns string table *metav1beta1.Table eID string - eFields render.Fields - eHeader render.Header + eFields model1.Fields + eHeader model1.Header }{ "withNS": { ns: "ns1", table: makeNSGeneric(), eID: "ns1/fred", - eFields: render.Fields{"ns1", "c1", "c2", "c3"}, - eHeader: render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + eFields: model1.Fields{"ns1", "c1", "c2", "c3"}, + eHeader: model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, }, "all": { ns: client.NamespaceAll, table: makeNSGeneric(), eID: "ns1/fred", - eFields: render.Fields{"ns1", "c1", "c2", "c3"}, - eHeader: render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + eFields: model1.Fields{"ns1", "c1", "c2", "c3"}, + eHeader: model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, }, "allNS": { ns: client.NamespaceAll, table: makeNSGeneric(), eID: "ns1/fred", - eFields: render.Fields{"ns1", "c1", "c2", "c3"}, - eHeader: render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + eFields: model1.Fields{"ns1", "c1", "c2", "c3"}, + eHeader: model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, }, "clusterWide": { ns: client.ClusterScope, table: makeNoNSGeneric(), eID: "-/fred", - eFields: render.Fields{"c1", "c2", "c3"}, - eHeader: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, + eFields: model1.Fields{"c1", "c2", "c3"}, + eHeader: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, }, "age": { ns: client.ClusterScope, table: makeAgeGeneric(), eID: "-/fred", - eFields: render.Fields{"c1", "c2", "2d"}, - eHeader: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "AGE", Time: true}, + eFields: model1.Fields{"c1", "c2", "2d"}, + eHeader: model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "AGE", Time: true}, }, }, } @@ -85,7 +86,7 @@ func TestGenericRender(t *testing.T) { var re render.Generic u := uu[k] t.Run(k, func(t *testing.T) { - var r render.Row + var r model1.Row re.SetTable(u.ns, u.table) assert.Equal(t, u.eHeader, re.Header(u.ns)) diff --git a/internal/render/helm/chart.go b/internal/render/helm/chart.go index b41d51f0b5..ee17b98a2a 100644 --- a/internal/render/helm/chart.go +++ b/internal/render/helm/chart.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "helm.sh/helm/v3/pkg/release" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,33 +25,33 @@ func (Chart) IsGeneric() bool { } // ColorerFunc colors a resource row. -func (Chart) ColorerFunc() render.ColorerFunc { - return render.DefaultColorer +func (Chart) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Header returns a header row. -func (Chart) Header(_ string) render.Header { - return render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "NAME"}, - render.HeaderColumn{Name: "REVISION"}, - render.HeaderColumn{Name: "STATUS"}, - render.HeaderColumn{Name: "CHART"}, - render.HeaderColumn{Name: "APP VERSION"}, - render.HeaderColumn{Name: "VALID", Wide: true}, - render.HeaderColumn{Name: "AGE", Time: true}, +func (Chart) Header(_ string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "REVISION"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "CHART"}, + model1.HeaderColumn{Name: "APP VERSION"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a chart to screen. -func (c Chart) Render(o interface{}, ns string, r *render.Row) error { +func (c Chart) Render(o interface{}, ns string, r *model1.Row) error { h, ok := o.(ReleaseRes) if !ok { return fmt.Errorf("expected ReleaseRes, but got %T", o) } r.ID = client.FQN(h.Release.Namespace, h.Release.Name) - r.Fields = render.Fields{ + r.Fields = model1.Fields{ h.Release.Namespace, h.Release.Name, strconv.Itoa(h.Release.Version), diff --git a/internal/render/helm/history.go b/internal/render/helm/history.go index 5fbbaf6d09..cf0f118d33 100644 --- a/internal/render/helm/history.go +++ b/internal/render/helm/history.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" ) @@ -26,24 +27,24 @@ func (History) IsGeneric() bool { } // ColorerFunc colors a resource row. -func (History) ColorerFunc() render.ColorerFunc { - return render.DefaultColorer +func (History) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Header returns a header row. -func (History) Header(_ string) render.Header { - return render.Header{ - render.HeaderColumn{Name: "REVISION"}, - render.HeaderColumn{Name: "STATUS"}, - render.HeaderColumn{Name: "CHART"}, - render.HeaderColumn{Name: "APP VERSION"}, - render.HeaderColumn{Name: "DESCRIPTION"}, - render.HeaderColumn{Name: "VALID", Wide: true}, +func (History) Header(_ string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "REVISION"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "CHART"}, + model1.HeaderColumn{Name: "APP VERSION"}, + model1.HeaderColumn{Name: "DESCRIPTION"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, } } // Render renders a chart to screen. -func (c History) Render(o interface{}, ns string, r *render.Row) error { +func (c History) Render(o interface{}, ns string, r *model1.Row) error { h, ok := o.(ReleaseRes) if !ok { return fmt.Errorf("expected HistoryRes, but got %T", o) @@ -51,7 +52,7 @@ func (c History) Render(o interface{}, ns string, r *render.Row) error { r.ID = client.FQN(h.Release.Namespace, h.Release.Name) r.ID += ":" + strconv.Itoa(h.Release.Version) - r.Fields = render.Fields{ + r.Fields = model1.Fields{ strconv.Itoa(h.Release.Version), h.Release.Info.Status.String(), h.Release.Chart.Metadata.Name + "-" + h.Release.Chart.Metadata.Version, diff --git a/internal/render/helpers.go b/internal/render/helpers.go index bd474cdd41..522a6fdf71 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -5,7 +5,6 @@ package render import ( "context" - "math" "sort" "strconv" "strings" @@ -19,11 +18,21 @@ import ( "golang.org/x/text/language" "golang.org/x/text/message" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/duration" ) +// ExtractImages returns a collection of container images. +// !!BOZO!! If this has any legs?? enable scans on other container types. +func ExtractImages(spec *v1.PodSpec) []string { + ii := make([]string, 0, len(spec.Containers)) + for _, c := range spec.Containers { + ii = append(ii, c.Image) + } + + return ii +} + func computeVulScore(m metav1.ObjectMeta, spec *v1.PodSpec) string { if vul.ImgScanner == nil || vul.ImgScanner.ShouldExcludes(m) { return "0" @@ -46,62 +55,12 @@ func runesToNum(rr []rune) int64 { return r } -func durationToSeconds(duration string) int64 { - if len(duration) == 0 { - return 0 - } - if duration == NAValue { - return math.MaxInt64 - } - - num := make([]rune, 0, 5) - var n, m int64 - for _, r := range duration { - switch r { - case 'y': - m = 365 * 24 * 60 * 60 - case 'd': - m = 24 * 60 * 60 - case 'h': - m = 60 * 60 - case 'm': - m = 60 - case 's': - m = 1 - default: - num = append(num, r) - continue - } - n, num = n+runesToNum(num)*m, num[:0] - } - - return n -} - -func capacityToNumber(capacity string) int64 { - quantity := resource.MustParse(capacity) - return quantity.Value() -} - // AsThousands prints a number with thousand separator. func AsThousands(n int64) string { p := message.NewPrinter(language.English) return p.Sprintf("%d", n) } -// Happy returns true if resource is happy, false otherwise. -func Happy(ns string, h Header, r Row) bool { - if len(r.Fields) == 0 { - return true - } - validCol := h.IndexOf("VALID", true) - if validCol < 0 { - return true - } - - return strings.TrimSpace(r.Fields[validCol]) == "" -} - // AsStatus returns error as string. func AsStatus(err error) string { if err == nil { @@ -333,15 +292,15 @@ func strPtrToStr(s *string) string { return *s } -// Check if string is in a string list. -func in(ll []string, s string) bool { - for _, l := range ll { - if l == s { - return true - } - } - return false -} +// // Check if string is in a string list. +// func in(ll []string, s string) bool { +// for _, l := range ll { +// if l == s { +// return true +// } +// } +// return false +// } // Pad a string up to the given length or truncates if greater than length. func Pad(s string, width int) string { @@ -356,29 +315,29 @@ func Pad(s string, width int) string { return s + strings.Repeat(" ", width-len(s)) } -// Converts labels string to map. -func labelize(labels string) map[string]string { - ll := strings.Split(labels, ",") - data := make(map[string]string, len(ll)) - - for _, l := range ll { - tokens := strings.Split(l, "=") - if len(tokens) == 2 { - data[tokens[0]] = tokens[1] - } - } - - return data -} - -func sortLabels(m map[string]string) (keys, vals []string) { - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - vals = append(vals, m[k]) - } - - return -} +// // Converts labels string to map. +// func labelize(labels string) map[string]string { +// ll := strings.Split(labels, ",") +// data := make(map[string]string, len(ll)) + +// for _, l := range ll { +// tokens := strings.Split(l, "=") +// if len(tokens) == 2 { +// data[tokens[0]] = tokens[1] +// } +// } + +// return data +// } + +// func sortLabels(m map[string]string) (keys, vals []string) { +// for k := range m { +// keys = append(keys, k) +// } +// sort.Strings(keys) +// for _, k := range keys { +// vals = append(vals, m[k]) +// } + +// return +// } diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 05b0f89d6b..0c6d0787d2 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -4,91 +4,57 @@ package render import ( - "math" + "encoding/json" + "fmt" + "os" "testing" "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" ) -func TestSortLabels(t *testing.T) { - uu := map[string]struct { - labels string - e [][]string - }{ - "simple": { - labels: "a=b,c=d", - e: [][]string{ - {"a", "c"}, - {"b", "d"}, - }, +func TestTableGenericHydrate(t *testing.T) { + raw := raw(t, "p1") + tt := metav1beta1.Table{ + ColumnDefinitions: []metav1beta1.TableColumnDefinition{ + {Name: "c1"}, + {Name: "c2"}, }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - hh, vv := sortLabels(labelize(u.labels)) - assert.Equal(t, u.e[0], hh) - assert.Equal(t, u.e[1], vv) - }) - } -} - -func TestLabelize(t *testing.T) { - uu := map[string]struct { - labels string - e map[string]string - }{ - "simple": { - labels: "a=b,c=d", - e: map[string]string{"a": "b", "c": "d"}, + Rows: []metav1beta1.TableRow{ + { + Cells: []interface{}{"fred", 10}, + Object: runtime.RawExtension{Raw: raw}, + }, + { + Cells: []interface{}{"blee", 20}, + Object: runtime.RawExtension{Raw: raw}, + }, }, } + rr := make([]model1.Row, 2) + var re Generic + re.SetTable("blee", &tt) - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, labelize(u.labels)) - }) - } + assert.Nil(t, model1.GenericHydrate("blee", &tt, rr, &re)) + assert.Equal(t, 2, len(rr)) + assert.Equal(t, 3, len(rr[0].Fields)) } -func TestDurationToSecond(t *testing.T) { - uu := map[string]struct { - s string - e int64 - }{ - "seconds": {s: "22s", e: 22}, - "minutes": {s: "22m", e: 1320}, - "hours": {s: "12h", e: 43200}, - "days": {s: "3d", e: 259200}, - "day_hour": {s: "3d9h", e: 291600}, - "day_hour_minute": {s: "2d22h3m", e: 252180}, - "day_hour_minute_seconds": {s: "2d22h3m50s", e: 252230}, - "year": {s: "3y", e: 94608000}, - "year_day": {s: "1y2d", e: 31708800}, - "n/a": {s: NAValue, e: math.MaxInt64}, +func TestTableHydrate(t *testing.T) { + oo := []runtime.Object{ + &PodWithMetrics{Raw: load(t, "p1")}, } + rr := make([]model1.Row, 1) - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, durationToSeconds(u.s)) - }) - } -} - -func BenchmarkDurationToSecond(b *testing.B) { - t := "2d22h3m50s" - - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - durationToSeconds(t) - } + assert.Nil(t, model1.Hydrate("blee", oo, rr, Pod{})) + assert.Equal(t, 1, len(rr)) + assert.Equal(t, 23, len(rr[0].Fields)) } func TestToAge(t *testing.T) { @@ -308,34 +274,6 @@ func TestBlank(t *testing.T) { } } -func TestIn(t *testing.T) { - uu := map[string]struct { - a []string - v string - e bool - }{ - "in": { - a: []string{"fred", "blee"}, - v: "blee", - e: true, - }, - "empty": { - v: "blee", - }, - "missing": { - a: []string{"fred", "blee"}, - v: "duh", - }, - } - - for k := range uu { - uc := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, uc.e, in(uc.a, uc.v)) - }) - } -} - func TestMetaFQN(t *testing.T) { uu := map[string]struct { m metav1.ObjectMeta @@ -489,3 +427,20 @@ func BenchmarkIntToStr(b *testing.B) { IntToStr(v) } } + +// Helpers... + +func load(t *testing.T, n string) *unstructured.Unstructured { + raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n)) + assert.Nil(t, err) + var o unstructured.Unstructured + err = json.Unmarshal(raw, &o) + assert.Nil(t, err) + return &o +} + +func raw(t *testing.T, n string) []byte { + raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.json", n)) + assert.Nil(t, err) + return raw +} diff --git a/internal/render/img_scan.go b/internal/render/img_scan.go index 03ab3c2eb9..691e2f104a 100644 --- a/internal/render/img_scan.go +++ b/internal/render/img_scan.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/vul" "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" @@ -24,15 +25,15 @@ type ImageScan struct { } // ColorerFunc colors a resource row. -func (c ImageScan) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) +func (c ImageScan) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) - sevCol := h.IndexOf(sevColName, true) - if sevCol == -1 { + idx, ok := h.IndexOf(sevColName, true) + if !ok { return c } - sev := strings.TrimSpace(re.Row.Fields[sevCol]) + sev := strings.TrimSpace(re.Row.Fields[idx]) switch sev { case vul.Sev1: c = tcell.ColorRed @@ -54,27 +55,27 @@ func (c ImageScan) ColorerFunc() ColorerFunc { } // Header returns a header row. -func (ImageScan) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "SEVERITY"}, - HeaderColumn{Name: "VULNERABILITY"}, - HeaderColumn{Name: "IMAGE"}, - HeaderColumn{Name: "LIBRARY"}, - HeaderColumn{Name: "VERSION"}, - HeaderColumn{Name: "FIXED-IN"}, - HeaderColumn{Name: "TYPE"}, +func (ImageScan) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "SEVERITY"}, + model1.HeaderColumn{Name: "VULNERABILITY"}, + model1.HeaderColumn{Name: "IMAGE"}, + model1.HeaderColumn{Name: "LIBRARY"}, + model1.HeaderColumn{Name: "VERSION"}, + model1.HeaderColumn{Name: "FIXED-IN"}, + model1.HeaderColumn{Name: "TYPE"}, } } // Render renders a K8s resource to screen. -func (is ImageScan) Render(o interface{}, name string, r *Row) error { +func (is ImageScan) Render(o interface{}, name string, r *model1.Row) error { res, ok := o.(ImageScanRes) if !ok { return fmt.Errorf("expected ImageScanRes, but got %T", o) } r.ID = fmt.Sprintf("%s|%s", res.Image, strings.Join(res.Row, "|")) - r.Fields = Fields{ + r.Fields = model1.Fields{ res.Row.Severity(), res.Row.Vulnerability(), res.Image, diff --git a/internal/render/job.go b/internal/render/job.go index 89298d314d..451f811ca6 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -10,6 +10,7 @@ import ( "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -24,25 +25,23 @@ type Job struct { } // Header returns a header row. -func (Job) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "COMPLETIONS"}, - HeaderColumn{Name: "DURATION"}, - HeaderColumn{Name: "SELECTOR", Wide: true}, - HeaderColumn{Name: "CONTAINERS", Wide: true}, - HeaderColumn{Name: "IMAGES", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Job) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "COMPLETIONS"}, + model1.HeaderColumn{Name: "DURATION"}, + model1.HeaderColumn{Name: "SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "CONTAINERS", Wide: true}, + model1.HeaderColumn{Name: "IMAGES", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h } // Render renders a K8s resource to screen. -func (j Job) Render(o interface{}, ns string, r *Row) error { +func (j Job) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Job, but got %T", o) @@ -57,7 +56,7 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { cc, ii := toContainers(job.Spec.Template.Spec) r.ID = client.MetaFQN(job.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ job.Namespace, job.Name, computeVulScore(job.ObjectMeta, &job.Spec.Template.Spec), diff --git a/internal/render/job_test.go b/internal/render/job_test.go index b26179966a..028a4ddfe4 100644 --- a/internal/render/job_test.go +++ b/internal/render/job_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestJobRender(t *testing.T) { c := render.Job{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "job"), "", &r)) assert.Equal(t, "default/hello-1567179180", r.ID) - assert.Equal(t, render.Fields{"default", "hello-1567179180", "0", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"default", "hello-1567179180", "0", "1/1", "8s", "controller-uid=7473e6d0-cb3b-11e9-990f-42010a800218", "c1", "blang/busybox-bash"}, r.Fields[:8]) } diff --git a/internal/render/node.go b/internal/render/node.go index 96b935811b..4a43c43e11 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -30,32 +31,32 @@ type Node struct { } // Header returns a header row. -func (Node) Header(_ string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "ROLE"}, - HeaderColumn{Name: "ARCH", Wide: true}, - HeaderColumn{Name: "TAINTS"}, - HeaderColumn{Name: "VERSION"}, - HeaderColumn{Name: "KERNEL", Wide: true}, - HeaderColumn{Name: "INTERNAL-IP", Wide: true}, - HeaderColumn{Name: "EXTERNAL-IP", Wide: true}, - HeaderColumn{Name: "PODS", Align: tview.AlignRight}, - HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%CPU", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%MEM", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "CPU/A", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "MEM/A", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Node) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "ROLE"}, + model1.HeaderColumn{Name: "ARCH", Wide: true}, + model1.HeaderColumn{Name: "TAINTS"}, + model1.HeaderColumn{Name: "VERSION"}, + model1.HeaderColumn{Name: "KERNEL", Wide: true}, + model1.HeaderColumn{Name: "INTERNAL-IP", Wide: true}, + model1.HeaderColumn{Name: "EXTERNAL-IP", Wide: true}, + model1.HeaderColumn{Name: "PODS", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%CPU", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%MEM", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "CPU/A", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "MEM/A", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (n Node) Render(o interface{}, ns string, r *Row) error { +func (n Node) Render(o interface{}, ns string, r *model1.Row) error { oo, ok := o.(*NodeWithMetrics) if !ok { return fmt.Errorf("expected *NodeAndMetrics, but got %T", o) @@ -87,7 +88,7 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { podCount = NAValue } r.ID = client.FQN("", na) - r.Fields = Fields{ + r.Fields = model1.Fields{ no.Name, join(statuses, ","), join(roles, ","), diff --git a/internal/render/node_test.go b/internal/render/node_test.go index a276f06b72..09fb4a6889 100644 --- a/internal/render/node_test.go +++ b/internal/render/node_test.go @@ -6,6 +6,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,12 +20,12 @@ func TestNodeRender(t *testing.T) { } var no render.Node - r := render.NewRow(14) + r := model1.NewRow(14) err := no.Render(&pom, "", &r) assert.Nil(t, err) assert.Equal(t, "minikube", r.ID) - e := render.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} + e := model1.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} assert.Equal(t, e, r.Fields[:16]) } @@ -34,7 +35,7 @@ func BenchmarkNodeRender(b *testing.B) { MX: makeNodeMX("n1", "10m", "10Mi"), } var no render.Node - r := render.NewRow(14) + r := model1.NewRow(14) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { diff --git a/internal/render/np.go b/internal/render/np.go index 26333aebfe..8f7bb24262 100644 --- a/internal/render/np.go +++ b/internal/render/np.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -20,24 +21,24 @@ type NetworkPolicy struct { } // Header returns a header row. -func (NetworkPolicy) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "ING-SELECTOR", Wide: true}, - HeaderColumn{Name: "ING-PORTS"}, - HeaderColumn{Name: "ING-BLOCK"}, - HeaderColumn{Name: "EGR-SELECTOR", Wide: true}, - HeaderColumn{Name: "EGR-PORTS"}, - HeaderColumn{Name: "EGR-BLOCK"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (NetworkPolicy) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "ING-SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "ING-PORTS"}, + model1.HeaderColumn{Name: "ING-BLOCK"}, + model1.HeaderColumn{Name: "EGR-SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "EGR-PORTS"}, + model1.HeaderColumn{Name: "EGR-BLOCK"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { +func (n NetworkPolicy) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected NetworkPolicy, but got %T", o) @@ -52,7 +53,7 @@ func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { ep, es, eb := egress(np.Spec.Egress) r.ID = client.MetaFQN(np.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ np.Namespace, np.Name, is, diff --git a/internal/render/np_test.go b/internal/render/np_test.go index bd3412f205..bd371df453 100644 --- a/internal/render/np_test.go +++ b/internal/render/np_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestNetworkPolicyRender(t *testing.T) { c := render.NetworkPolicy{} - r := render.NewRow(9) + r := model1.NewRow(9) assert.NoError(t, c.Render(load(t, "np"), "", &r)) assert.Equal(t, "default/fred", r.ID) - assert.Equal(t, render.Fields{"default", "fred", "ns:app=blee,po:app=fred", "TCP:6379", "172.17.0.0/16[172.17.1.0/24,172.17.3.0/24...]", "", "TCP:5978", "10.0.0.0/24"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"default", "fred", "ns:app=blee,po:app=fred", "TCP:6379", "172.17.0.0/16[172.17.1.0/24,172.17.3.0/24...]", "", "TCP:5978", "10.0.0.0/24"}, r.Fields[:8]) } diff --git a/internal/render/ns.go b/internal/render/ns.go index 77954a5eda..4f9ecf81f6 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,19 +22,17 @@ type Namespace struct { } // ColorerFunc colors a resource row. -func (n Namespace) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) - - if re.Kind == EventUpdate { - c = StdColor +func (n Namespace) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) + if c == model1.ErrColor { + return c } - if strings.Contains(strings.TrimSpace(re.Row.Fields[0]), "*") { - c = HighlightColor + if re.Kind == model1.EventUpdate { + c = model1.StdColor } - - if !Happy(ns, h, re.Row) { - c = ErrColor + if strings.Contains(strings.TrimSpace(re.Row.Fields[0]), "*") { + c = model1.HighlightColor } return c @@ -41,18 +40,18 @@ func (n Namespace) ColorerFunc() ColorerFunc { } // Header returns a header rbw. -func (Namespace) Header(string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Namespace) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (n Namespace) Render(o interface{}, _ string, r *Row) error { +func (n Namespace) Render(o interface{}, _ string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Namespace, but got %T", o) @@ -64,7 +63,7 @@ func (n Namespace) Render(o interface{}, _ string, r *Row) error { } r.ID = client.MetaFQN(ns.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ ns.Name, string(ns.Status.Phase), mapToStr(ns.Labels), diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go index 81ca793e3d..ad4337bd7d 100644 --- a/internal/render/ns_test.go +++ b/internal/render/ns_test.go @@ -6,6 +6,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" @@ -13,66 +14,66 @@ import ( func TestNSColorer(t *testing.T) { uu := map[string]struct { - re render.RowEvent + re model1.RowEvent e tcell.Color }{ "add": { - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{ + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{ "blee", "Active", }, }, }, - e: render.AddColor, + e: model1.AddColor, }, "update": { - re: render.RowEvent{ - Kind: render.EventUpdate, - Row: render.Row{ - Fields: render.Fields{ + re: model1.RowEvent{ + Kind: model1.EventUpdate, + Row: model1.Row{ + Fields: model1.Fields{ "blee", "Active", }, }, }, - e: render.StdColor, + e: model1.StdColor, }, "decorator": { - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{ + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{ "blee*", "Active", }, }, }, - e: render.HighlightColor, + e: model1.HighlightColor, }, } - h := render.Header{ - render.HeaderColumn{Name: "NAME"}, - render.HeaderColumn{Name: "STATUS"}, + h := model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, } var r render.Namespace for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, r.ColorerFunc()("", h, u.re)) + assert.Equal(t, u.e, r.ColorerFunc()("", h, &u.re)) }) } } func TestNamespaceRender(t *testing.T) { c := render.Namespace{} - r := render.NewRow(3) + r := model1.NewRow(3) assert.NoError(t, c.Render(load(t, "ns"), "-", &r)) assert.Equal(t, "-/kube-system", r.ID) - assert.Equal(t, render.Fields{"kube-system", "Active"}, r.Fields[:2]) + assert.Equal(t, model1.Fields{"kube-system", "Active"}, r.Fields[:2]) } diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 3b29962d56..656b49b8bd 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" v1beta1 "k8s.io/api/policy/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,24 +22,24 @@ type PodDisruptionBudget struct { } // Header returns a header row. -func (PodDisruptionBudget) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "MIN AVAILABLE", Align: tview.AlignRight}, - HeaderColumn{Name: "MAX UNAVAILABLE", Align: tview.AlignRight}, - HeaderColumn{Name: "ALLOWED DISRUPTIONS", Align: tview.AlignRight}, - HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, - HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, - HeaderColumn{Name: "EXPECTED", Align: tview.AlignRight}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (PodDisruptionBudget) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "MIN AVAILABLE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "MAX UNAVAILABLE", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "ALLOWED DISRUPTIONS", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "EXPECTED", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { +func (p PodDisruptionBudget) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected PodDisruptionBudget, but got %T", o) @@ -50,7 +51,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(pdb.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ pdb.Namespace, pdb.Name, numbToStr(pdb.Spec.MinAvailable), diff --git a/internal/render/pdb_test.go b/internal/render/pdb_test.go index 2f40acaedb..9567c487f8 100644 --- a/internal/render/pdb_test.go +++ b/internal/render/pdb_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestPodDisruptionBudgetRender(t *testing.T) { c := render.PodDisruptionBudget{} - r := render.NewRow(9) + r := model1.NewRow(9) assert.NoError(t, c.Render(load(t, "pdb"), "", &r)) assert.Equal(t, "default/fred", r.ID) - assert.Equal(t, render.Fields{"default", "fred", "2", render.NAValue, "0", "0", "2", "0"}, r.Fields[:8]) + assert.Equal(t, model1.Fields{"default", "fred", "2", render.NAValue, "0", "0", "2", "0"}, r.Fields[:8]) } diff --git a/internal/render/pod.go b/internal/render/pod.go index 710bd6eefd..829834df5a 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -18,6 +18,7 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" ) const ( @@ -51,84 +52,67 @@ type Pod struct { } // ColorerFunc colors a resource row. -func (p Pod) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) +func (p Pod) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) - statusCol := h.IndexOf("STATUS", true) - if statusCol == -1 { + idx, ok := h.IndexOf("STATUS", true) + if !ok { return c } - status := strings.TrimSpace(re.Row.Fields[statusCol]) + status := strings.TrimSpace(re.Row.Fields[idx]) switch status { case Pending, ContainerCreating: - c = PendingColor + c = model1.PendingColor case PodInitializing: - c = AddColor + c = model1.AddColor case Initialized: - c = HighlightColor + c = model1.HighlightColor case Completed: - c = CompletedColor + c = model1.CompletedColor case Running: - c = StdColor - if !Happy(ns, h, re.Row) { - c = ErrColor + if c != model1.ErrColor { + c = model1.StdColor } case Terminating: - c = KillColor - default: - if !Happy(ns, h, re.Row) { - c = ErrColor - } + c = model1.KillColor } + return c } } // Header returns a header row. -func (Pod) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "PF"}, - HeaderColumn{Name: "READY"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, - HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight, Wide: true}, - HeaderColumn{Name: "MEM/R:L", Align: tview.AlignRight, Wide: true}, - HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, - HeaderColumn{Name: "IP"}, - HeaderColumn{Name: "NODE"}, - HeaderColumn{Name: "NOMINATED NODE", Wide: true}, - HeaderColumn{Name: "READINESS GATES", Wide: true}, - HeaderColumn{Name: "QOS", Wide: true}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (p Pod) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "PF"}, + model1.HeaderColumn{Name: "READY"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight, Wide: true}, + model1.HeaderColumn{Name: "MEM/R:L", Align: tview.AlignRight, Wide: true}, + model1.HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%CPU/L", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%MEM/R", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "%MEM/L", Align: tview.AlignRight, MX: true}, + model1.HeaderColumn{Name: "IP"}, + model1.HeaderColumn{Name: "NODE"}, + model1.HeaderColumn{Name: "NOMINATED NODE", Wide: true}, + model1.HeaderColumn{Name: "READINESS GATES", Wide: true}, + model1.HeaderColumn{Name: "QOS", Wide: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h -} - -// ExtractImages returns a collection of container images. -// !!BOZO!! If this has any legs?? enable scans on other container types. -func ExtractImages(spec *v1.PodSpec) []string { - ii := make([]string, 0, len(spec.Containers)) - for _, c := range spec.Containers { - ii = append(ii, c.Image) - } - - return ii } // Render renders a K8s resource to screen. -func (p Pod) Render(o interface{}, ns string, row *Row) error { +func (p Pod) Render(o interface{}, ns string, row *model1.Row) error { pwm, ok := o.(*PodWithMetrics) if !ok { return fmt.Errorf("expected PodWithMetrics, but got %T", o) @@ -151,9 +135,10 @@ func (p Pod) Render(o interface{}, ns string, row *Row) error { c, r := gatherCoMX(po.Spec.Containers, ccmx) phase := p.Phase(&po) row.ID = client.MetaFQN(po.ObjectMeta) - row.Fields = Fields{ + + row.Fields = model1.Fields{ po.Namespace, - po.ObjectMeta.Name, + po.Name, computeVulScore(po.ObjectMeta, &po.Spec), "●", strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)), diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index ec2e058a78..57fe6c4da8 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -6,6 +6,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" @@ -16,128 +17,128 @@ import ( ) func init() { - render.AddColor = tcell.ColorBlue - render.HighlightColor = tcell.ColorYellow - render.CompletedColor = tcell.ColorGray - render.StdColor = tcell.ColorWhite - render.ErrColor = tcell.ColorRed - render.KillColor = tcell.ColorGray + model1.AddColor = tcell.ColorBlue + model1.HighlightColor = tcell.ColorYellow + model1.CompletedColor = tcell.ColorGray + model1.StdColor = tcell.ColorWhite + model1.ErrColor = tcell.ColorRed + model1.KillColor = tcell.ColorGray } func TestPodColorer(t *testing.T) { - stdHeader := render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "NAME"}, - render.HeaderColumn{Name: "READY"}, - render.HeaderColumn{Name: "RESTARTS"}, - render.HeaderColumn{Name: "STATUS"}, - render.HeaderColumn{Name: "VALID"}, + stdHeader := model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "READY"}, + model1.HeaderColumn{Name: "RESTARTS"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "VALID"}, } uu := map[string]struct { - re render.RowEvent - h render.Header + re model1.RowEvent + h model1.Header e tcell.Color }{ "valid": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.Running, ""}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.Running, ""}, }, }, - e: render.StdColor, + e: model1.StdColor, }, "init": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, ""}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, ""}, }, }, - e: render.AddColor, + e: model1.AddColor, }, "init-err": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, "blah"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, "blah"}, }, }, - e: render.AddColor, + e: model1.AddColor, }, "initialized": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.Initialized, "blah"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.Initialized, "blah"}, }, }, - e: render.HighlightColor, + e: model1.HighlightColor, }, "completed": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.Completed, "blah"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.Completed, "blah"}, }, }, - e: render.CompletedColor, + e: model1.CompletedColor, }, "terminating": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", render.Terminating, "blah"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", render.Terminating, "blah"}, }, }, - e: render.KillColor, + e: model1.KillColor, }, "invalid": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", "Running", "blah"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", "Running", "blah"}, }, }, - e: render.ErrColor, + e: model1.ErrColor, }, "unknown-cool": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", ""}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", "blee", ""}, }, }, - e: render.AddColor, + e: model1.AddColor, }, "unknown-err": { h: stdHeader, - re: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", "doh"}, + re: model1.RowEvent{ + Kind: model1.EventAdd, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", "blee", "doh"}, }, }, - e: render.ErrColor, + e: model1.ErrColor, }, "status": { h: stdHeader[0:3], - re: render.RowEvent{ - Kind: render.EventDelete, - Row: render.Row{ - Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", ""}, + re: model1.RowEvent{ + Kind: model1.EventDelete, + Row: model1.Row{ + Fields: model1.Fields{"blee", "fred", "1/1", "0", "blee", ""}, }, }, - e: render.KillColor, + e: model1.KillColor, }, } @@ -145,7 +146,7 @@ func TestPodColorer(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, r.ColorerFunc()("", u.h, u.re)) + assert.Equal(t, u.e, r.ColorerFunc()("", u.h, &u.re)) }) } } @@ -157,12 +158,12 @@ func TestPodRender(t *testing.T) { } var po render.Pod - r := render.NewRow(14) + r := model1.NewRow(14) err := po.Render(&pom, "", &r) assert.Nil(t, err) assert.Equal(t, "default/nginx", r.ID) - e := render.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "", ""} + e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "", ""} assert.Equal(t, e, r.Fields[:19]) } @@ -172,7 +173,7 @@ func BenchmarkPodRender(b *testing.B) { MX: makePodMX("nginx", "10m", "10Mi"), } var po render.Pod - r := render.NewRow(12) + r := model1.NewRow(12) b.ReportAllocs() b.ResetTimer() @@ -188,12 +189,12 @@ func TestPodInitRender(t *testing.T) { } var po render.Pod - r := render.NewRow(14) + r := model1.NewRow(14) err := po.Render(&pom, "", &r) assert.Nil(t, err) assert.Equal(t, "default/nginx", r.ID) - e := render.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "", ""} + e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "", ""} assert.Equal(t, e, r.Fields[:19]) } diff --git a/internal/render/policy.go b/internal/render/policy.go index e750bcb0fd..777ecfaafc 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -7,23 +7,24 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) -func rbacVerbHeader() Header { - return Header{ - HeaderColumn{Name: "GET "}, - HeaderColumn{Name: "LIST "}, - HeaderColumn{Name: "WATCH "}, - HeaderColumn{Name: "CREATE"}, - HeaderColumn{Name: "PATCH "}, - HeaderColumn{Name: "UPDATE"}, - HeaderColumn{Name: "DELETE"}, - HeaderColumn{Name: "DEL-LIST "}, - HeaderColumn{Name: "EXTRAS", Wide: true}, +func rbacVerbHeader() model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "GET "}, + model1.HeaderColumn{Name: "LIST "}, + model1.HeaderColumn{Name: "WATCH "}, + model1.HeaderColumn{Name: "CREATE"}, + model1.HeaderColumn{Name: "PATCH "}, + model1.HeaderColumn{Name: "UPDATE"}, + model1.HeaderColumn{Name: "DELETE"}, + model1.HeaderColumn{Name: "DEL-LIST "}, + model1.HeaderColumn{Name: "EXTRAS", Wide: true}, } } @@ -33,28 +34,28 @@ type Policy struct { } // ColorerFunc colors a resource row. -func (Policy) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (Policy) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorMediumSpringGreen } } // Header returns a header row. -func (Policy) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "API-GROUP"}, - HeaderColumn{Name: "BINDING"}, +func (Policy) Header(ns string) model1.Header { + h := model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "API-GROUP"}, + model1.HeaderColumn{Name: "BINDING"}, } h = append(h, rbacVerbHeader()...) - h = append(h, HeaderColumn{Name: "VALID", Wide: true}) + h = append(h, model1.HeaderColumn{Name: "VALID", Wide: true}) return h } // Render renders a K8s resource to screen. -func (Policy) Render(o interface{}, gvr string, r *Row) error { +func (Policy) Render(o interface{}, gvr string, r *model1.Row) error { p, ok := o.(PolicyRes) if !ok { return fmt.Errorf("expecting PolicyRes but got %T", o) diff --git a/internal/render/policy_test.go b/internal/render/policy_test.go index 536425fb3d..b0770d14dc 100644 --- a/internal/render/policy_test.go +++ b/internal/render/policy_test.go @@ -7,6 +7,7 @@ import ( "errors" "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) @@ -46,7 +47,7 @@ func TestPolicyResMerge(t *testing.T) { func TestPolicyRender(t *testing.T) { var p render.Policy - var r render.Row + var r model1.Row o := render.PolicyRes{ Namespace: "blee", Binding: "fred", @@ -59,7 +60,7 @@ func TestPolicyRender(t *testing.T) { assert.Nil(t, p.Render(o, "fred", &r)) assert.Equal(t, "blee/res", r.ID) - assert.Equal(t, render.Fields{ + assert.Equal(t, model1.Fields{ "blee", "res", "grp", diff --git a/internal/render/popeye.go b/internal/render/popeye.go index c8dd757d54..e43df218c6 100644 --- a/internal/render/popeye.go +++ b/internal/render/popeye.go @@ -3,92 +3,82 @@ package render -import ( - "fmt" - "math" - "strconv" - "strings" - - "github.com/derailed/k9s/internal/client" - "github.com/derailed/popeye/pkg/config" - "github.com/derailed/tcell/v2" - "github.com/derailed/tview" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// Popeye renders a sanitizer to screen. -type Popeye struct { - Base -} - -// ColorerFunc colors a resource row. -func (Popeye) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) - - warnCol := h.IndexOf("WARNING", true) - status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol])) - if status > 0 { - c = tcell.ColorOrange - } - errCol := h.IndexOf("ERROR", true) - status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol])) - if status > 0 { - c = ErrColor - } - return c - } -} - -// Header returns a header row. -func (Popeye) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "RESOURCE"}, - HeaderColumn{Name: "SCORE%", Align: tview.AlignRight}, - HeaderColumn{Name: "SCANNED", Align: tview.AlignRight}, - HeaderColumn{Name: "ERROR", Align: tview.AlignRight}, - HeaderColumn{Name: "WARNING", Align: tview.AlignRight}, - HeaderColumn{Name: "INFO", Align: tview.AlignRight}, - HeaderColumn{Name: "OK", Align: tview.AlignRight}, - } -} - -// Render renders a K8s resource to screen. -func (Popeye) Render(o interface{}, ns string, r *Row) error { - s, ok := o.(Section) - if !ok { - return fmt.Errorf("expected Section, but got %T", o) - } - - r.ID = client.FQN(ns, s.Title) - r.Fields = append(r.Fields, - s.Title, - strconv.Itoa(s.Tally.Score()), - strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error), - strconv.Itoa(s.Tally.Error), - strconv.Itoa(s.Tally.Warning), - strconv.Itoa(s.Tally.Info), - strconv.Itoa(s.Tally.OK), - ) - return nil -} - -// ---------------------------------------------------------------------------- -// Helpers... +import "github.com/derailed/popeye/pkg/config" + +// !!BOZO!! Popeye + +// // Popeye renders a sanitizer to screen. +// type Popeye struct { +// Base +// } + +// // ColorerFunc colors a resource row. +// func (Popeye) ColorerFunc() ColorerFunc { +// return func(ns string, h Header, re *model1.RowEvent) tcell.Color { +// c := DefaultColorer(ns, h, re) + +// warnCol := h.IndexOf("WARNING", true) +// status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol])) +// if status > 0 { +// c = tcell.ColorOrange +// } +// errCol := h.IndexOf("ERROR", true) +// status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol])) +// if status > 0 { +// c = ErrColor +// } +// return c +// } +// } + +// // Header returns a header row. +// func (Popeye) Header(ns string) model1.Header { +// return model1.Header{ +// model1.HeaderColumn{Name: "RESOURCE"}, +// model1.HeaderColumn{Name: "SCORE%", Align: tview.AlignRight}, +// model1.HeaderColumn{Name: "SCANNED", Align: tview.AlignRight}, +// model1.HeaderColumn{Name: "ERROR", Align: tview.AlignRight}, +// model1.HeaderColumn{Name: "WARNING", Align: tview.AlignRight}, +// model1.HeaderColumn{Name: "INFO", Align: tview.AlignRight}, +// model1.HeaderColumn{Name: "OK", Align: tview.AlignRight}, +// } +// } + +// // Render renders a K8s resource to screen. +// func (Popeye) Render(o interface{}, ns string, r *model1.Row) error { +// s, ok := o.(Section) +// if !ok { +// return fmt.Errorf("expected Section, but got %T", o) +// } + +// r.ID = client.FQN(ns, s.Title) +// r.Fields = append(r.Fields, +// s.Title, +// strconv.Itoa(s.Tally.Score()), +// strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error), +// strconv.Itoa(s.Tally.Error), +// strconv.Itoa(s.Tally.Warning), +// strconv.Itoa(s.Tally.Info), +// strconv.Itoa(s.Tally.OK), +// ) +// return nil +// } + +// // ---------------------------------------------------------------------------- +// // Helpers... type ( - // Builder represents a popeye report. - Builder struct { - Report Report `json:"popeye" yaml:"popeye"` - } - - // Report represents the output of a sanitization pass. - Report struct { - Score int `json:"score" yaml:"score"` - Grade string `json:"grade" yaml:"grade"` - Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"` - } + // // Builder represents a popeye report. + // Builder struct { + // Report Report `json:"popeye" yaml:"popeye"` + // } + + // // Report represents the output of a sanitization pass. + // Report struct { + // Score int `json:"score" yaml:"score"` + // Grade string `json:"grade" yaml:"grade"` + // Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"` + // } // Sections represents a collection of sections. Sections []Section @@ -116,89 +106,90 @@ type ( } // Tally tracks a section scores. + Tally struct { OK, Info, Warning, Error int Count int } ) -// Sum sums up tally counts. -func (t *Tally) Sum() int { - return t.OK + t.Info + t.Warning + t.Error -} - -// Score returns the overall sections score in percent. -func (t *Tally) Score() int { - oks := t.OK + t.Info - return toPerc(float64(oks), float64(oks+t.Warning+t.Error)) -} - -func toPerc(v1, v2 float64) int { - if v2 == 0 { - return 0 - } - return int(math.Floor((v1 / v2) * 100)) -} - -// Len returns a section length. -func (s Sections) Len() int { - return len(s) -} - -// Swap swaps values. -func (s Sections) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Less compares section scores. -func (s Sections) Less(i, j int) bool { - t1, t2 := s[i].Tally, s[j].Tally - return t1.Score() < t2.Score() -} - -// GetObjectKind returns a schema object. -func (Section) GetObjectKind() schema.ObjectKind { - return nil -} - -// DeepCopyObject returns a container copy. -func (s Section) DeepCopyObject() runtime.Object { - return s -} - -// MaxSeverity gather the max severity in a collection of issues. -func (s Section) MaxSeverity() config.Level { - max := config.OkLevel - for _, issues := range s.Outcome { - m := issues.MaxSeverity() - if m > max { - max = m - } - } - - return max -} - -// MaxSeverity gather the max severity in a collection of issues. -func (i Issues) MaxSeverity() config.Level { - max := config.OkLevel - for _, is := range i { - if is.Level > max { - max = is.Level - } - } - - return max -} - -// CountSeverity counts severity level instances. -func (i Issues) CountSeverity(l config.Level) int { - var count int - for _, is := range i { - if is.Level == l { - count++ - } - } - - return count -} +// // Sum sums up tally counts. +// func (t *Tally) Sum() int { +// return t.OK + t.Info + t.Warning + t.Error +// } + +// // Score returns the overall sections score in percent. +// func (t *Tally) Score() int { +// oks := t.OK + t.Info +// return toPerc(float64(oks), float64(oks+t.Warning+t.Error)) +// } + +// func toPerc(v1, v2 float64) int { +// if v2 == 0 { +// return 0 +// } +// return int(math.Floor((v1 / v2) * 100)) +// } + +// // Len returns a section length. +// func (s Sections) Len() int { +// return len(s) +// } + +// // Swap swaps values. +// func (s Sections) Swap(i, j int) { +// s[i], s[j] = s[j], s[i] +// } + +// // Less compares section scores. +// func (s Sections) Less(i, j int) bool { +// t1, t2 := s[i].Tally, s[j].Tally +// return t1.Score() < t2.Score() +// } + +// // GetObjectKind returns a schema object. +// func (Section) GetObjectKind() schema.ObjectKind { +// return nil +// } + +// // DeepCopyObject returns a container copy. +// func (s Section) DeepCopyObject() runtime.Object { +// return s +// } + +// // MaxSeverity gather the max severity in a collection of issues. +// func (s Section) MaxSeverity() config.Level { +// max := config.OkLevel +// for _, issues := range s.Outcome { +// m := issues.MaxSeverity() +// if m > max { +// max = m +// } +// } + +// return max +// } + +// // MaxSeverity gather the max severity in a collection of issues. +// func (i Issues) MaxSeverity() config.Level { +// max := config.OkLevel +// for _, is := range i { +// if is.Level > max { +// max = is.Level +// } +// } + +// return max +// } + +// // CountSeverity counts severity level instances. +// func (i Issues) CountSeverity(l config.Level) int { +// var count int +// for _, is := range i { +// if is.Level == l { +// count++ +// } +// } + +// return count +// } diff --git a/internal/render/port_forward_test.go b/internal/render/port_forward_test.go index c3d0d9c226..6c4cd18819 100644 --- a/internal/render/port_forward_test.go +++ b/internal/render/port_forward_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) @@ -23,10 +24,10 @@ func TestPortForwardRender(t *testing.T) { } var p render.PortForward - var r render.Row + var r model1.Row assert.Nil(t, p.Render(o, "fred", &r)) assert.Equal(t, "blee/fred", r.ID) - assert.Equal(t, render.Fields{ + assert.Equal(t, model1.Fields{ "blee", "fred", "co", diff --git a/internal/render/portforward.go b/internal/render/portforward.go index 5ae2f71eee..267be33cf6 100644 --- a/internal/render/portforward.go +++ b/internal/render/portforward.go @@ -9,6 +9,7 @@ import ( "time" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -39,29 +40,29 @@ type PortForward struct { } // ColorerFunc colors a resource row. -func (PortForward) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (PortForward) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorSkyblue } } // Header returns a header row. -func (PortForward) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "CONTAINER"}, - HeaderColumn{Name: "PORTS"}, - HeaderColumn{Name: "URL"}, - HeaderColumn{Name: "C"}, - HeaderColumn{Name: "N"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (PortForward) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "CONTAINER"}, + model1.HeaderColumn{Name: "PORTS"}, + model1.HeaderColumn{Name: "URL"}, + model1.HeaderColumn{Name: "C"}, + model1.HeaderColumn{Name: "N"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (f PortForward) Render(o interface{}, gvr string, r *Row) error { +func (f PortForward) Render(o interface{}, gvr string, r *model1.Row) error { pf, ok := o.(ForwardRes) if !ok { return fmt.Errorf("expecting a ForwardRes but got %T", o) @@ -71,7 +72,7 @@ func (f PortForward) Render(o interface{}, gvr string, r *Row) error { r.ID = pf.ID() ns, n := client.Namespaced(r.ID) - r.Fields = Fields{ + r.Fields = model1.Fields{ ns, trimContainer(n), pf.Container(), diff --git a/internal/render/pv.go b/internal/render/pv.go index d91ea18b6e..9e42893725 100644 --- a/internal/render/pv.go +++ b/internal/render/pv.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -23,51 +24,49 @@ type PersistentVolume struct { } // ColorerFunc colors a resource row. -func (p PersistentVolume) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { - return ErrColor - } +func (p PersistentVolume) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) - statusCol := h.IndexOf("STATUS", true) - if statusCol == -1 { - return DefaultColorer(ns, h, re) + idx, ok := h.IndexOf("STATUS", true) + if ok { + return c } - switch strings.TrimSpace(re.Row.Fields[statusCol]) { + switch strings.TrimSpace(re.Row.Fields[idx]) { case string(v1.VolumeBound): - return StdColor + return model1.StdColor case string(v1.VolumeAvailable): return tcell.ColorGreen case string(v1.VolumePending): - return PendingColor + return model1.PendingColor case terminatingPhase: - return CompletedColor + return model1.CompletedColor } - return DefaultColorer(ns, h, re) + return c } } // Header returns a header rbw. -func (PersistentVolume) Header(string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "CAPACITY", Capacity: true}, - HeaderColumn{Name: "ACCESS MODES"}, - HeaderColumn{Name: "RECLAIM POLICY"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "CLAIM"}, - HeaderColumn{Name: "STORAGECLASS"}, - HeaderColumn{Name: "REASON"}, - HeaderColumn{Name: "VOLUMEMODE", Wide: true}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (PersistentVolume) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "CAPACITY", Capacity: true}, + model1.HeaderColumn{Name: "ACCESS MODES"}, + model1.HeaderColumn{Name: "RECLAIM POLICY"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "CLAIM"}, + model1.HeaderColumn{Name: "STORAGECLASS"}, + model1.HeaderColumn{Name: "REASON"}, + model1.HeaderColumn{Name: "VOLUMEMODE", Wide: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error { +func (p PersistentVolume) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected PersistentVolume, but got %T", o) @@ -94,7 +93,7 @@ func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error { size := pv.Spec.Capacity[v1.ResourceStorage] r.ID = client.MetaFQN(pv.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ pv.Name, size.String(), accessMode(pv.Spec.AccessModes), diff --git a/internal/render/pv_test.go b/internal/render/pv_test.go index 93f77fb583..615fd8b6c5 100644 --- a/internal/render/pv_test.go +++ b/internal/render/pv_test.go @@ -6,24 +6,25 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestPersistentVolumeRender(t *testing.T) { c := render.PersistentVolume{} - r := render.NewRow(9) + r := model1.NewRow(9) assert.NoError(t, c.Render(load(t, "pv"), "-", &r)) assert.Equal(t, "-/pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", r.ID) - assert.Equal(t, render.Fields{"pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", "1Gi", "RWO", "Delete", "Bound", "default/www-nginx-sts-1", "standard"}, r.Fields[:7]) + assert.Equal(t, model1.Fields{"pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", "1Gi", "RWO", "Delete", "Bound", "default/www-nginx-sts-1", "standard"}, r.Fields[:7]) } func TestTerminatingPersistentVolumeRender(t *testing.T) { c := render.PersistentVolume{} - r := render.NewRow(9) + r := model1.NewRow(9) assert.NoError(t, c.Render(load(t, "pv_terminating"), "-", &r)) assert.Equal(t, "-/pvc-a4d86f51-916c-476b-83af-b551c91a8ac0", r.ID) - assert.Equal(t, render.Fields{"pvc-a4d86f51-916c-476b-83af-b551c91a8ac0", "1Gi", "RWO", "Delete", "Terminating", "default/www-nginx-sts-2", "standard"}, r.Fields[:7]) + assert.Equal(t, model1.Fields{"pvc-a4d86f51-916c-476b-83af-b551c91a8ac0", "1Gi", "RWO", "Delete", "Terminating", "default/www-nginx-sts-2", "standard"}, r.Fields[:7]) } diff --git a/internal/render/pvc.go b/internal/render/pvc.go index bd3cc43b36..79678346bc 100644 --- a/internal/render/pvc.go +++ b/internal/render/pvc.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,23 +19,23 @@ type PersistentVolumeClaim struct { } // Header returns a header rbw. -func (PersistentVolumeClaim) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "VOLUME"}, - HeaderColumn{Name: "CAPACITY", Capacity: true}, - HeaderColumn{Name: "ACCESS MODES"}, - HeaderColumn{Name: "STORAGECLASS"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (PersistentVolumeClaim) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "VOLUME"}, + model1.HeaderColumn{Name: "CAPACITY", Capacity: true}, + model1.HeaderColumn{Name: "ACCESS MODES"}, + model1.HeaderColumn{Name: "STORAGECLASS"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { +func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected PersistentVolumeClaim, but got %T", o) @@ -65,7 +66,7 @@ func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(pvc.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ pvc.Namespace, pvc.Name, string(phase), diff --git a/internal/render/pvc_test.go b/internal/render/pvc_test.go index c1005cb1cf..ec85c2e180 100644 --- a/internal/render/pvc_test.go +++ b/internal/render/pvc_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestPersistentVolumeClaimRender(t *testing.T) { c := render.PersistentVolumeClaim{} - r := render.NewRow(8) + r := model1.NewRow(8) assert.NoError(t, c.Render(load(t, "pvc"), "", &r)) assert.Equal(t, "default/www-nginx-sts-0", r.ID) - assert.Equal(t, render.Fields{"default", "www-nginx-sts-0", "Bound", "pvc-fbabd470-8725-11e9-a8e8-42010a80015b", "1Gi", "RWO", "standard"}, r.Fields[:7]) + assert.Equal(t, model1.Fields{"default", "www-nginx-sts-0", "Bound", "pvc-fbabd470-8725-11e9-a8e8-42010a80015b", "1Gi", "RWO", "standard"}, r.Fields[:7]) } diff --git a/internal/render/rbac.go b/internal/render/rbac.go index ec23c8ddb2..12ad96e7a5 100644 --- a/internal/render/rbac.go +++ b/internal/render/rbac.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/model1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -37,31 +38,31 @@ type Rbac struct { } // ColorerFunc colors a resource row. -func (Rbac) ColorerFunc() ColorerFunc { - return DefaultColorer +func (Rbac) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Header returns a header row. -func (Rbac) Header(ns string) Header { - h := make(Header, 0, 10) +func (Rbac) Header(ns string) model1.Header { + h := make(model1.Header, 0, 10) h = append(h, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "API-GROUP"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "API-GROUP"}, ) h = append(h, rbacVerbHeader()...) - return append(h, HeaderColumn{Name: "VALID", Wide: true}) + return append(h, model1.HeaderColumn{Name: "VALID", Wide: true}) } // Render renders a K8s resource to screen. -func (r Rbac) Render(o interface{}, ns string, ro *Row) error { +func (r Rbac) Render(o interface{}, ns string, ro *model1.Row) error { p, ok := o.(PolicyRes) if !ok { return fmt.Errorf("expecting RuleRes but got %T", o) } ro.ID = p.Resource - ro.Fields = make(Fields, 0, len(r.Header(ns))) + ro.Fields = make(model1.Fields, 0, len(r.Header(ns))) ro.Fields = append(ro.Fields, cleanseResource(p.Resource), p.Group, diff --git a/internal/render/reference.go b/internal/render/reference.go index 31695438ee..21dec9d75d 100644 --- a/internal/render/reference.go +++ b/internal/render/reference.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -18,24 +19,24 @@ type Reference struct { } // ColorerFunc colors a resource row. -func (Reference) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (Reference) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorCadetBlue } } // Header returns a header row. -func (Reference) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "GVR"}, +func (Reference) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "GVR"}, } } // Render renders a K8s resource to screen. // BOZO!! Pass in a row with pre-alloc fields?? -func (Reference) Render(o interface{}, ns string, r *Row) error { +func (Reference) Render(o interface{}, ns string, r *model1.Row) error { ref, ok := o.(ReferenceRes) if !ok { return fmt.Errorf("expected ReferenceRes, but got %T", o) diff --git a/internal/render/reference_test.go b/internal/render/reference_test.go index 50654c1ade..46aaf70094 100644 --- a/internal/render/reference_test.go +++ b/internal/render/reference_test.go @@ -6,6 +6,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) @@ -19,11 +20,11 @@ func TestReferenceRender(t *testing.T) { var ( ref = render.Reference{} - r render.Row + r model1.Row ) assert.Nil(t, ref.Render(o, "fred", &r)) assert.Equal(t, "ns1/blee", r.ID) - assert.Equal(t, render.Fields{ + assert.Equal(t, model1.Fields{ "ns1", "blee", "v1/secrets", diff --git a/internal/render/ro.go b/internal/render/ro.go index 3e90164627..7b3ce31549 100644 --- a/internal/render/ro.go +++ b/internal/render/ro.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -18,22 +19,22 @@ type Role struct { } // Header returns a header row. -func (Role) Header(ns string) Header { - var h Header +func (Role) Header(ns string) model1.Header { + var h model1.Header if client.IsAllNamespaces(ns) { - h = append(h, HeaderColumn{Name: "NAMESPACE"}) + h = append(h, model1.HeaderColumn{Name: "NAMESPACE"}) } return append(h, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, ) } // Render renders a K8s resource to screen. -func (r Role) Render(o interface{}, ns string, row *Row) error { +func (r Role) Render(o interface{}, ns string, row *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Role, but got %T", o) @@ -45,7 +46,7 @@ func (r Role) Render(o interface{}, ns string, row *Row) error { } row.ID = client.MetaFQN(ro.ObjectMeta) - row.Fields = make(Fields, 0, len(r.Header(ns))) + row.Fields = make(model1.Fields, 0, len(r.Header(ns))) if client.IsAllNamespaces(ns) { row.Fields = append(row.Fields, ro.Namespace) } diff --git a/internal/render/ro_test.go b/internal/render/ro_test.go index 1e0e4cc5d4..5beb907d4d 100644 --- a/internal/render/ro_test.go +++ b/internal/render/ro_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestRoleRender(t *testing.T) { c := render.Role{} - r := render.NewRow(3) + r := model1.NewRow(3) assert.NoError(t, c.Render(load(t, "ro"), "", &r)) assert.Equal(t, "default/blee", r.ID) - assert.Equal(t, render.Fields{"default", "blee"}, r.Fields[:2]) + assert.Equal(t, model1.Fields{"default", "blee"}, r.Fields[:2]) } diff --git a/internal/render/rob.go b/internal/render/rob.go index 46783a88f8..1f58fd609c 100644 --- a/internal/render/rob.go +++ b/internal/render/rob.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -19,25 +20,25 @@ type RoleBinding struct { } // Header returns a header rbw. -func (RoleBinding) Header(ns string) Header { - var h Header +func (RoleBinding) Header(ns string) model1.Header { + var h model1.Header if client.IsAllNamespaces(ns) { - h = append(h, HeaderColumn{Name: "NAMESPACE"}) + h = append(h, model1.HeaderColumn{Name: "NAMESPACE"}) } return append(h, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "ROLE"}, - HeaderColumn{Name: "KIND"}, - HeaderColumn{Name: "SUBJECTS"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "ROLE"}, + model1.HeaderColumn{Name: "KIND"}, + model1.HeaderColumn{Name: "SUBJECTS"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, ) } // Render renders a K8s resource to screen. -func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { +func (r RoleBinding) Render(o interface{}, ns string, row *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected RoleBinding, but got %T", o) @@ -51,7 +52,7 @@ func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { kind, ss := renderSubjects(rb.Subjects) row.ID = client.MetaFQN(rb.ObjectMeta) - row.Fields = make(Fields, 0, len(r.Header(ns))) + row.Fields = make(model1.Fields, 0, len(r.Header(ns))) if client.IsAllNamespaces(ns) { row.Fields = append(row.Fields, rb.Namespace) } diff --git a/internal/render/rob_test.go b/internal/render/rob_test.go index 306cab3066..f18a08bf26 100644 --- a/internal/render/rob_test.go +++ b/internal/render/rob_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestRoleBindingRender(t *testing.T) { c := render.RoleBinding{} - r := render.NewRow(6) + r := model1.NewRow(6) assert.NoError(t, c.Render(load(t, "rb"), "", &r)) assert.Equal(t, "default/blee", r.ID) - assert.Equal(t, render.Fields{"default", "blee", "blee", "SvcAcct", "fernand"}, r.Fields[:5]) + assert.Equal(t, model1.Fields{"default", "blee", "blee", "SvcAcct", "fernand"}, r.Fields[:5]) } diff --git a/internal/render/row.go b/internal/render/row.go deleted file mode 100644 index 858b11987a..0000000000 --- a/internal/render/row.go +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render - -import ( - "reflect" - "sort" - "strings" - - "github.com/fvbommel/sortorder" -) - -// Fields represents a collection of row fields. -type Fields []string - -// Customize returns a subset of fields. -func (f Fields) Customize(cols []int, out Fields) { - for i, c := range cols { - if c < 0 { - out[i] = NAValue - continue - } - if c < len(f) { - out[i] = f[c] - } - } -} - -// Diff returns true if fields differ or false otherwise. -func (f Fields) Diff(ff Fields, ageCol int) bool { - if ageCol < 0 { - return !reflect.DeepEqual(f[:len(f)-1], ff[:len(ff)-1]) - } - if !reflect.DeepEqual(f[:ageCol], ff[:ageCol]) { - return true - } - return !reflect.DeepEqual(f[ageCol+1:], ff[ageCol+1:]) -} - -// Clone returns a copy of the fields. -func (f Fields) Clone() Fields { - cp := make(Fields, len(f)) - copy(cp, f) - - return cp -} - -// ---------------------------------------------------------------------------- - -// Row represents a collection of columns. -type Row struct { - ID string - Fields Fields -} - -// NewRow returns a new row with initialized fields. -func NewRow(size int) Row { - return Row{Fields: make([]string, size)} -} - -// Labelize returns a new row based on labels. -func (r Row) Labelize(cols []int, labelCol int, labels []string) Row { - out := NewRow(len(cols) + len(labels)) - for _, col := range cols { - out.Fields = append(out.Fields, r.Fields[col]) - } - m := labelize(r.Fields[labelCol]) - for _, label := range labels { - out.Fields = append(out.Fields, m[label]) - } - - return out -} - -// Customize returns a row subset based on given col indices. -func (r Row) Customize(cols []int) Row { - out := NewRow(len(cols)) - r.Fields.Customize(cols, out.Fields) - out.ID = r.ID - - return out -} - -// Diff returns true if row differ or false otherwise. -func (r Row) Diff(ro Row, ageCol int) bool { - if r.ID != ro.ID { - return true - } - return r.Fields.Diff(ro.Fields, ageCol) -} - -// Clone copies a row. -func (r Row) Clone() Row { - return Row{ - ID: r.ID, - Fields: r.Fields.Clone(), - } -} - -// Len returns the length of the row. -func (r Row) Len() int { - return len(r.Fields) -} - -// ---------------------------------------------------------------------------- - -// Rows represents a collection of rows. -type Rows []Row - -// Delete removes an element by id. -func (rr Rows) Delete(id string) Rows { - idx, ok := rr.Find(id) - if !ok { - return rr - } - - if idx == 0 { - return rr[1:] - } - if idx+1 == len(rr) { - return rr[:len(rr)-1] - } - - return append(rr[:idx], rr[idx+1:]...) -} - -// Upsert adds a new item. -func (rr Rows) Upsert(r Row) Rows { - idx, ok := rr.Find(r.ID) - if !ok { - return append(rr, r) - } - rr[idx] = r - - return rr -} - -// Find locates a row by id. Returns false is not found. -func (rr Rows) Find(id string) (int, bool) { - for i, r := range rr { - if r.ID == id { - return i, true - } - } - - return 0, false -} - -// Sort rows based on column index and order. -func (rr Rows) Sort(col int, asc, isNum, isDur, isCapacity bool) { - t := RowSorter{ - Rows: rr, - Index: col, - IsNumber: isNum, - IsDuration: isDur, - IsCapacity: isCapacity, - Asc: asc, - } - sort.Sort(t) -} - -// ---------------------------------------------------------------------------- - -// RowSorter sorts rows. -type RowSorter struct { - Rows Rows - Index int - IsNumber bool - IsDuration bool - IsCapacity bool - Asc bool -} - -func (s RowSorter) Len() int { - return len(s.Rows) -} - -func (s RowSorter) Swap(i, j int) { - s.Rows[i], s.Rows[j] = s.Rows[j], s.Rows[i] -} - -func (s RowSorter) Less(i, j int) bool { - v1, v2 := s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index] - id1, id2 := s.Rows[i].ID, s.Rows[j].ID - less := Less(s.IsNumber, s.IsDuration, s.IsCapacity, id1, id2, v1, v2) - if s.Asc { - return less - } - return !less -} - -// ---------------------------------------------------------------------------- -// Helpers... - -// Less return true if c1 <= c2. -func Less(isNumber, isDuration, isCapacity bool, id1, id2, v1, v2 string) bool { - var less bool - switch { - case isNumber: - less = lessNumber(v1, v2) - case isDuration: - less = lessDuration(v1, v2) - case isCapacity: - less = lessCapacity(v1, v2) - default: - less = sortorder.NaturalLess(v1, v2) - } - if v1 == v2 { - return sortorder.NaturalLess(id1, id2) - } - - return less -} - -func lessDuration(s1, s2 string) bool { - d1, d2 := durationToSeconds(s1), durationToSeconds(s2) - return d1 <= d2 -} - -func lessCapacity(s1, s2 string) bool { - c1, c2 := capacityToNumber(s1), capacityToNumber(s2) - - return c1 <= c2 -} - -func lessNumber(s1, s2 string) bool { - v1, v2 := strings.Replace(s1, ",", "", -1), strings.Replace(s2, ",", "", -1) - - return sortorder.NaturalLess(v1, v2) -} diff --git a/internal/render/row_event_test.go b/internal/render/row_event_test.go deleted file mode 100644 index d75e1894f3..0000000000 --- a/internal/render/row_event_test.go +++ /dev/null @@ -1,539 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render_test - -import ( - "testing" - "time" - - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" -) - -func TestRowEventCustomize(t *testing.T) { - uu := map[string]struct { - re1, e render.RowEvent - cols []int - }{ - "empty": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{}}, - }, - }, - "full": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - cols: []int{0, 1, 2}, - }, - "deltas": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - Deltas: render.DeltaRow{"a", "b", "c"}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - Deltas: render.DeltaRow{"a", "b", "c"}, - }, - cols: []int{0, 1, 2}, - }, - "deltas-skip": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - Deltas: render.DeltaRow{"a", "b", "c"}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"3", "1"}}, - Deltas: render.DeltaRow{"c", "a"}, - }, - cols: []int{2, 0}, - }, - "reverse": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"3", "2", "1"}}, - }, - cols: []int{2, 1, 0}, - }, - "skip": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"3", "1"}}, - }, - cols: []int{2, 0}, - }, - "miss": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - e: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"3", "", "1"}}, - }, - cols: []int{2, 10, 0}, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.re1.Customize(u.cols)) - }) - } -} - -func TestRowEventDiff(t *testing.T) { - uu := map[string]struct { - re1, re2 render.RowEvent - e bool - }{ - "same": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - re2: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - }, - "diff-kind": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - re2: render.RowEvent{ - Kind: render.EventDelete, - Row: render.Row{ID: "B", Fields: render.Fields{"1", "2", "3"}}, - }, - e: true, - }, - "diff-delta": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - Deltas: render.DeltaRow{"1", "2", "3"}, - }, - re2: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - Deltas: render.DeltaRow{"10", "2", "3"}, - }, - e: true, - }, - "diff-id": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - re2: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "B", Fields: render.Fields{"1", "2", "3"}}, - }, - e: true, - }, - "diff-field": { - re1: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - }, - re2: render.RowEvent{ - Kind: render.EventAdd, - Row: render.Row{ID: "A", Fields: render.Fields{"10", "2", "3"}}, - }, - e: true, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.re1.Diff(u.re2, -1)) - }) - } -} - -func TestRowEventsDiff(t *testing.T) { - uu := map[string]struct { - re1, re2 render.RowEvents - ageCol int - e bool - }{ - "same": { - re1: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re2: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - ageCol: -1, - }, - "diff-len": { - re1: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re2: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - ageCol: -1, - e: true, - }, - "diff-id": { - re1: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re2: render.RowEvents{ - {Row: render.Row{ID: "D", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - ageCol: -1, - e: true, - }, - "diff-order": { - re1: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re2: render.RowEvents{ - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - ageCol: -1, - e: true, - }, - "diff-withAge": { - re1: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re2: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "13"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - ageCol: 1, - e: true, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.re1.Diff(u.re2, u.ageCol)) - }) - } -} - -func TestRowEventsUpsert(t *testing.T) { - uu := map[string]struct { - ee, e render.RowEvents - re render.RowEvent - }{ - "add": { - ee: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - re: render.RowEvent{ - Row: render.Row{ID: "D", Fields: render.Fields{"f1", "f2", "f3"}}, - }, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - {Row: render.Row{ID: "D", Fields: render.Fields{"f1", "f2", "f3"}}}, - }, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.ee.Upsert(u.re)) - }) - } -} - -func TestRowEventsCustomize(t *testing.T) { - uu := map[string]struct { - re, e render.RowEvents - cols []int - }{ - "same": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - cols: []int{0, 1, 2}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "reverse": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - cols: []int{2, 1, 0}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"3", "2", "1"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"3", "2", "0"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"3", "2", "10"}}}, - }, - }, - "skip": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - cols: []int{1, 0}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"2", "1"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"2", "0"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"2", "10"}}}, - }, - }, - "missing": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - cols: []int{1, 0, 4}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"2", "1", ""}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"2", "0", ""}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"2", "10", ""}}}, - }, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.re.Customize(u.cols)) - }) - } -} - -func TestRowEventsDelete(t *testing.T) { - uu := map[string]struct { - re render.RowEvents - id string - e render.RowEvents - }{ - "first": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - id: "A", - e: render.RowEvents{ - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "middle": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - id: "B", - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "last": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - id: "C", - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - }, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.re.Delete(u.id)) - }) - } -} - -func TestRowEventsSort(t *testing.T) { - uu := map[string]struct { - re render.RowEvents - col int - duration, num, asc bool - capacity bool - e render.RowEvents - }{ - "age_time": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}}, - }, - col: 2, - asc: true, - duration: true, - e: render.RowEvents{ - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", testTime().String()}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", testTime().Add(10 * time.Second).String()}}}, - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", testTime().Add(20 * time.Second).String()}}}, - }, - }, - "col0": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - col: 0, - asc: true, - e: render.RowEvents{ - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "id_preserve": { - re: render.RowEvents{ - {Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3"}}}, - {Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3"}}}, - }, - col: 1, - asc: true, - e: render.RowEvents{ - {Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3"}}}, - {Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3"}}}, - }, - }, - "capacity": { - re: render.RowEvents{ - {Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3", "1Gi"}}}, - {Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3", "1.1G"}}}, - {Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3", "0.5Ti"}}}, - {Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3", "12e6"}}}, - {Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3", "1234"}}}, - {Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3", "0.1Ei"}}}, - }, - col: 3, - asc: true, - capacity: true, - e: render.RowEvents{ - {Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3", "1234"}}}, - {Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3", "12e6"}}}, - {Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3", "1Gi"}}}, - {Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3", "1.1G"}}}, - {Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3", "0.5Ti"}}}, - {Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3", "0.1Ei"}}}, - }, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - u.re.Sort("", u.col, u.duration, u.num, u.capacity, u.asc) - assert.Equal(t, u.e, u.re) - }) - } -} - -func TestRowEventsClone(t *testing.T) { - uu := map[string]struct { - r render.RowEvents - }{ - "empty": { - r: render.RowEvents{}, - }, - "full": { - r: makeRowEvents(), - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - c := u.r.Clone() - assert.Equal(t, len(u.r), len(c)) - if len(u.r) > 0 { - u.r[0].Row.Fields[0] = "blee" - assert.Equal(t, "A", c[0].Row.Fields[0]) - } - }) - } -} - -// Helpers... - -func makeRowEvents() render.RowEvents { - return render.RowEvents{ - {Row: render.Row{ID: "ns1/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns1/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns1/C", Fields: render.Fields{"C", "2", "3"}}}, - {Row: render.Row{ID: "ns2/A", Fields: render.Fields{"A", "2", "3"}}}, - {Row: render.Row{ID: "ns2/B", Fields: render.Fields{"B", "2", "3"}}}, - {Row: render.Row{ID: "ns2/C", Fields: render.Fields{"C", "2", "3"}}}, - } -} diff --git a/internal/render/rs.go b/internal/render/rs.go index 6dd7f38f1e..85d5dbed3a 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -20,29 +21,27 @@ type ReplicaSet struct { } // ColorerFunc colors a resource row. -func (r ReplicaSet) ColorerFunc() ColorerFunc { - return DefaultColorer +func (r ReplicaSet) ColorerFunc() model1.ColorerFunc { + return model1.DefaultColorer } // Header returns a header row. -func (ReplicaSet) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, - HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, - HeaderColumn{Name: "READY", Align: tview.AlignRight}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (ReplicaSet) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "READY", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h } // Render renders a K8s resource to screen. -func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { +func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected ReplicaSet, but got %T", o) @@ -54,7 +53,7 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *Row) error { } row.ID = client.MetaFQN(rs.ObjectMeta) - row.Fields = Fields{ + row.Fields = model1.Fields{ rs.Namespace, rs.Name, computeVulScore(rs.ObjectMeta, &rs.Spec.Template.Spec), diff --git a/internal/render/rs_test.go b/internal/render/rs_test.go index 8a85dc599a..7a84cf38e3 100644 --- a/internal/render/rs_test.go +++ b/internal/render/rs_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestReplicaSetRender(t *testing.T) { c := render.ReplicaSet{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "rs"), "", &r)) assert.Equal(t, "icx/icx-db-7d4b578979", r.ID) - assert.Equal(t, render.Fields{"icx", "icx-db-7d4b578979", "0", "1", "1", "1"}, r.Fields[:6]) + assert.Equal(t, model1.Fields{"icx", "icx-db-7d4b578979", "0", "1", "1", "1"}, r.Fields[:6]) } diff --git a/internal/render/sa.go b/internal/render/sa.go index 43f7c89861..1f463a4e34 100644 --- a/internal/render/sa.go +++ b/internal/render/sa.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -19,19 +20,19 @@ type ServiceAccount struct { } // Header returns a header row. -func (ServiceAccount) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "SECRET"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (ServiceAccount) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "SECRET"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { +func (s ServiceAccount) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected ServiceAccount, but got %T", o) @@ -43,7 +44,7 @@ func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(sa.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ sa.Namespace, sa.Name, strconv.Itoa(len(sa.Secrets)), diff --git a/internal/render/sa_test.go b/internal/render/sa_test.go index c143bc509d..932ee79895 100644 --- a/internal/render/sa_test.go +++ b/internal/render/sa_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestServiceAccountRender(t *testing.T) { c := render.ServiceAccount{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "sa"), "", &r)) assert.Equal(t, "default/blee", r.ID) - assert.Equal(t, render.Fields{"default", "blee", "2"}, r.Fields[:3]) + assert.Equal(t, model1.Fields{"default", "blee", "2"}, r.Fields[:3]) } diff --git a/internal/render/sc.go b/internal/render/sc.go index d5f5ecaaeb..f805fb1000 100644 --- a/internal/render/sc.go +++ b/internal/render/sc.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -20,21 +21,21 @@ type StorageClass struct { } // Header returns a header row. -func (StorageClass) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "PROVISIONER"}, - HeaderColumn{Name: "RECLAIMPOLICY"}, - HeaderColumn{Name: "VOLUMEBINDINGMODE"}, - HeaderColumn{Name: "ALLOWVOLUMEEXPANSION"}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (StorageClass) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "PROVISIONER"}, + model1.HeaderColumn{Name: "RECLAIMPOLICY"}, + model1.HeaderColumn{Name: "VOLUMEBINDINGMODE"}, + model1.HeaderColumn{Name: "ALLOWVOLUMEEXPANSION"}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (s StorageClass) Render(o interface{}, ns string, r *Row) error { +func (s StorageClass) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected StorageClass, but got %T", o) @@ -46,7 +47,7 @@ func (s StorageClass) Render(o interface{}, ns string, r *Row) error { } r.ID = client.FQN(client.ClusterScope, sc.ObjectMeta.Name) - r.Fields = Fields{ + r.Fields = model1.Fields{ s.nameWithDefault(sc.ObjectMeta), sc.Provisioner, strPtrToStr((*string)(sc.ReclaimPolicy)), diff --git a/internal/render/sc_test.go b/internal/render/sc_test.go index 004e91c494..c588313610 100644 --- a/internal/render/sc_test.go +++ b/internal/render/sc_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestStorageClassRender(t *testing.T) { c := render.StorageClass{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "sc"), "", &r)) assert.Equal(t, "-/standard", r.ID) - assert.Equal(t, render.Fields{"standard (default)", "kubernetes.io/gce-pd", "Delete", "Immediate", "true"}, r.Fields[:5]) + assert.Equal(t, model1.Fields{"standard (default)", "kubernetes.io/gce-pd", "Delete", "Immediate", "true"}, r.Fields[:5]) } diff --git a/internal/render/screen_dump.go b/internal/render/screen_dump.go index 9b237d7a07..8193c612dc 100644 --- a/internal/render/screen_dump.go +++ b/internal/render/screen_dump.go @@ -9,6 +9,7 @@ import ( "path/filepath" "time" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -21,31 +22,31 @@ type ScreenDump struct { } // ColorerFunc colors a resource row. -func (ScreenDump) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (ScreenDump) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorNavajoWhite } } // Header returns a header row. -func (ScreenDump) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "DIR"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (ScreenDump) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "DIR"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (b ScreenDump) Render(o interface{}, ns string, r *Row) error { +func (b ScreenDump) Render(o interface{}, ns string, r *model1.Row) error { f, ok := o.(FileRes) if !ok { return fmt.Errorf("expecting screendumper, but got %T", o) } r.ID = filepath.Join(f.Dir, f.File.Name()) - r.Fields = Fields{ + r.Fields = model1.Fields{ f.File.Name(), f.Dir, "", diff --git a/internal/render/screen_dump_test.go b/internal/render/screen_dump_test.go index bde7f109fd..55c82f3887 100644 --- a/internal/render/screen_dump_test.go +++ b/internal/render/screen_dump_test.go @@ -8,13 +8,14 @@ import ( "testing" "time" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestScreenDumpRender(t *testing.T) { var s render.ScreenDump - var r render.Row + var r model1.Row o := render.FileRes{ File: fileInfo{}, Dir: "fred/blee", @@ -22,7 +23,7 @@ func TestScreenDumpRender(t *testing.T) { assert.Nil(t, s.Render(o, "fred", &r)) assert.Equal(t, "fred/blee/bob", r.ID) - assert.Equal(t, render.Fields{ + assert.Equal(t, model1.Fields{ "bob", "fred/blee", "", diff --git a/internal/render/secret.go b/internal/render/secret.go new file mode 100644 index 0000000000..d1d3aad9b0 --- /dev/null +++ b/internal/render/secret.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "strconv" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// Secret renders a K8s Secret to screen. +type Secret struct { + Base +} + +// Header returns a header rbw. +func (Secret) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "TYPE"}, + model1.HeaderColumn{Name: "DATA"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, + } +} + +// Render renders a K8s resource to screen. +func (n Secret) Render(o interface{}, _ string, r *model1.Row) error { + raw, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expected Secret, but got %T", o) + } + var sec v1.Secret + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sec) + if err != nil { + return err + } + + r.ID = client.FQN(sec.Namespace, sec.Name) + r.Fields = model1.Fields{ + sec.Namespace, + sec.Name, + string(sec.Type), + strconv.Itoa(len(sec.Data)), + "", + ToAge(raw.GetCreationTimestamp()), + } + + return nil +} diff --git a/internal/render/sts.go b/internal/render/sts.go index cc6b05232a..e35560ce04 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -19,26 +20,24 @@ type StatefulSet struct { } // Header returns a header row. -func (StatefulSet) Header(ns string) Header { - h := Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "VS", VS: true}, - HeaderColumn{Name: "READY"}, - HeaderColumn{Name: "SELECTOR", Wide: true}, - HeaderColumn{Name: "SERVICE"}, - HeaderColumn{Name: "CONTAINERS", Wide: true}, - HeaderColumn{Name: "IMAGES", Wide: true}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (StatefulSet) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "VS", VS: true}, + model1.HeaderColumn{Name: "READY"}, + model1.HeaderColumn{Name: "SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "SERVICE"}, + model1.HeaderColumn{Name: "CONTAINERS", Wide: true}, + model1.HeaderColumn{Name: "IMAGES", Wide: true}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } - - return h } // Render renders a K8s resource to screen. -func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { +func (s StatefulSet) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected StatefulSet, but got %T", o) @@ -50,7 +49,7 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(sts.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ sts.Namespace, sts.Name, computeVulScore(sts.ObjectMeta, &sts.Spec.Template.Spec), diff --git a/internal/render/sts_test.go b/internal/render/sts_test.go index 0070d1a818..d8a4edc8a4 100644 --- a/internal/render/sts_test.go +++ b/internal/render/sts_test.go @@ -6,15 +6,16 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestStatefulSetRender(t *testing.T) { c := render.StatefulSet{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.Nil(t, c.Render(load(t, "sts"), "", &r)) assert.Equal(t, "default/nginx-sts", r.ID) - assert.Equal(t, render.Fields{"default", "nginx-sts", "0", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) + assert.Equal(t, model1.Fields{"default", "nginx-sts", "0", "4/4", "app=nginx-sts", "nginx-sts", "nginx", "k8s.gcr.io/nginx-slim:0.8", "app=nginx-sts", ""}, r.Fields[:len(r.Fields)-1]) } diff --git a/internal/render/subject.go b/internal/render/subject.go index b58e0ba7b4..af3c0a6173 100644 --- a/internal/render/subject.go +++ b/internal/render/subject.go @@ -6,6 +6,7 @@ package render import ( "fmt" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -17,31 +18,31 @@ type Subject struct { } // ColorerFunc colors a resource row. -func (Subject) ColorerFunc() ColorerFunc { - return func(ns string, _ Header, re RowEvent) tcell.Color { +func (Subject) ColorerFunc() model1.ColorerFunc { + return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color { return tcell.ColorMediumSpringGreen } } // Header returns a header row. -func (Subject) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "KIND"}, - HeaderColumn{Name: "FIRST LOCATION"}, - HeaderColumn{Name: "VALID", Wide: true}, +func (Subject) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "KIND"}, + model1.HeaderColumn{Name: "FIRST LOCATION"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, } } // Render renders a K8s resource to screen. -func (s Subject) Render(o interface{}, ns string, r *Row) error { +func (s Subject) Render(o interface{}, ns string, r *model1.Row) error { res, ok := o.(SubjectRes) if !ok { return fmt.Errorf("expected SubjectRes, but got %T", s) } r.ID = res.Name - r.Fields = Fields{ + r.Fields = model1.Fields{ res.Name, res.Kind, res.FirstLocation, diff --git a/internal/render/svc.go b/internal/render/svc.go index 2f3cb30cf8..73081cadf0 100644 --- a/internal/render/svc.go +++ b/internal/render/svc.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -21,23 +22,23 @@ type Service struct { } // Header returns a header row. -func (Service) Header(ns string) Header { - return Header{ - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "TYPE"}, - HeaderColumn{Name: "CLUSTER-IP"}, - HeaderColumn{Name: "EXTERNAL-IP"}, - HeaderColumn{Name: "SELECTOR", Wide: true}, - HeaderColumn{Name: "PORTS", Wide: false}, - HeaderColumn{Name: "LABELS", Wide: true}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Service) Header(ns string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "TYPE"}, + model1.HeaderColumn{Name: "CLUSTER-IP"}, + model1.HeaderColumn{Name: "EXTERNAL-IP"}, + model1.HeaderColumn{Name: "SELECTOR", Wide: true}, + model1.HeaderColumn{Name: "PORTS", Wide: false}, + model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (s Service) Render(o interface{}, ns string, r *Row) error { +func (s Service) Render(o interface{}, ns string, r *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Service, but got %T", o) @@ -49,7 +50,7 @@ func (s Service) Render(o interface{}, ns string, r *Row) error { } r.ID = client.MetaFQN(svc.ObjectMeta) - r.Fields = Fields{ + r.Fields = model1.Fields{ svc.Namespace, svc.ObjectMeta.Name, string(svc.Spec.Type), diff --git a/internal/render/svc_test.go b/internal/render/svc_test.go index e0b70471ac..6a1ea62fe4 100644 --- a/internal/render/svc_test.go +++ b/internal/render/svc_test.go @@ -6,22 +6,23 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" ) func TestServiceRender(t *testing.T) { c := render.Service{} - r := render.NewRow(4) + r := model1.NewRow(4) assert.NoError(t, c.Render(load(t, "svc"), "", &r)) assert.Equal(t, "default/dictionary1", r.ID) - assert.Equal(t, render.Fields{"default", "dictionary1", "ClusterIP", "10.47.248.116", "", "app=dictionary1", "http:4001►0"}, r.Fields[:7]) + assert.Equal(t, model1.Fields{"default", "dictionary1", "ClusterIP", "10.47.248.116", "", "app=dictionary1", "http:4001►0"}, r.Fields[:7]) } func BenchmarkSvcRender(b *testing.B) { var svc render.Service - r := render.NewRow(4) + r := model1.NewRow(4) s := load(b, "svc") b.ResetTimer() b.ReportAllocs() diff --git a/internal/render/table_data.go b/internal/render/table_data.go deleted file mode 100644 index aaf96cf227..0000000000 --- a/internal/render/table_data.go +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render - -import ( - "sync" - - "github.com/derailed/k9s/internal/client" -) - -// TableData tracks a K8s resource for tabular display. -type TableData struct { - Header Header - RowEvents RowEvents - Namespace string - mx sync.RWMutex -} - -// NewTableData returns a new table. -func NewTableData() *TableData { - return &TableData{} -} - -// Empty checks if there are no entries. -func (t *TableData) Empty() bool { - t.mx.RLock() - defer t.mx.RUnlock() - - return len(t.RowEvents) == 0 -} - -// Count returns the number of entries. -func (t *TableData) Count() int { - t.mx.RLock() - defer t.mx.RUnlock() - - return len(t.RowEvents) -} - -// IndexOfHeader return the index of the header. -func (t *TableData) IndexOfHeader(h string) int { - return t.Header.IndexOf(h, false) -} - -// Labelize prints out specific label columns. -func (t *TableData) Labelize(labels []string) *TableData { - labelCol := t.Header.IndexOf("LABELS", true) - cols := []int{0, 1} - if client.IsNamespaced(t.Namespace) { - cols = cols[1:] - } - data := TableData{ - Namespace: t.Namespace, - Header: t.Header.Labelize(cols, labelCol, t.RowEvents), - } - data.RowEvents = t.RowEvents.Labelize(cols, labelCol, labels) - - return &data -} - -// Customize returns a new model with customized column layout. -func (t *TableData) Customize(cols []string, wide bool) *TableData { - res := TableData{ - Namespace: t.Namespace, - Header: t.Header.Customize(cols, wide), - } - ids := t.Header.MapIndices(cols, wide) - res.RowEvents = t.RowEvents.Customize(ids) - - return &res -} - -// Clear clears out the entire table. -func (t *TableData) Clear() { - t.Header, t.RowEvents = Header{}, RowEvents{} -} - -// Clone returns a copy of the table. -func (t *TableData) Clone() *TableData { - return &TableData{ - Header: t.Header.Clone(), - RowEvents: t.RowEvents.Clone(), - Namespace: t.Namespace, - } -} - -// SetHeader sets table header. -func (t *TableData) SetHeader(ns string, h Header) { - t.Namespace, t.Header = ns, h -} - -// Update computes row deltas and update the table data. -func (t *TableData) Update(rows Rows) { - empty := t.Empty() - kk := make(map[string]struct{}, len(rows)) - t.mx.Lock() - { - var blankDelta DeltaRow - for _, row := range rows { - kk[row.ID] = struct{}{} - if empty { - t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row)) - continue - } - if index, ok := t.RowEvents.FindIndex(row.ID); ok { - delta := NewDeltaRow(t.RowEvents[index].Row, row, t.Header) - if delta.IsBlank() { - t.RowEvents[index].Kind, t.RowEvents[index].Deltas = EventUnchanged, blankDelta - t.RowEvents[index].Row = row - } else { - t.RowEvents[index] = NewRowEventWithDeltas(row, delta) - } - continue - } - t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row)) - } - } - t.mx.Unlock() - - if !empty { - t.Delete(kk) - } -} - -// Delete removes items in cache that are no longer valid. -func (t *TableData) Delete(newKeys map[string]struct{}) { - t.mx.Lock() - { - var victims []string - for _, re := range t.RowEvents { - if _, ok := newKeys[re.Row.ID]; !ok { - victims = append(victims, re.Row.ID) - } - } - for _, id := range victims { - t.RowEvents = t.RowEvents.Delete(id) - } - } - t.mx.Unlock() -} - -// Diff checks if two tables are equal. -func (t *TableData) Diff(t2 *TableData) bool { - if t2 == nil || t.Namespace != t2.Namespace || t.Header.Diff(t2.Header) { - return true - } - - return t.RowEvents.Diff(t2.RowEvents, t.Header.IndexOf("AGE", true)) -} diff --git a/internal/render/table_data_test.go b/internal/render/table_data_test.go deleted file mode 100644 index 0ddb7c1516..0000000000 --- a/internal/render/table_data_test.go +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package render_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" -) - -func TestTableDataCustomize(t *testing.T) { - uu := map[string]struct { - t1, e *render.TableData - cols []string - wide bool - }{ - "same": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - cols: []string{"A", "B", "C"}, - e: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - }, - "wide-col": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - cols: []string{"A", "B", "C"}, - e: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: false}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - }, - "wide": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B", Wide: true}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - wide: true, - cols: []string{"A", "C"}, - e: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "B", Wide: true}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "3", "2"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "3", "2"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "3", "2"}}}, - }, - }, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.t1.Customize(u.cols, u.wide)) - }) - } -} - -func TestTableDataDiff(t *testing.T) { - uu := map[string]struct { - t1, t2 *render.TableData - e bool - }{ - "empty": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - e: true, - }, - "same": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - t2: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - }, - "ns-diff": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - t2: &render.TableData{ - Namespace: "blee", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - e: true, - }, - "header-diff": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "D"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - t2: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - e: true, - }, - "row-diff": { - t1: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - t2: &render.TableData{ - Namespace: "fred", - Header: render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - }, - RowEvents: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"100", "2", "3"}}}, - }, - }, - e: true, - }, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.t1.Diff(u.t2)) - }) - } -} - -func TestTableDataUpdate(t *testing.T) { - uu := map[string]struct { - re render.RowEvents - rr render.Rows - e render.RowEvents - }{ - "no-change": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - rr: render.Rows{ - render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}, - render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}, - }, - e: render.RowEvents{ - {Kind: render.EventUnchanged, Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "add": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - rr: render.Rows{ - render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}, - render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}, - render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}, - }, - e: render.RowEvents{ - {Kind: render.EventUnchanged, Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - {Kind: render.EventAdd, Row: render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "delete": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - rr: render.Rows{ - render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}, - render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}, - }, - e: render.RowEvents{ - {Kind: render.EventUnchanged, Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "update": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - rr: render.Rows{ - render.Row{ID: "A", Fields: render.Fields{"10", "2", "3"}}, - render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}, - render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}, - }, - e: render.RowEvents{ - { - Kind: render.EventUpdate, - Row: render.Row{ID: "A", Fields: render.Fields{"10", "2", "3"}}, - Deltas: render.DeltaRow{"1", "", ""}, - }, - {Kind: render.EventUnchanged, Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Kind: render.EventUnchanged, Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - } - - var table render.TableData - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - table.RowEvents = u.re - table.Update(u.rr) - assert.Equal(t, u.e, table.RowEvents) - }) - } -} - -func TestTableDataDelete(t *testing.T) { - uu := map[string]struct { - re render.RowEvents - kk map[string]struct{} - e render.RowEvents - }{ - "ordered": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - kk: map[string]struct{}{"A": {}, "C": {}}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - "unordered": { - re: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - {Row: render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}}, - }, - kk: map[string]struct{}{"C": {}, "A": {}}, - e: render.RowEvents{ - {Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}}, - {Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}}, - }, - }, - } - - var table render.TableData - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - table.RowEvents = u.re - table.Delete(u.kk) - assert.Equal(t, u.e, table.RowEvents) - }) - } -} diff --git a/internal/render/testdata/p1.json b/internal/render/testdata/p1.json new file mode 100644 index 0000000000..ea8d8dad73 --- /dev/null +++ b/internal/render/testdata/p1.json @@ -0,0 +1,146 @@ +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": "2019-12-31T12:26:47-07:00" + }, + "creationTimestamp": "2019-12-31T19:27:22Z", + "generateName": "nginx-7fb78fb6d8-", + "labels": { + "app": "nginx", + "pod-template-hash": "7fb78fb6d8" + }, + "name": "nginx-7fb78fb6d8-2w75j", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "nginx-7fb78fb6d8", + "uid": "7ccd0600-2c03-11ea-883f-42010a800044" + } + ], + "resourceVersion": "87290191", + "selfLink": "/api/v1/namespaces/default/pods/nginx-7fb78fb6d8-2w75j", + "uid": "91bb1cf2-2c03-11ea-883f-42010a800044" + }, + "spec": { + "containers": [ + { + "image": "k8s.gcr.io/nginx-slim:0.8", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "cpu": "200m", + "memory": "20Mi" + }, + "requests": { + "cpu": "200m", + "memory": "20Mi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-dsl46", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "gke-k9s-default-pool-0fa2fb89-lbtf", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-dsl46", + "secret": { + "defaultMode": 420, + "secretName": "default-token-dsl46" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2019-12-31T19:27:23Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-12-31T19:27:25Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-12-31T19:27:25Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2019-12-31T19:27:22Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://90e0abf7a779dd76d36038883312baed57a8351428a1d6340df3cff698f51809", + "image": "k8s.gcr.io/nginx-slim:0.8", + "imageID": "docker-pullable://k8s.gcr.io/nginx-slim@sha256:8b4501fe0fe221df663c22e16539f399e89594552f400408303c42f3dd8d0e52", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "state": { + "running": { + "startedAt": "2019-12-31T19:27:24Z" + } + } + } + ], + "hostIP": "10.128.0.15", + "phase": "Running", + "podIP": "10.44.0.229", + "qosClass": "Guaranteed", + "startTime": "2019-12-31T19:27:23Z" + } +} \ No newline at end of file diff --git a/internal/render/workload.go b/internal/render/workload.go index 250c52ecff..7a3a2645ec 100644 --- a/internal/render/workload.go +++ b/internal/render/workload.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/tcell/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -19,17 +20,17 @@ type Workload struct { } // ColorerFunc colors a resource row. -func (n Workload) ColorerFunc() ColorerFunc { - return func(ns string, h Header, re RowEvent) tcell.Color { - c := DefaultColorer(ns, h, re) +func (n Workload) ColorerFunc() model1.ColorerFunc { + return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color { + c := model1.DefaultColorer(ns, h, re) - statusCol := h.IndexOf("STATUS", true) - if statusCol == -1 { + idx, ok := h.IndexOf("STATUS", true) + if !ok { return c } - status := strings.TrimSpace(re.Row.Fields[statusCol]) + status := strings.TrimSpace(re.Row.Fields[idx]) if status == "DEGRADED" { - c = PendingColor + c = model1.PendingColor } return c @@ -37,27 +38,27 @@ func (n Workload) ColorerFunc() ColorerFunc { } // Header returns a header rbw. -func (Workload) Header(string) Header { - return Header{ - HeaderColumn{Name: "KIND"}, - HeaderColumn{Name: "NAMESPACE"}, - HeaderColumn{Name: "NAME"}, - HeaderColumn{Name: "STATUS"}, - HeaderColumn{Name: "READY"}, - HeaderColumn{Name: "VALID", Wide: true}, - HeaderColumn{Name: "AGE", Time: true}, +func (Workload) Header(string) model1.Header { + return model1.Header{ + model1.HeaderColumn{Name: "KIND"}, + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "STATUS"}, + model1.HeaderColumn{Name: "READY"}, + model1.HeaderColumn{Name: "VALID", Wide: true}, + model1.HeaderColumn{Name: "AGE", Time: true}, } } // Render renders a K8s resource to screen. -func (n Workload) Render(o interface{}, _ string, r *Row) error { +func (n Workload) Render(o interface{}, _ string, r *model1.Row) error { res, ok := o.(*WorkloadRes) if !ok { return fmt.Errorf("expected allRes but got %T", o) } r.ID = fmt.Sprintf("%s|%s|%s", res.Row.Cells[0].(string), res.Row.Cells[1].(string), res.Row.Cells[2].(string)) - r.Fields = Fields{ + r.Fields = model1.Fields{ res.Row.Cells[0].(string), res.Row.Cells[1].(string), res.Row.Cells[2].(string), diff --git a/internal/ui/action.go b/internal/ui/action.go index 3e4b8efd2a..f270e67ba7 100644 --- a/internal/ui/action.go +++ b/internal/ui/action.go @@ -5,6 +5,7 @@ package ui import ( "sort" + "sync" "github.com/derailed/k9s/internal/model" "github.com/derailed/tcell/v2" @@ -12,9 +13,13 @@ import ( ) type ( + // RangeFn represents a range iteration callback. + RangeFn func(tcell.Key, KeyAction) + // ActionHandler handles a keyboard command. ActionHandler func(*tcell.EventKey) *tcell.EventKey + // ActionOpts tracks various action options. ActionOpts struct { Visible bool Shared bool @@ -30,8 +35,14 @@ type ( Opts ActionOpts } + // KeyMap tracks key to action mappings. + KeyMap map[tcell.Key]KeyAction + // KeyActions tracks mappings between keystrokes and actions. - KeyActions map[tcell.Key]KeyAction + KeyActions struct { + actions KeyMap + mx sync.RWMutex + } ) // NewKeyAction returns a new keyboard action. @@ -58,53 +69,134 @@ func NewKeyActionWithOpts(d string, a ActionHandler, opts ActionOpts) KeyAction } } -func (a KeyActions) Reset(aa KeyActions) { +// NewKeyActions returns a new instance. +func NewKeyActions() *KeyActions { + return &KeyActions{ + actions: make(map[tcell.Key]KeyAction), + } +} + +// NewKeyActionsFromMap construct actions from key map. +func NewKeyActionsFromMap(mm KeyMap) *KeyActions { + return &KeyActions{actions: mm} +} + +// Get fetches an action given a key. +func (a *KeyActions) Get(key tcell.Key) (KeyAction, bool) { + a.mx.RLock() + defer a.mx.RUnlock() + + v, ok := a.actions[key] + + return v, ok +} + +// Len returns action mapping count. +func (a *KeyActions) Len() int { + a.mx.RLock() + defer a.mx.RUnlock() + + return len(a.actions) +} + +// Reset clears out actions. +func (a *KeyActions) Reset(aa *KeyActions) { a.Clear() - a.Add(aa) + a.Merge(aa) } -// Add sets up keyboard action listener. -func (a KeyActions) Add(aa KeyActions) { +// Range ranges over all actions and triggers a given function. +func (a *KeyActions) Range(f RangeFn) { + var km KeyMap + a.mx.RLock() + { + km = a.actions + } + a.mx.RUnlock() + + for k, v := range km { + f(k, v) + } +} + +// Add adds a new key action. +func (a *KeyActions) Add(k tcell.Key, ka KeyAction) { + a.mx.Lock() + defer a.mx.Unlock() + + a.actions[k] = ka +} + +// Bulk bulk insert key mappings. +func (a *KeyActions) Bulk(aa KeyMap) { + a.mx.Lock() + defer a.mx.Unlock() + for k, v := range aa { - a[k] = v + a.actions[k] = v + } +} + +// Merge merges given actions into existing set. +func (a *KeyActions) Merge(aa *KeyActions) { + a.mx.Lock() + defer a.mx.Unlock() + + for k, v := range aa.actions { + a.actions[k] = v } } // Clear remove all actions. -func (a KeyActions) Clear() { - for k := range a { - delete(a, k) +func (a *KeyActions) Clear() { + a.mx.Lock() + defer a.mx.Unlock() + + for k := range a.actions { + delete(a.actions, k) } } // ClearDanger remove all dangerous actions. -func (a KeyActions) ClearDanger() { - for k, v := range a { +func (a *KeyActions) ClearDanger() { + a.mx.Lock() + defer a.mx.Unlock() + + for k, v := range a.actions { if v.Opts.Dangerous { - delete(a, k) + delete(a.actions, k) } } } // Set replace actions with new ones. -func (a KeyActions) Set(aa KeyActions) { - for k, v := range aa { - a[k] = v +func (a *KeyActions) Set(aa *KeyActions) { + a.mx.Lock() + defer a.mx.Unlock() + + for k, v := range aa.actions { + a.actions[k] = v } } // Delete deletes actions by the given keys. -func (a KeyActions) Delete(kk ...tcell.Key) { +func (a *KeyActions) Delete(kk ...tcell.Key) { + a.mx.Lock() + defer a.mx.Unlock() + for _, k := range kk { - delete(a, k) + delete(a.actions, k) } } // Hints returns a collection of hints. -func (a KeyActions) Hints() model.MenuHints { - kk := make([]int, 0, len(a)) - for k := range a { - if !a[k].Opts.Shared { +func (a *KeyActions) Hints() model.MenuHints { + a.mx.RLock() + defer a.mx.RUnlock() + + kk := make([]int, 0, len(a.actions)) + for k := range a.actions { + if !a.actions[k].Opts.Shared { kk = append(kk, int(k)) } } @@ -116,13 +208,14 @@ func (a KeyActions) Hints() model.MenuHints { hh = append(hh, model.MenuHint{ Mnemonic: name, - Description: a[tcell.Key(k)].Description, - Visible: a[tcell.Key(k)].Opts.Visible, + Description: a.actions[tcell.Key(k)].Description, + Visible: a.actions[tcell.Key(k)].Opts.Visible, }, ) } else { log.Error().Msgf("Unable to locate KeyName for %#v", k) } } + return hh } diff --git a/internal/ui/action_test.go b/internal/ui/action_test.go index de031ebd88..60733aa8e0 100644 --- a/internal/ui/action_test.go +++ b/internal/ui/action_test.go @@ -12,11 +12,11 @@ import ( ) func TestKeyActionsHints(t *testing.T) { - kk := ui.KeyActions{ + kk := ui.NewKeyActionsFromMap(ui.KeyMap{ ui.KeyF: ui.NewKeyAction("fred", nil, true), ui.KeyB: ui.NewKeyAction("blee", nil, true), ui.KeyZ: ui.NewKeyAction("zorg", nil, false), - } + }) hh := kk.Hints() diff --git a/internal/ui/app.go b/internal/ui/app.go index 4a3b3e274e..e0c261ee64 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -22,7 +22,7 @@ type App struct { Main *Pages flash *model.Flash - actions KeyActions + actions *KeyActions views map[string]tview.Primitive cmdBuff *model.FishBuff running bool @@ -33,7 +33,7 @@ type App struct { func NewApp(cfg *config.Config, context string) *App { a := App{ Application: tview.NewApplication(), - actions: make(KeyActions), + actions: NewKeyActions(), Configurator: Configurator{Config: cfg, Styles: config.NewStyles()}, Main: NewPages(), flash: model.NewFlash(model.DefaultFlashDelay), @@ -139,13 +139,14 @@ func (a *App) Conn() client.Connection { } func (a *App) bindKeys() { - a.actions = KeyActions{ + a.actions = NewKeyActionsFromMap(KeyMap{ KeyColon: NewKeyAction("Cmd", a.activateCmd, false), tcell.KeyCtrlR: NewKeyAction("Redraw", a.redrawCmd, false), + tcell.KeyCtrlP: NewKeyAction("Persist", a.saveCmd, false), tcell.KeyCtrlC: NewKeyAction("Quit", a.quitCmd, false), tcell.KeyCtrlU: NewSharedKeyAction("Clear Filter", a.clearCmd, false), tcell.KeyCtrlQ: NewSharedKeyAction("Clear Filter", a.clearCmd, false), - } + }) } // BailOut exits the application. @@ -156,6 +157,7 @@ func (a *App) BailOut() { // ResetPrompt reset the prompt model and marks buffer as active. func (a *App) ResetPrompt(m PromptModel) { + m.ClearText(false) a.Prompt().SetModel(m) a.SetFocus(a.Prompt()) m.SetActive(true) @@ -166,6 +168,15 @@ func (a *App) ResetCmd() { a.cmdBuff.Reset() } +func (a *App) saveCmd(evt *tcell.EventKey) *tcell.EventKey { + if err := a.Config.Save(true); err != nil { + a.Flash().Err(err) + } + a.Flash().Info("current context config saved") + + return nil +} + // ActivateCmd toggle command mode. func (a *App) ActivateCmd(b bool) { a.cmdBuff.SetActive(b) @@ -206,20 +217,17 @@ func (a *App) InCmdMode() bool { // HasAction checks if key matches a registered binding. func (a *App) HasAction(key tcell.Key) (KeyAction, bool) { - act, ok := a.actions[key] - return act, ok + return a.actions.Get(key) } // GetActions returns a collection of actions. -func (a *App) GetActions() KeyActions { +func (a *App) GetActions() *KeyActions { return a.actions } // AddActions returns the application actions. -func (a *App) AddActions(aa KeyActions) { - for k, v := range aa { - a.actions[k] = v - } +func (a *App) AddActions(aa *KeyActions) { + a.actions.Merge(aa) } // Views return the application root views. diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index 36d5c6ff02..47f96fb401 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -54,9 +54,9 @@ func TestAppGetActions(t *testing.T) { a := ui.NewApp(mock.NewMockConfig(), "") a.Init() - a.AddActions(ui.KeyActions{ui.KeyZ: ui.KeyAction{Description: "zorg"}}) + a.GetActions().Add(ui.KeyZ, ui.KeyAction{Description: "zorg"}) - assert.Equal(t, 6, len(a.GetActions())) + assert.Equal(t, 7, a.GetActions().Len()) } func TestAppViews(t *testing.T) { diff --git a/internal/ui/config.go b/internal/ui/config.go index 70435617d4..487a1cc927 100644 --- a/internal/ui/config.go +++ b/internal/ui/config.go @@ -6,13 +6,14 @@ package ui import ( "context" "errors" + "io/fs" "os" "path/filepath" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/render" "github.com/fsnotify/fsnotify" "github.com/rs/zerolog/log" ) @@ -91,7 +92,7 @@ func (c *Configurator) RefreshCustomViews() error { // SkinsDirWatcher watches for skin directory file changes. func (c *Configurator) SkinsDirWatcher(ctx context.Context, s synchronizer) error { - if _, err := os.Stat(config.AppSkinsDir); os.IsNotExist(err) { + if _, err := os.Stat(config.AppSkinsDir); errors.Is(err, fs.ErrNotExist) { return err } w, err := fsnotify.NewWatcher() @@ -139,7 +140,7 @@ func (c *Configurator) ConfigWatcher(ctx context.Context, s synchronizer) error if evt.Has(fsnotify.Create) || evt.Has(fsnotify.Write) { log.Debug().Msgf("ConfigWatcher file changed: %s", evt.Name) if evt.Name == config.AppConfigFile { - if err := c.Config.Load(evt.Name); err != nil { + if err := c.Config.Load(evt.Name, false); err != nil { log.Error().Err(err).Msgf("k9s config reload failed") s.Flash().Warn("k9s config reload failed. Check k9s logs!") s.Logo().Warn("K9s config reload failed!") @@ -190,14 +191,14 @@ func (c *Configurator) activeSkin() (string, bool) { } if ct, err := c.Config.K9s.ActiveContext(); err == nil && ct.Skin != "" { - if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); !os.IsNotExist(err) { + if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); err == nil { skin = ct.Skin log.Debug().Msgf("[Skin] Loading context skin (%q) from %q", skin, c.Config.K9s.ActiveContextName()) } } if sk := c.Config.K9s.UI.Skin; skin == "" && sk != "" { - if _, err := os.Stat(config.SkinFileFromName(sk)); !os.IsNotExist(err) { + if _, err := os.Stat(config.SkinFileFromName(sk)); err == nil { skin = sk log.Debug().Msgf("[Skin] Loading global skin (%q)", skin) } @@ -272,12 +273,12 @@ func (c *Configurator) updateStyles(f string) { } c.Styles.Update() - render.ModColor = c.Styles.Frame().Status.ModifyColor.Color() - render.AddColor = c.Styles.Frame().Status.AddColor.Color() - render.ErrColor = c.Styles.Frame().Status.ErrorColor.Color() - render.StdColor = c.Styles.Frame().Status.NewColor.Color() - render.PendingColor = c.Styles.Frame().Status.PendingColor.Color() - render.HighlightColor = c.Styles.Frame().Status.HighlightColor.Color() - render.KillColor = c.Styles.Frame().Status.KillColor.Color() - render.CompletedColor = c.Styles.Frame().Status.CompletedColor.Color() + model1.ModColor = c.Styles.Frame().Status.ModifyColor.Color() + model1.AddColor = c.Styles.Frame().Status.AddColor.Color() + model1.ErrColor = c.Styles.Frame().Status.ErrorColor.Color() + model1.StdColor = c.Styles.Frame().Status.NewColor.Color() + model1.PendingColor = c.Styles.Frame().Status.PendingColor.Color() + model1.HighlightColor = c.Styles.Frame().Status.HighlightColor.Color() + model1.KillColor = c.Styles.Frame().Status.KillColor.Color() + model1.CompletedColor = c.Styles.Frame().Status.CompletedColor.Color() } diff --git a/internal/ui/config_test.go b/internal/ui/config_test.go index a38d26a1bd..3e95694989 100644 --- a/internal/ui/config_test.go +++ b/internal/ui/config_test.go @@ -13,7 +13,7 @@ import ( "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" "github.com/stretchr/testify/assert" @@ -47,8 +47,8 @@ func TestSkinnedContext(t *testing.T) { cfg.Config.K9s.UI = config.UI{Skin: "black-and-wtf"} cfg.RefreshStyles(newMockSynchronizer()) assert.True(t, cfg.HasSkin()) - assert.Equal(t, tcell.ColorGhostWhite.TrueColor(), render.StdColor) - assert.Equal(t, tcell.ColorWhiteSmoke.TrueColor(), render.ErrColor) + assert.Equal(t, tcell.ColorGhostWhite.TrueColor(), model1.StdColor) + assert.Equal(t, tcell.ColorWhiteSmoke.TrueColor(), model1.ErrColor) } func TestBenchConfig(t *testing.T) { diff --git a/internal/ui/menu_test.go b/internal/ui/menu_test.go index 599e2d2c62..9a4788035c 100644 --- a/internal/ui/menu_test.go +++ b/internal/ui/menu_test.go @@ -27,16 +27,16 @@ func TestNewMenu(t *testing.T) { func TestActionHints(t *testing.T) { uu := map[string]struct { - aa ui.KeyActions + aa *ui.KeyActions e model.MenuHints }{ "a": { - aa: ui.KeyActions{ + aa: ui.NewKeyActionsFromMap(ui.KeyMap{ ui.KeyB: ui.NewKeyAction("bleeB", nil, true), ui.KeyA: ui.NewKeyAction("bleeA", nil, true), ui.Key0: ui.NewKeyAction("zero", nil, true), ui.Key1: ui.NewKeyAction("one", nil, false), - }, + }), e: model.MenuHints{ {Mnemonic: "0", Description: "zero", Visible: true}, {Mnemonic: "1", Description: "one", Visible: false}, diff --git a/internal/ui/padding.go b/internal/ui/padding.go index b57cdb1f72..25cacdc541 100644 --- a/internal/ui/padding.go +++ b/internal/ui/padding.go @@ -7,6 +7,7 @@ import ( "strings" "unicode" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" ) @@ -14,26 +15,27 @@ import ( type MaxyPad []int // ComputeMaxColumns figures out column max size and necessary padding. -func ComputeMaxColumns(pads MaxyPad, sortColName string, header render.Header, ee render.RowEvents) { +func ComputeMaxColumns(pads MaxyPad, sortColName string, t *model1.TableData) { const colPadding = 1 - for index, h := range header { - pads[index] = len(h.Name) - if h.Name == sortColName { - pads[index] = len(h.Name) + 2 + for i, n := range t.ColumnNames(true) { + pads[i] = len(n) + if n == sortColName { + pads[i] += 2 } } var row int - for _, e := range ee { - for index, field := range e.Row.Fields { + t.RowsRange(func(_ int, re model1.RowEvent) bool { + for index, field := range re.Row.Fields { width := len(field) + colPadding if index < len(pads) && width > pads[index] { pads[index] = width } } row++ - } + return true + }) } // IsASCII checks if table cell has all ascii characters. diff --git a/internal/ui/padding_test.go b/internal/ui/padding_test.go index 51a0bcde43..0dbcb87a38 100644 --- a/internal/ui/padding_test.go +++ b/internal/ui/padding_test.go @@ -6,70 +6,74 @@ package ui import ( "testing" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" "github.com/stretchr/testify/assert" ) func TestMaxColumn(t *testing.T) { uu := map[string]struct { - t *render.TableData + t *model1.TableData s string e MaxyPad }{ "ascii col 0": { - &render.TableData{ - Header: render.Header{render.HeaderColumn{Name: "A"}, render.HeaderColumn{Name: "B"}}, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"hello", "world"}, + model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{model1.HeaderColumn{Name: "A"}, model1.HeaderColumn{Name: "B"}}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"hello", "world"}, }, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"yo", "mama"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"yo", "mama"}, }, }, - }, - }, + ), + ), "A", MaxyPad{6, 6}, }, "ascii col 1": { - &render.TableData{ - Header: render.Header{render.HeaderColumn{Name: "A"}, render.HeaderColumn{Name: "B"}}, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"hello", "world"}, + model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{model1.HeaderColumn{Name: "A"}, model1.HeaderColumn{Name: "B"}}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"hello", "world"}, }, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"yo", "mama"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"yo", "mama"}, }, }, - }, - }, + ), + ), "B", MaxyPad{6, 6}, }, "non_ascii": { - &render.TableData{ - Header: render.Header{render.HeaderColumn{Name: "A"}, render.HeaderColumn{Name: "B"}}, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"Hello World lord of ipsums 😅", "world"}, + model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{model1.HeaderColumn{Name: "A"}, model1.HeaderColumn{Name: "B"}}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"Hello World lord of ipsums 😅", "world"}, }, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"o", "mama"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"o", "mama"}, }, }, - }, - }, + ), + ), "A", MaxyPad{32, 6}, }, @@ -78,8 +82,8 @@ func TestMaxColumn(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - pads := make(MaxyPad, len(u.t.Header)) - ComputeMaxColumns(pads, u.s, u.t.Header, u.t.RowEvents) + pads := make(MaxyPad, u.t.HeaderCount()) + ComputeMaxColumns(pads, u.s, u.t) assert.Equal(t, u.e, pads) }) } @@ -119,27 +123,28 @@ func TestPad(t *testing.T) { } func BenchmarkMaxColumn(b *testing.B) { - table := render.TableData{ - Header: render.Header{render.HeaderColumn{Name: "A"}, render.HeaderColumn{Name: "B"}}, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"hello", "world"}, + table := model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{model1.HeaderColumn{Name: "A"}, model1.HeaderColumn{Name: "B"}}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"hello", "world"}, }, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"yo", "mama"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"yo", "mama"}, }, }, - }, - } + ), + ) - pads := make(MaxyPad, len(table.Header)) + pads := make(MaxyPad, table.HeaderCount()) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - ComputeMaxColumns(pads, "A", table.Header, table.RowEvents) + ComputeMaxColumns(pads, "A", table) } } diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index 25f33c0f19..d1df36c480 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -107,7 +107,7 @@ func (s *SelectTable) SelectRow(r, c int, broadcast bool) { if !broadcast { s.SetSelectionChangedFunc(nil) } - if c := s.model.Count(); c > 0 && r-1 > c { + if c := s.model.RowCount(); c > 0 && r-1 > c { r = c + 1 } defer s.SetSelectionChangedFunc(s.selectionChanged) diff --git a/internal/ui/table.go b/internal/ui/table.go index 66b0450308..0af73164ec 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -5,15 +5,14 @@ package ui import ( "context" - "errors" "fmt" - "strings" + "sync" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/vul" "github.com/derailed/tcell/v2" @@ -27,10 +26,10 @@ const maxTruncate = 50 type ( // ColorerFunc represents a row colorer. - ColorerFunc func(ns string, evt render.RowEvent) tcell.Color + ColorerFunc func(ns string, evt model1.RowEvent) tcell.Color // DecorateFunc represents a row decorator. - DecorateFunc func(*render.TableData) + DecorateFunc func(*model1.TableData) // SelectedRowFunc a table selection callback. SelectedRowFunc func(r int) @@ -39,21 +38,22 @@ type ( // Table represents tabular data. type Table struct { gvr client.GVR - sortCol SortColumn + sortCol model1.SortColumn manualSort bool - header render.Header Path string Extras string *SelectTable - actions KeyActions + actions *KeyActions cmdBuff *model.FishBuff styles *config.Styles viewSetting *config.ViewSetting - colorerFn render.ColorerFunc + colorerFn model1.ColorerFunc decorateFn DecorateFunc wide bool toast bool hasMetrics bool + ctx context.Context + mx sync.RWMutex } // NewTable returns a new table view. @@ -65,12 +65,69 @@ func NewTable(gvr client.GVR) *Table { marks: make(map[string]struct{}), }, gvr: gvr, - actions: make(KeyActions), + actions: NewKeyActions(), cmdBuff: model.NewFishBuff('/', model.FilterBuffer), - sortCol: SortColumn{asc: true}, + sortCol: model1.SortColumn{ASC: true}, } } +func (t *Table) setSortCol(sc model1.SortColumn) { + t.mx.Lock() + defer t.mx.Unlock() + + t.sortCol = sc +} + +func (t *Table) toggleSortCol() { + t.mx.Lock() + defer t.mx.Unlock() + + t.sortCol.ASC = !t.sortCol.ASC +} + +func (t *Table) getSortCol() model1.SortColumn { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.sortCol +} + +func (t *Table) setMSort(b bool) { + t.mx.Lock() + defer t.mx.Unlock() + + t.manualSort = b +} + +func (t *Table) getMSort() bool { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.manualSort +} + +func (t *Table) setVs(vs *config.ViewSetting) { + t.mx.Lock() + defer t.mx.Unlock() + + t.viewSetting = vs +} + +func (t *Table) getVs() *config.ViewSetting { + t.mx.RLock() + defer t.mx.RUnlock() + + return t.viewSetting +} + +func (t *Table) GetContext() context.Context { + return t.ctx +} + +func (t *Table) SetContext(ctx context.Context) { + t.ctx = ctx +} + // Init initializes the component. func (t *Table) Init(ctx context.Context) { t.SetFixed(1, 0) @@ -92,8 +149,9 @@ func (t *Table) Init(ctx context.Context) { func (t *Table) GVR() client.GVR { return t.gvr } // ViewSettingsChanged notifies listener the view configuration changed. -func (t *Table) ViewSettingsChanged(settings config.ViewSetting) { - t.viewSetting, t.manualSort = &settings, false +func (t *Table) ViewSettingsChanged(vs config.ViewSetting) { + t.setVs(&vs) + t.setMSort(false) t.Refresh() } @@ -129,7 +187,7 @@ func (t *Table) ToggleWide() { } // Actions returns active menu bindings. -func (t *Table) Actions() KeyActions { +func (t *Table) Actions() *KeyActions { return t.actions } @@ -171,7 +229,7 @@ func (t *Table) ExtraHints() map[string]string { } // GetFilteredData fetch filtered tabular data. -func (t *Table) GetFilteredData() *render.TableData { +func (t *Table) GetFilteredData() *model1.TableData { return t.filtered(t.GetModel().Peek()) } @@ -181,67 +239,51 @@ func (t *Table) SetDecorateFn(f DecorateFunc) { } // SetColorerFn specifies the default colorer. -func (t *Table) SetColorerFn(f render.ColorerFunc) { +func (t *Table) SetColorerFn(f model1.ColorerFunc) { t.colorerFn = f } // SetSortCol sets in sort column index and order. func (t *Table) SetSortCol(name string, asc bool) { - t.sortCol.name, t.sortCol.asc = name, asc + t.setSortCol(model1.SortColumn{Name: name, ASC: asc}) } // Update table content. -func (t *Table) Update(data *render.TableData, hasMetrics bool) { - t.header = data.Header +func (t *Table) Update(data *model1.TableData, hasMetrics bool) *model1.TableData { if t.decorateFn != nil { t.decorateFn(data) } t.hasMetrics = hasMetrics - t.doUpdate(t.filtered(data)) - t.UpdateTitle() + + return t.doUpdate(t.filtered(data)) } -func (t *Table) doUpdate(data *render.TableData) { - if client.IsAllNamespaces(data.Namespace) { - t.actions[KeyShiftP] = NewKeyAction("Sort Namespace", t.SortColCmd("NAMESPACE", true), false) +func (t *Table) doUpdate(data *model1.TableData) *model1.TableData { + if client.IsAllNamespaces(data.GetNamespace()) { + t.actions.Add( + KeyShiftP, + NewKeyAction("Sort Namespace", t.SortColCmd("NAMESPACE", true), false), + ) } else { t.actions.Delete(KeyShiftP) } - cols := t.header.Columns(t.wide) - if t.viewSetting != nil && len(t.viewSetting.Columns) > 0 { - cols = t.viewSetting.Columns - } - custData := data.Customize(cols, t.wide) - // The sortColumn settings in the configuration file are only used - // if the sortCol has not been modified manually - if t.viewSetting != nil && t.viewSetting.SortColumn != "" && !t.manualSort { - tokens := strings.Split(t.viewSetting.SortColumn, ":") - if custData.Header.IndexOf(tokens[0], false) >= 0 && !t.manualSort { - t.sortCol.name, t.sortCol.asc = tokens[0], true - if len(tokens) == 2 && tokens[1] == "desc" { - t.sortCol.asc = false - } - } - } + cdata, sortCol := data.Customize(t.getVs(), t.getSortCol(), t.getMSort(), true) + t.setSortCol(sortCol) - if t.sortCol.name == "" && client.IsAllNamespaces(data.Namespace) { - t.sortCol.name = "NAMESPACE" - } - if t.sortCol.name == "" || (t.sortCol.name == "NAMESPACE" && !client.IsAllNamespaces(data.Namespace)) && len(custData.Header) > 0 { - if idx := custData.Header.IndexOf("NAME", false); idx >= 0 { - t.sortCol.name = custData.Header[idx].Name - } else { - t.sortCol.name = custData.Header[0].Name - } - } + return cdata +} +func (t *Table) UpdateUI(cdata, data *model1.TableData) { t.Clear() fg := t.styles.Table().Header.FgColor.Color() bg := t.styles.Table().Header.BgColor.Color() var col int - for _, h := range custData.Header { + for _, h := range cdata.Header() { + if !t.wide && h.Wide { + continue + } if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() { continue } @@ -258,38 +300,42 @@ func (t *Table) doUpdate(data *render.TableData) { c.SetTextColor(fg) col++ } - colIndex := custData.Header.IndexOf(t.sortCol.name, false) - custData.RowEvents.Sort( - custData.Namespace, - colIndex, - custData.Header.IsTimeCol(colIndex), - custData.Header.IsMetricsCol(colIndex), - custData.Header.IsCapacityCol(colIndex), - t.sortCol.asc, - ) - - pads := make(MaxyPad, len(custData.Header)) - ComputeMaxColumns(pads, t.sortCol.name, custData.Header, custData.RowEvents) - for row, re := range custData.RowEvents { - idx, _ := data.RowEvents.FindIndex(re.Row.ID) - t.buildRow(row+1, re, data.RowEvents[idx], custData.Header, pads) - } + cdata.Sort(t.getSortCol()) + + pads := make(MaxyPad, cdata.HeaderCount()) + ComputeMaxColumns(pads, t.getSortCol().Name, cdata) + cdata.RowsRange(func(row int, re model1.RowEvent) bool { + ore, ok := data.FindRow(re.Row.ID) + if !ok { + log.Error().Msgf("unable to find original re: %q", re.Row.ID) + return true + } + t.buildRow(row+1, re, ore, cdata.Header(), pads) + + return true + }) + t.updateSelection(true) + t.UpdateTitle() } -func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads MaxyPad) { - color := render.DefaultColorer +func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads MaxyPad) { + color := model1.DefaultColorer if t.colorerFn != nil { color = t.colorerFn } marked := t.IsMarked(re.Row.ID) var col int + ns := t.GetModel().GetNamespace() for c, field := range re.Row.Fields { if c >= len(h) { log.Error().Msgf("field/header overflow detected for %q -- %d::%d. Check your mappings!", t.GVR(), c, len(h)) continue } + if !t.wide && h[c].Wide { + continue + } if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() { continue @@ -315,7 +361,7 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M cell := tview.NewTableCell(field) cell.SetExpansion(1) cell.SetAlign(h[c].Align) - fgColor := color(t.GetModel().GetNamespace(), t.header, ore) + fgColor := color(ns, h, &re) cell.SetTextColor(fgColor) if marked { cell.SetTextColor(t.styles.Table().MarkColor.Color()) @@ -331,13 +377,14 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M // SortColCmd designates a sorted column. func (t *Table) SortColCmd(name string, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { - t.manualSort = true - t.sortCol.asc = !t.sortCol.asc - if t.sortCol.name != name { - t.sortCol.asc = asc + sc := t.getSortCol() + sc.ASC = !sc.ASC + if sc.Name != name { + sc.ASC = asc } - t.sortCol.name = name - t.manualSort = true + sc.Name = name + t.setSortCol(sc) + t.setMSort(true) t.Refresh() return nil } @@ -345,7 +392,7 @@ func (t *Table) SortColCmd(name string, asc bool) func(evt *tcell.EventKey) *tce // SortInvertCmd reverses sorting order. func (t *Table) SortInvertCmd(evt *tcell.EventKey) *tcell.EventKey { - t.sortCol.asc = !t.sortCol.asc + t.toggleSortCol() t.Refresh() return nil @@ -360,21 +407,23 @@ func (t *Table) ClearMarks() { // Refresh update the table data. func (t *Table) Refresh() { data := t.model.Peek() - if len(data.Header) == 0 { + if data.HeaderCount() == 0 { return } // BOZO!! Really want to tell model reload now. Refactor! - t.Update(data, t.hasMetrics) + cdata := t.Update(data, t.hasMetrics) + t.UpdateUI(cdata, data) } // GetSelectedRow returns the entire selected row or nil if nothing selected. -func (t *Table) GetSelectedRow(path string) *render.Row { +func (t *Table) GetSelectedRow(path string) *model1.Row { data := t.model.Peek() - i, ok := data.RowEvents.FindIndex(path) + re, ok := data.FindRow(path) if !ok { return nil } - return &data.RowEvents[i].Row + + return &re.Row } // NameColIndex returns the index of the resource name column. @@ -386,38 +435,25 @@ func (t *Table) NameColIndex() int { if t.GetModel().ClusterWide() { col++ } + return col } // AddHeaderCell configures a table cell header. -func (t *Table) AddHeaderCell(col int, h render.HeaderColumn) { - sortCol := h.Name == t.sortCol.name - c := tview.NewTableCell(sortIndicator(sortCol, t.sortCol.asc, t.styles.Table(), h.Name)) +func (t *Table) AddHeaderCell(col int, h model1.HeaderColumn) { + sc := t.getSortCol() + sortCol := h.Name == sc.Name + c := tview.NewTableCell(sortIndicator(sortCol, sc.ASC, t.styles.Table(), h.Name)) c.SetExpansion(1) c.SetAlign(h.Align) t.SetCell(0, col, c) } -func (t *Table) filtered(data *render.TableData) *render.TableData { - filtered := data - if t.toast { - filtered = filterToast(data) - } - if t.cmdBuff.Empty() || IsLabelSelector(t.cmdBuff.GetText()) { - return filtered - } - - q := t.cmdBuff.GetText() - if f, ok := dao.HasFuzzySelector(q); ok { - return fuzzyFilter(f, filtered) - } - - filtered, err := rxFilter(q, dao.IsInverseSelector(q), filtered) - if err != nil { - log.Error().Err(errors.New("invalid filter expression")).Msg("Regexp") - } - - return filtered +func (t *Table) filtered(data *model1.TableData) *model1.TableData { + return data.Filter(model1.FilterOpts{ + Toast: t.toast, + Filter: t.cmdBuff.GetText(), + }) } // CmdBuff returns the associated command buffer. @@ -470,7 +506,7 @@ func (t *Table) styleTitle() string { } buff := t.cmdBuff.GetText() - if IsLabelSelector(buff) { + if internal.IsLabelSelector(buff) { buff = render.Truncate(TrimLabelSelector(buff), maxTruncate) } else if l := t.GetModel().GetLabelFilter(); l != "" { buff = render.Truncate(l, maxTruncate) diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index bd6ea48118..479ed2d0bd 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -6,15 +6,11 @@ package ui import ( "context" "fmt" - "regexp" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/view/cmd" "github.com/rs/zerolog/log" - "github.com/sahilm/fuzzy" ) const ( @@ -40,11 +36,6 @@ const ( NoNSFmat = "%s-%d.csv" ) -var ( - // LabelRx identifies a label query. - LabelRx = regexp.MustCompile(`\A\-l`) -) - func mustExtractStyles(ctx context.Context) *config.Styles { styles, ok := ctx.Value(internal.KeyStyles).(*config.Styles) if !ok { @@ -63,15 +54,6 @@ func TrimCell(tv *SelectTable, row, col int) string { return strings.TrimSpace(c.Text) } -// IsLabelSelector checks if query is a label query. -func IsLabelSelector(s string) bool { - if LabelRx.MatchString(s) { - return true - } - - return !strings.Contains(s, " ") && cmd.ToLabels(s) != nil -} - // TrimLabelSelector extracts label query. func TrimLabelSelector(s string) string { if strings.Index(s, "-l") == 0 { @@ -116,75 +98,3 @@ func formatCell(field string, padding int) string { return field } - -func filterToast(data *render.TableData) *render.TableData { - validX := data.Header.IndexOf("VALID", true) - if validX == -1 { - return data - } - - toast := render.TableData{ - Header: data.Header, - RowEvents: make(render.RowEvents, 0, len(data.RowEvents)), - Namespace: data.Namespace, - } - for _, re := range data.RowEvents { - if re.Row.Fields[validX] != "" { - toast.RowEvents = append(toast.RowEvents, re) - } - } - - return &toast -} - -func rxFilter(q string, inverse bool, data *render.TableData) (*render.TableData, error) { - if inverse { - q = q[1:] - } - rx, err := regexp.Compile(`(?i)(` + q + `)`) - if err != nil { - return data, fmt.Errorf("%w -- %s", err, q) - } - - filtered := render.TableData{ - Header: data.Header, - RowEvents: make(render.RowEvents, 0, len(data.RowEvents)), - Namespace: data.Namespace, - } - ageIndex := data.Header.IndexOf("AGE", true) - - const spacer = " " - for _, re := range data.RowEvents { - ff := re.Row.Fields - if ageIndex >= 0 && ageIndex+1 <= len(ff) { - ff = append(ff[0:ageIndex], ff[ageIndex+1:]...) - } - fields := strings.Join(ff, spacer) - if (inverse && !rx.MatchString(fields)) || - ((!inverse) && rx.MatchString(fields)) { - filtered.RowEvents = append(filtered.RowEvents, re) - } - } - - return &filtered, nil -} - -func fuzzyFilter(q string, data *render.TableData) *render.TableData { - q = strings.TrimSpace(q) - ss := make([]string, 0, len(data.RowEvents)) - for _, re := range data.RowEvents { - ss = append(ss, re.Row.ID) - } - - filtered := render.TableData{ - Header: data.Header, - RowEvents: make(render.RowEvents, 0, len(data.RowEvents)), - Namespace: data.Namespace, - } - mm := fuzzy.Find(q, ss) - for _, m := range mm { - filtered.RowEvents = append(filtered.RowEvents, data.RowEvents[m.Index]) - } - - return &filtered -} diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index 219ffaa3d3..7bec2d4082 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -33,28 +33,6 @@ func TestTruncate(t *testing.T) { } } -func TestIsLabelSelector(t *testing.T) { - uu := map[string]struct { - s string - ok bool - }{ - "empty": {s: ""}, - "cool": {s: "-l app=fred,env=blee", ok: true}, - "no-flag": {s: "app=fred,env=blee", ok: true}, - "no-space": {s: "-lapp=fred,env=blee", ok: true}, - "wrong-flag": {s: "-f app=fred,env=blee"}, - "missing-key": {s: "=fred"}, - "missing-val": {s: "fred="}, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.ok, IsLabelSelector(u.s)) - }) - } -} - func TestTrimLabelSelector(t *testing.T) { uu := map[string]struct { sel, e string diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index f6169abdef..9b604d84a9 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -13,7 +13,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,10 +32,11 @@ func TestTableUpdate(t *testing.T) { v.Init(makeContext()) data := makeTableData() - v.Update(data, false) + cdata := v.Update(data, false) + v.UpdateUI(cdata, data) - assert.Equal(t, len(data.RowEvents)+1, v.GetRowCount()) - assert.Equal(t, len(data.Header), v.GetColumnCount()) + assert.Equal(t, data.RowCount()+1, v.GetRowCount()) + assert.Equal(t, data.HeaderCount(), v.GetColumnCount()) } func TestTableSelection(t *testing.T) { @@ -43,12 +44,14 @@ func TestTableSelection(t *testing.T) { v.Init(makeContext()) m := &mockModel{} v.SetModel(m) - v.Update(m.Peek(), false) + data := m.Peek() + cdata := v.Update(data, false) + v.UpdateUI(cdata, data) v.SelectRow(1, 0, true) r := v.GetSelectedRow("r1") if r != nil { - assert.Equal(t, render.Row{ID: "r1", Fields: render.Fields{"blee", "duh", "fred"}}, *r) + assert.Equal(t, model1.Row{ID: "r1", Fields: model1.Fields{"blee", "duh", "fred"}}, *r) } assert.Equal(t, "r1", v.GetSelectedItem()) assert.Equal(t, "blee", v.GetSelectedCell(0)) @@ -71,9 +74,9 @@ func (t *mockModel) SetInstance(string) {} func (t *mockModel) SetLabelFilter(string) {} func (t *mockModel) GetLabelFilter() string { return "" } func (t *mockModel) Empty() bool { return false } -func (t *mockModel) Count() int { return 1 } +func (t *mockModel) RowCount() int { return 1 } func (t *mockModel) HasMetrics() bool { return true } -func (t *mockModel) Peek() *render.TableData { return makeTableData() } +func (t *mockModel) Peek() *model1.TableData { return makeTableData() } func (t *mockModel) Refresh(context.Context) error { return nil } func (t *mockModel) ClusterWide() bool { return false } func (t *mockModel) GetNamespace() string { return "blee" } @@ -97,30 +100,29 @@ func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) { func (t *mockModel) InNamespace(string) bool { return true } func (t *mockModel) SetRefreshRate(time.Duration) {} -func makeTableData() *render.TableData { - t := render.NewTableData() - t.Namespace = "" - t.Header = render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - } - t.RowEvents = render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - ID: "r1", - Fields: render.Fields{"blee", "duh", "fred"}, - }, +func makeTableData() *model1.TableData { + return model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{ + model1.HeaderColumn{Name: "A"}, + model1.HeaderColumn{Name: "B"}, + model1.HeaderColumn{Name: "C"}, }, - render.RowEvent{ - Row: render.Row{ - ID: "r2", - Fields: render.Fields{"blee", "duh", "zorg"}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + ID: "r1", + Fields: model1.Fields{"blee", "duh", "fred"}, + }, }, - }, - } - - return t + model1.RowEvent{ + Row: model1.Row{ + ID: "r2", + Fields: model1.Fields{"blee", "duh", "zorg"}, + }, + }, + ), + ) } func makeContext() context.Context { diff --git a/internal/ui/tree.go b/internal/ui/tree.go index 10eb31e764..5af3046b04 100644 --- a/internal/ui/tree.go +++ b/internal/ui/tree.go @@ -18,7 +18,7 @@ type KeyListenerFunc func() type Tree struct { *tview.TreeView - actions KeyActions + actions *KeyActions selectedItem string cmdBuff *model.FishBuff expandNodes bool @@ -31,7 +31,7 @@ func NewTree() *Tree { return &Tree{ TreeView: tview.NewTreeView(), expandNodes: true, - actions: make(KeyActions), + actions: NewKeyActions(), cmdBuff: model.NewFishBuff('/', model.FilterBuffer), } } @@ -75,7 +75,7 @@ func (t *Tree) SetKeyListenerFn(f KeyListenerFunc) { } // Actions returns active menu bindings. -func (t *Tree) Actions() KeyActions { +func (t *Tree) Actions() *KeyActions { return t.actions } @@ -91,14 +91,14 @@ func (t *Tree) ExtraHints() map[string]string { // BindKeys binds default mnemonics. func (t *Tree) BindKeys() { - t.Actions().Add(KeyActions{ + t.Actions().Merge(NewKeyActionsFromMap(KeyMap{ KeySpace: NewKeyAction("Expand/Collapse", t.noopCmd, true), KeyX: NewKeyAction("Expand/Collapse All", t.toggleCollapseCmd, true), - }) + })) } func (t *Tree) keyboard(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := t.actions[AsKey(evt)]; ok { + if a, ok := t.actions.Get(AsKey(evt)); ok { return a.Action(evt) } diff --git a/internal/ui/types.go b/internal/ui/types.go index 8013c5e7e8..534d084e29 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -9,22 +9,11 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) -type ( - // SortFn represent a function that can sort columnar data. - SortFn func(rows render.Rows, sortCol SortColumn) - - // SortColumn represents a sortable column. - SortColumn struct { - name string - asc bool - } -) - // Namespaceable represents a namespaceable model. type Namespaceable interface { // ClusterWide returns true if the model represents resource in all namespaces. @@ -63,11 +52,11 @@ type Tabular interface { // Empty returns true if model has no data. Empty() bool - // Count returns the model data count. - Count() int + // RowCount returns the model data count. + RowCount() int // Peek returns current model data. - Peek() *render.TableData + Peek() *model1.TableData // Watch watches a given resource for changes. Watch(context.Context) error diff --git a/internal/view/actions.go b/internal/view/actions.go index 6709b4451b..7d76619889 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -57,13 +57,13 @@ func inScope(scopes []string, aliases map[string]struct{}) bool { return false } -func hotKeyActions(r Runner, aa ui.KeyActions) error { +func hotKeyActions(r Runner, aa *ui.KeyActions) error { hh := config.NewHotKeys() - for k, a := range aa { + aa.Range(func(k tcell.Key, a ui.KeyAction) { if a.Opts.HotKey { - delete(aa, k) + aa.Delete(k) } - } + }) var errs error if err := hh.Load(r.App().Config.ContextHotkeysPath()); err != nil { @@ -75,7 +75,7 @@ func hotKeyActions(r Runner, aa ui.KeyActions) error { errs = errors.Join(errs, err) continue } - if _, ok := aa[key]; ok { + if _, ok := aa.Get(key); ok { if !hk.Override { errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k)) continue @@ -89,14 +89,14 @@ func hotKeyActions(r Runner, aa ui.KeyActions) error { continue } - aa[key] = ui.NewKeyActionWithOpts( + aa.Add(key, ui.NewKeyActionWithOpts( hk.Description, gotoCmd(r, command, "", !hk.KeepHistory), ui.ActionOpts{ Shared: true, HotKey: true, }, - ) + )) } return errs @@ -109,18 +109,23 @@ func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler { } } -func pluginActions(r Runner, aa ui.KeyActions) error { +func pluginActions(r Runner, aa *ui.KeyActions) error { pp := config.NewPlugins() - for k, a := range aa { + aa.Range(func(k tcell.Key, a ui.KeyAction) { if a.Opts.Plugin { - delete(aa, k) + aa.Delete(k) } + }) + + path, err := r.App().Config.ContextPluginsPath() + if err != nil { + return err + } + if err := pp.Load(path); err != nil { + return err } var errs error - if err := pp.Load(r.App().Config.ContextPluginsPath()); err != nil { - errs = errors.Join(errs, err) - } aliases := r.Aliases() for k, plugin := range pp.Plugins { if !inScope(plugin.Scopes, aliases) { @@ -131,7 +136,7 @@ func pluginActions(r Runner, aa ui.KeyActions) error { errs = errors.Join(errs, err) continue } - if _, ok := aa[key]; ok { + if _, ok := aa.Get(key); ok { if !plugin.Override { errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k)) continue @@ -139,13 +144,14 @@ func pluginActions(r Runner, aa ui.KeyActions) error { log.Info().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) } - aa[key] = ui.NewKeyActionWithOpts( + aa.Add(key, ui.NewKeyActionWithOpts( plugin.Description, pluginAction(r, plugin), ui.ActionOpts{ Visible: true, Plugin: true, - }) + }, + )) } return errs diff --git a/internal/view/alias.go b/internal/view/alias.go index 7e654cba4d..496f2e5c73 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -47,10 +47,10 @@ func (a *Alias) aliasContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyAliases, a.App().command.alias) } -func (a *Alias) bindKeys(aa ui.KeyActions) { +func (a *Alias) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true), ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd("RESOURCE", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd("COMMAND", true), false), diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 15cd393694..6deba28983 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -13,7 +13,7 @@ import ( "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view" "github.com/derailed/tcell/v2" @@ -93,9 +93,9 @@ func (t *mockModel) SetInstance(string) {} func (t *mockModel) SetLabelFilter(string) {} func (t *mockModel) GetLabelFilter() string { return "" } func (t *mockModel) Empty() bool { return false } -func (t *mockModel) Count() int { return 1 } +func (t *mockModel) RowCount() int { return 1 } func (t *mockModel) HasMetrics() bool { return true } -func (t *mockModel) Peek() *render.TableData { return makeTableData() } +func (t *mockModel) Peek() *model1.TableData { return makeTableData() } func (t *mockModel) ClusterWide() bool { return false } func (t *mockModel) GetNamespace() string { return "blee" } func (t *mockModel) SetNamespace(string) {} @@ -123,27 +123,27 @@ func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) { func (t *mockModel) InNamespace(string) bool { return true } func (t *mockModel) SetRefreshRate(time.Duration) {} -func makeTableData() *render.TableData { - return &render.TableData{ - Namespace: client.ClusterScope, - Header: render.Header{ - render.HeaderColumn{Name: "RESOURCE"}, - render.HeaderColumn{Name: "COMMAND"}, - render.HeaderColumn{Name: "APIGROUP"}, +func makeTableData() *model1.TableData { + return model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{ + model1.HeaderColumn{Name: "RESOURCE"}, + model1.HeaderColumn{Name: "COMMAND"}, + model1.HeaderColumn{Name: "APIGROUP"}, }, - RowEvents: render.RowEvents{ - render.RowEvent{ - Row: render.Row{ + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ ID: "r1", - Fields: render.Fields{"blee", "duh", "fred"}, + Fields: model1.Fields{"blee", "duh", "fred"}, }, }, - render.RowEvent{ - Row: render.Row{ + model1.RowEvent{ + Row: model1.Row{ ID: "r2", - Fields: render.Fields{"fred", "duh", "zorg"}, + Fields: model1.Fields{"fred", "duh", "zorg"}, }, }, - }, - } + ), + ) } diff --git a/internal/view/app.go b/internal/view/app.go index 1cfb2dcdd7..fef223111a 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -243,14 +243,14 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey { } func (a *App) bindKeys() { - a.AddActions(ui.KeyActions{ + a.AddActions(ui.NewKeyActionsFromMap(ui.KeyMap{ ui.KeyShift9: ui.NewSharedKeyAction("DumpGOR", a.dumpGOR, false), tcell.KeyCtrlE: ui.NewSharedKeyAction("ToggleHeader", a.toggleHeaderCmd, false), tcell.KeyCtrlG: ui.NewSharedKeyAction("toggleCrumbs", a.toggleCrumbsCmd, false), ui.KeyHelp: ui.NewSharedKeyAction("Help", a.helpCmd, false), tcell.KeyCtrlA: ui.NewSharedKeyAction("Aliases", a.aliasCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, false), - }) + })) } func (a *App) dumpGOR(evt *tcell.EventKey) *tcell.EventKey { @@ -483,7 +483,7 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { return err } } - if err := a.Config.Save(); err != nil { + if err := a.Config.Save(true); err != nil { log.Error().Err(err).Msg("config save failed!") } else { log.Debug().Msgf("Saved context config for: %q", name) @@ -516,7 +516,7 @@ func (a *App) BailOut() { } }() - if err := a.Config.Save(); err != nil { + if err := a.Config.Save(true); err != nil { log.Error().Err(err).Msg("config save failed!") } @@ -721,7 +721,6 @@ func (a *App) inject(c model.Component, clearStack bool) error { if clearStack { a.Content.Stack.Clear() } - a.Content.Push(c) return nil diff --git a/internal/view/app_test.go b/internal/view/app_test.go index 924fb8f2cc..e1e932f0e6 100644 --- a/internal/view/app_test.go +++ b/internal/view/app_test.go @@ -15,5 +15,5 @@ func TestAppNew(t *testing.T) { a := view.NewApp(mock.NewMockConfig()) _ = a.Init("blee", 10) - assert.Equal(t, 11, len(a.GetActions())) + assert.Equal(t, 12, a.GetActions().Len()) } diff --git a/internal/view/browser.go b/internal/view/browser.go index 932fbbb6cd..b6045a6de5 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -17,7 +17,7 @@ import ( "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" @@ -35,6 +35,7 @@ type Browser struct { contextFn ContextFunc cancelFn context.CancelFunc mx sync.RWMutex + updating bool } // NewBrowser returns a new browser. @@ -44,6 +45,18 @@ func NewBrowser(gvr client.GVR) ResourceViewer { } } +func (b *Browser) setUpdating(f bool) { + b.mx.Lock() + defer b.mx.Unlock() + b.updating = f +} + +func (b *Browser) getUpdating() bool { + b.mx.RLock() + defer b.mx.RUnlock() + return b.updating +} + // Init watches all running pods in given namespace. func (b *Browser) Init(ctx context.Context) error { var err error @@ -51,8 +64,8 @@ func (b *Browser) Init(ctx context.Context) error { if err != nil { return err } - colorerFn := render.DefaultColorer - if r, ok := model.Registry[b.GVR().String()]; ok { + colorerFn := model1.DefaultColorer + if r, ok := model.Registry[b.GVR().String()]; ok && r.Renderer != nil { colorerFn = r.Renderer.ColorerFunc() } b.GetTable().SetColorerFn(colorerFn) @@ -118,8 +131,8 @@ func (b *Browser) suggestFilter() model.SuggestionFunc { } } -func (b *Browser) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (b *Browser) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", b.resetCmd, false), tcell.KeyEnter: ui.NewSharedKeyAction("Filter", b.filterCmd, false), tcell.KeyHelp: ui.NewSharedKeyAction("Help", b.helpCmd, false), @@ -179,7 +192,7 @@ func (b *Browser) BufferChanged(_, _ string) {} // BufferCompleted indicates input was accepted. func (b *Browser) BufferCompleted(text, _ string) { - if ui.IsLabelSelector(text) { + if internal.IsLabelSelector(text) { b.GetModel().SetLabelFilter(ui.TrimLabelSelector(text)) } else { b.GetModel().SetLabelFilter("") @@ -191,26 +204,48 @@ func (b *Browser) BufferActive(state bool, k model.BufferKind) { if state { return } - if err := b.GetModel().Refresh(b.prepareContext()); err != nil { + if err := b.GetModel().Refresh(b.GetContext()); err != nil { log.Error().Err(err).Msgf("Refresh failed for %s", b.GVR()) } + data := b.GetModel().Peek() + cdata := b.Update(data, b.App().Conn().HasMetrics()) b.app.QueueUpdateDraw(func() { - b.Update(b.GetModel().Peek(), b.App().Conn().HasMetrics()) + if b.getUpdating() { + return + } + b.setUpdating(true) + defer b.setUpdating(false) + b.UpdateUI(cdata, data) if b.GetRowCount() > 1 { b.App().filterHistory.Push(b.CmdBuff().GetText()) } + }) } func (b *Browser) prepareContext() context.Context { ctx := b.defaultContext() - ctx, b.cancelFn = context.WithCancel(ctx) + + b.mx.Lock() + { + if b.cancelFn != nil { + b.cancelFn() + } + ctx, b.cancelFn = context.WithCancel(ctx) + } + b.mx.Unlock() + if b.contextFn != nil { ctx = b.contextFn(ctx) } if path, ok := ctx.Value(internal.KeyPath).(string); ok && path != "" { b.Path = path } + b.mx.Lock() + { + b.SetContext(ctx) + } + b.mx.Unlock() return ctx } @@ -237,7 +272,7 @@ func (b *Browser) Aliases() map[string]struct{} { // Model Protocol... // TableDataChanged notifies view new data is available. -func (b *Browser) TableDataChanged(data *render.TableData) { +func (b *Browser) TableDataChanged(data *model1.TableData) { var cancel context.CancelFunc b.mx.RLock() cancel = b.cancelFn @@ -247,9 +282,15 @@ func (b *Browser) TableDataChanged(data *render.TableData) { return } + cdata := b.Update(data, b.app.Conn().HasMetrics()) b.app.QueueUpdateDraw(func() { + if b.getUpdating() { + return + } + b.setUpdating(true) + defer b.setUpdating(false) b.refreshActions() - b.Update(data, b.app.Conn().HasMetrics()) + b.UpdateUI(cdata, data) }) } @@ -287,14 +328,17 @@ func (b *Browser) helpCmd(evt *tcell.EventKey) *tcell.EventKey { func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey { if !b.CmdBuff().InCmdMode() { + hasFilter := !b.CmdBuff().Empty() b.CmdBuff().ClearText(false) - b.GetModel().SetLabelFilter("") + if hasFilter { + b.GetModel().SetLabelFilter("") + b.Refresh() + } return b.App().PrevCmd(evt) - } b.CmdBuff().Reset() - if ui.IsLabelSelector(b.CmdBuff().GetText()) { + if internal.IsLabelSelector(b.CmdBuff().GetText()) { b.Start() } b.Refresh() @@ -308,7 +352,7 @@ func (b *Browser) filterCmd(evt *tcell.EventKey) *tcell.EventKey { } b.CmdBuff().SetActive(false) - if ui.IsLabelSelector(b.CmdBuff().GetText()) { + if internal.IsLabelSelector(b.CmdBuff().GetText()) { b.Start() return nil } @@ -471,7 +515,7 @@ func (b *Browser) defaultContext() context.Context { ctx := context.WithValue(context.Background(), internal.KeyFactory, b.app.factory) ctx = context.WithValue(ctx, internal.KeyGVR, b.GVR()) ctx = context.WithValue(ctx, internal.KeyPath, b.Path) - if ui.IsLabelSelector(b.CmdBuff().GetText()) { + if internal.IsLabelSelector(b.CmdBuff().GetText()) { ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText())) } ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace())) @@ -484,41 +528,41 @@ func (b *Browser) refreshActions() { if b.App().Content.Top() != nil && b.App().Content.Top().Name() != b.Name() { return } - aa := ui.KeyActions{ + aa := ui.NewKeyActionsFromMap(ui.KeyMap{ ui.KeyC: ui.NewKeyAction("Copy", b.cpCmd, false), tcell.KeyEnter: ui.NewKeyAction("View", b.enterCmd, false), tcell.KeyCtrlR: ui.NewKeyAction("Refresh", b.refreshCmd, false), - } + }) if b.app.ConOK() { b.namespaceActions(aa) if !b.app.Config.K9s.IsReadOnly() { if client.Can(b.meta.Verbs, "edit") { - aa[ui.KeyE] = ui.NewKeyActionWithOpts("Edit", b.editCmd, + aa.Add(ui.KeyE, ui.NewKeyActionWithOpts("Edit", b.editCmd, ui.ActionOpts{ Visible: true, Dangerous: true, - }) + })) } if client.Can(b.meta.Verbs, "delete") { - aa[tcell.KeyCtrlD] = ui.NewKeyActionWithOpts("Delete", b.deleteCmd, + aa.Add(tcell.KeyCtrlD, ui.NewKeyActionWithOpts("Delete", b.deleteCmd, ui.ActionOpts{ Visible: true, Dangerous: true, - }) + })) } } else { b.Actions().ClearDanger() } } if !dao.IsK9sMeta(b.meta) { - aa[ui.KeyY] = ui.NewKeyAction(yamlAction, b.viewCmd, true) - aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true) + aa.Add(ui.KeyY, ui.NewKeyAction(yamlAction, b.viewCmd, true)) + aa.Add(ui.KeyD, ui.NewKeyAction("Describe", b.describeCmd, true)) } for _, f := range b.bindKeysFn { f(aa) } - b.Actions().Add(aa) + b.Actions().Merge(aa) if err := pluginActions(b, b.Actions()); err != nil { log.Warn().Msgf("Plugins load failed: %s", err) @@ -528,25 +572,24 @@ func (b *Browser) refreshActions() { log.Warn().Msgf("Hotkeys load failed: %s", err) b.app.Logo().Warn("HotKeys load failed!") } - b.app.Menu().HydrateMenu(b.Hints()) } -func (b *Browser) namespaceActions(aa ui.KeyActions) { +func (b *Browser) namespaceActions(aa *ui.KeyActions) { if !b.meta.Namespaced || b.GetTable().Path != "" { return } - aa[ui.KeyN] = ui.NewKeyAction("Copy Namespace", b.cpNsCmd, false) + aa.Add(ui.KeyN, ui.NewKeyAction("Copy Namespace", b.cpNsCmd, false)) b.namespaces = make(map[int]string, data.MaxFavoritesNS) - aa[ui.Key0] = ui.NewKeyAction(client.NamespaceAll, b.switchNamespaceCmd, true) + aa.Add(ui.Key0, ui.NewKeyAction(client.NamespaceAll, b.switchNamespaceCmd, true)) b.namespaces[0] = client.NamespaceAll index := 1 for _, ns := range b.app.Config.FavNamespaces() { if ns == client.NamespaceAll { continue } - aa[ui.NumKeys[index]] = ui.NewKeyAction(ns, b.switchNamespaceCmd, true) + aa.Add(ui.NumKeys[index], ui.NewKeyAction(ns, b.switchNamespaceCmd, true)) b.namespaces[index] = ns index++ } diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index 98512a9ac0..3ff7cd839f 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -111,14 +111,9 @@ func (c *ClusterInfo) warnCell(s string, w bool) string { // ClusterInfoChanged notifies the cluster meta was changed. func (c *ClusterInfo) ClusterInfoChanged(prev, curr model.ClusterMeta) { c.app.QueueUpdateDraw(func() { - var ic = " ✏️" - if c.app.Config.K9s.IsReadOnly() { - ic = " 🔒" - } - c.Clear() c.layout() - row := c.setCell(0, curr.Context+ic) + row := c.setCell(0, curr.Context) row = c.setCell(row, curr.Cluster) row = c.setCell(row, curr.User) if curr.K9sLatest != "" { diff --git a/internal/view/cm.go b/internal/view/cm.go index 05c36da2a1..32d3e47c92 100644 --- a/internal/view/cm.go +++ b/internal/view/cm.go @@ -28,10 +28,8 @@ func NewConfigMap(gvr client.GVR) ResourceViewer { return &s } -func (s *ConfigMap) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyU: ui.NewKeyAction("UsedBy", s.refCmd, true), - }) +func (s *ConfigMap) bindKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyU, ui.NewKeyAction("UsedBy", s.refCmd, true)) } func (s *ConfigMap) refCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/command.go b/internal/view/command.go index 9a8d335551..bb437355c8 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -157,7 +157,7 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { if context, ok := p.HasContext(); ok { if context != c.app.Config.ActiveContextName() { - if err := c.app.Config.Save(); err != nil { + if err := c.app.Config.Save(true); err != nil { log.Error().Err(err).Msg("config save failed!") } else { log.Debug().Msgf("Saved context config for: %q", context) diff --git a/internal/view/container.go b/internal/view/container.go index 3d25a78eac..01f4f1e5e5 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/port" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -38,25 +39,29 @@ func NewContainer(gvr client.GVR) ResourceViewer { return &c } -func (c *Container) portForwardIndicator(data *render.TableData) { +func (c *Container) portForwardIndicator(data *model1.TableData) { ff := c.App().factory.Forwarders() - col := data.IndexOfHeader("PF") - for _, re := range data.RowEvents { + col, ok := data.IndexOfHeader("PF") + if !ok { + return + } + data.RowsRange(func(_ int, re model1.RowEvent) bool { if ff.IsContainerForwarded(c.GetTable().Path, re.Row.ID) { re.Row.Fields[col] = "[orange::b]Ⓕ" } - } + return true + }) } -func (c *Container) decorateRows(data *render.TableData) { +func (c *Container) decorateRows(data *model1.TableData) { decorateCpuMemHeaderRows(c.App(), data) } // Name returns the component name. func (c *Container) Name() string { return containerTitle } -func (c *Container) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (c *Container) bindDangerousKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyS: ui.NewKeyActionWithOpts( "Shell", c.shellCmd, @@ -74,25 +79,25 @@ func (c *Container) bindDangerousKeys(aa ui.KeyActions) { }) } -func (c *Container) bindKeys(aa ui.KeyActions) { +func (c *Container) bindKeys(aa *ui.KeyActions) { aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace) if !c.App().Config.K9s.IsReadOnly() { c.bindDangerousKeys(aa) } - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyF: ui.NewKeyAction("Show PortForward", c.showPFCmd, true), ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", c.GetTable().SortColCmd("RESTARTS", false), false), }) - aa.Add(resourceSorters(c.GetTable())) + aa.Merge(resourceSorters(c.GetTable())) } func (c *Container) k9sEnv() Env { path := c.GetTable().GetSelectedItem() row := c.GetTable().GetSelectedRow(path) - env := defaultEnv(c.App().Conn().Config(), path, c.GetTable().GetModel().Peek().Header, row) + env := defaultEnv(c.App().Conn().Config(), path, c.GetTable().GetModel().Peek().Header(), row) env["NAMESPACE"], env["POD"] = client.Namespaced(c.GetTable().Path) return env diff --git a/internal/view/context.go b/internal/view/context.go index 22266194f8..4ba51bf988 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -37,11 +37,9 @@ func NewContext(gvr client.GVR) ResourceViewer { return &c } -func (c *Context) bindKeys(aa ui.KeyActions) { +func (c *Context) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) - aa.Add(ui.KeyActions{ - ui.KeyR: ui.NewKeyAction("Rename", c.renameCmd, true), - }) + aa.Add(ui.KeyR, ui.NewKeyAction("Rename", c.renameCmd, true)) } func (c *Context) renameCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/cow.go b/internal/view/cow.go index 6920f4dabc..75b71b118e 100644 --- a/internal/view/cow.go +++ b/internal/view/cow.go @@ -19,7 +19,7 @@ import ( type Cow struct { *tview.TextView - actions ui.KeyActions + actions *ui.KeyActions app *App says string } @@ -29,7 +29,7 @@ func NewCow(app *App, says string) *Cow { return &Cow{ TextView: tview.NewTextView(), app: app, - actions: make(ui.KeyActions), + actions: ui.NewKeyActions(), says: says, } } @@ -88,13 +88,11 @@ func cowTalk(says string, w int) string { } func (c *Cow) bindKeys() { - c.actions.Set(ui.KeyActions{ - tcell.KeyEscape: ui.NewKeyAction("Back", c.resetCmd, false), - }) + c.actions.Add(tcell.KeyEscape, ui.NewKeyAction("Back", c.resetCmd, false)) } func (c *Cow) keyboard(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := c.actions[ui.AsKey(evt)]; ok { + if a, ok := c.actions.Get(ui.AsKey(evt)); ok { return a.Action(evt) } @@ -113,7 +111,7 @@ func (c *Cow) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } // Actions returns menu actions. -func (c *Cow) Actions() ui.KeyActions { +func (c *Cow) Actions() *ui.KeyActions { return c.actions } diff --git a/internal/view/crd.go b/internal/view/crd.go new file mode 100644 index 0000000000..7ff1a1f969 --- /dev/null +++ b/internal/view/crd.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package view + +import ( + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/ui" +) + +// CRD represents a crd viewer. +type CRD struct { + ResourceViewer +} + +// NewCRD returns a new viewer. +func NewCRD(gvr client.GVR) ResourceViewer { + s := CRD{ + ResourceViewer: NewBrowser(gvr), + } + s.AddBindKeysFn(s.bindKeys) + s.GetTable().SetEnterFn(s.showCRD) + + return &s +} + +func (s *CRD) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ + ui.KeyShiftV: ui.NewKeyAction("Sort Versions", s.GetTable().SortColCmd("VERSIONS", false), true), + ui.KeyShiftR: ui.NewKeyAction("Sort Group", s.GetTable().SortColCmd("GROUP", true), true), + ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd("KIND", true), true), + }) +} + +func (s *CRD) showCRD(app *App, _ ui.Tabular, _ client.GVR, path string) { + _, crd := client.Namespaced(path) + app.gotoResource(crd, "", false) +} diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index 90943a812d..864a3381a8 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -71,8 +71,8 @@ func jobCtx(path, uid string) ContextFunc { } } -func (c *CronJob) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (c *CronJob) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyT: ui.NewKeyAction("Trigger", c.triggerCmd, true), ui.KeyS: ui.NewKeyAction("Suspend/Resume", c.toggleSuspendCmd, true), ui.KeyShiftL: ui.NewKeyAction("Sort LastScheduled", c.GetTable().SortColCmd(lastScheduledCol, true), false), diff --git a/internal/view/details.go b/internal/view/details.go index 5ad7b4fb2a..c07c6171b3 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -28,7 +28,7 @@ type Details struct { *tview.Flex text *tview.TextView - actions ui.KeyActions + actions *ui.KeyActions app *App title, subject string cmdBuff *model.FishBuff @@ -47,7 +47,7 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) * app: app, title: title, subject: subject, - actions: make(ui.KeyActions), + actions: ui.NewKeyActions(), cmdBuff: model.NewFishBuff('/', model.FilterBuffer), model: model.NewText(), searchable: searchable, @@ -132,7 +132,7 @@ func (d *Details) BufferActive(state bool, k model.BufferKind) { } func (d *Details) bindKeys() { - d.actions.Set(ui.KeyActions{ + d.actions.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewSharedKeyAction("Filter", d.filterCmd, false), tcell.KeyEscape: ui.NewKeyAction("Back", d.resetCmd, false), tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false), @@ -150,7 +150,7 @@ func (d *Details) bindKeys() { } func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := d.actions[ui.AsKey(evt)]; ok { + if a, ok := d.actions.Get(ui.AsKey(evt)); ok { return a.Action(evt) } @@ -181,7 +181,7 @@ func (d *Details) SetSubject(s string) { } // Actions returns menu actions. -func (d *Details) Actions() ui.KeyActions { +func (d *Details) Actions() *ui.KeyActions { return d.actions } diff --git a/internal/view/dir.go b/internal/view/dir.go index dae5ca724e..bb34f682d9 100644 --- a/internal/view/dir.go +++ b/internal/view/dir.go @@ -60,8 +60,8 @@ func (d *Dir) dirContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyPath, d.path) } -func (d *Dir) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (d *Dir) bindDangerousKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyA: ui.NewKeyActionWithOpts("Apply", d.applyCmd, ui.ActionOpts{ Visible: true, Dangerous: true, @@ -77,14 +77,14 @@ func (d *Dir) bindDangerousKeys(aa ui.KeyActions) { }) } -func (d *Dir) bindKeys(aa ui.KeyActions) { +func (d *Dir) bindKeys(aa *ui.KeyActions) { // !!BOZO!! Lame! aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ) if !d.App().Config.K9s.IsReadOnly() { d.bindDangerousKeys(aa) } - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyY: ui.NewKeyAction(yamlAction, d.viewCmd, true), tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true), }) diff --git a/internal/view/dp.go b/internal/view/dp.go index 11decf20cb..f9cd93d64d 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -40,8 +40,8 @@ func NewDeploy(gvr client.GVR) ResourceViewer { return &d } -func (d *Deploy) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (d *Deploy) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyShiftR: ui.NewKeyAction("Sort Ready", d.GetTable().SortColCmd(readyCol, true), false), ui.KeyShiftU: ui.NewKeyAction("Sort UpToDate", d.GetTable().SortColCmd(uptodateCol, true), false), ui.KeyShiftL: ui.NewKeyAction("Sort Available", d.GetTable().SortColCmd(availCol, true), false), diff --git a/internal/view/ds.go b/internal/view/ds.go index 4bb9dd6c1b..a9e24abd73 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -33,8 +33,8 @@ func NewDaemonSet(gvr client.GVR) ResourceViewer { return &d } -func (d *DaemonSet) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (d *DaemonSet) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyShiftD: ui.NewKeyAction("Sort Desired", d.GetTable().SortColCmd("DESIRED", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Current", d.GetTable().SortColCmd("CURRENT", true), false), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", d.GetTable().SortColCmd(readyCol, true), false), diff --git a/internal/view/event.go b/internal/view/event.go index 6de78f30bf..b75c975a97 100644 --- a/internal/view/event.go +++ b/internal/view/event.go @@ -25,9 +25,9 @@ func NewEvent(gvr client.GVR) ResourceViewer { return &e } -func (e *Event) bindKeys(aa ui.KeyActions) { +func (e *Event) bindKeys(aa *ui.KeyActions) { aa.Delete(tcell.KeyCtrlD, ui.KeyE, ui.KeyA) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyShiftL: ui.NewKeyAction("Sort LastSeen", e.GetTable().SortColCmd("LAST SEEN", false), false), ui.KeyShiftF: ui.NewKeyAction("Sort FirstSeen", e.GetTable().SortColCmd("FIRST SEEN", false), false), ui.KeyShiftT: ui.NewKeyAction("Sort Type", e.GetTable().SortColCmd("TYPE", true), false), diff --git a/internal/view/group.go b/internal/view/group.go index 503b190c65..0cfe42ddb5 100644 --- a/internal/view/group.go +++ b/internal/view/group.go @@ -26,9 +26,9 @@ func NewGroup(gvr client.GVR) ResourceViewer { return &g } -func (g *Group) bindKeys(aa ui.KeyActions) { +func (g *Group) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("Rules", g.policyCmd, true), ui.KeyShiftK: ui.NewKeyAction("Sort Kind", g.GetTable().SortColCmd("KIND", true), false), }) diff --git a/internal/view/helm_chart.go b/internal/view/helm_chart.go index afa58e5056..c3d595baf5 100644 --- a/internal/view/helm_chart.go +++ b/internal/view/helm_chart.go @@ -37,9 +37,9 @@ func (c *HelmChart) chartContext(ctx context.Context) context.Context { return ctx } -func (c *HelmChart) bindKeys(aa ui.KeyActions) { +func (c *HelmChart) bindKeys(aa *ui.KeyActions) { aa.Delete(tcell.KeyCtrlS) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyR: ui.NewKeyAction("Releases", c.historyCmd, true), ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(statusCol, true), false), }) diff --git a/internal/view/helm_history.go b/internal/view/helm_history.go index 0949d5dd5e..a2b5e5a922 100644 --- a/internal/view/helm_history.go +++ b/internal/view/helm_history.go @@ -53,13 +53,13 @@ func (h *History) HistoryContext(ctx context.Context) context.Context { return ctx } -func (h *History) bindKeys(aa ui.KeyActions) { +func (h *History) bindKeys(aa *ui.KeyActions) { if !h.App().Config.K9s.IsReadOnly() { h.bindDangerousKeys(aa) } aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace, tcell.KeyCtrlD) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyShiftN: ui.NewKeyAction("Sort Revision", h.GetTable().SortColCmd("REVISION", true), false), ui.KeyShiftS: ui.NewKeyAction("Sort Status", h.GetTable().SortColCmd("STATUS", true), false), ui.KeyShiftA: ui.NewKeyAction("Sort Age", h.GetTable().SortColCmd("AGE", true), false), @@ -81,17 +81,13 @@ func (h *History) getValsCmd(app *App, _ ui.Tabular, _ client.GVR, path string) } } -func (h *History) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyR: ui.NewKeyActionWithOpts( - "RollBackTo...", - h.rollbackCmd, - ui.ActionOpts{ - Visible: true, - Dangerous: true, - }, - ), - }) +func (h *History) bindDangerousKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyR, ui.NewKeyActionWithOpts("RollBackTo...", h.rollbackCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + )) } func (h *History) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/help.go b/internal/view/help.go index 06665e7be9..4347a43cfa 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -77,7 +77,7 @@ func (h *Help) StylesChanged(s *config.Styles) { func (h *Help) bindKeys() { h.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS, ui.KeySlash) - h.Actions().Set(ui.KeyActions{ + h.Actions().Bulk(ui.KeyMap{ tcell.KeyEscape: ui.NewKeyAction("Back", h.app.PrevCmd, true), ui.KeyHelp: ui.NewKeyAction("Back", h.app.PrevCmd, false), tcell.KeyEnter: ui.NewKeyAction("Back", h.app.PrevCmd, false), diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 70605969a6..8ec027a9b0 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -16,6 +16,7 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view/cmd" @@ -106,16 +107,16 @@ func k8sEnv(c *client.Config) Env { } } -func defaultEnv(c *client.Config, path string, header render.Header, row *render.Row) Env { +func defaultEnv(c *client.Config, path string, header model1.Header, row *model1.Row) Env { env := k8sEnv(c) env["NAMESPACE"], env["NAME"] = client.Namespaced(path) if row == nil { return env } - for _, col := range header.Columns(true) { - i := header.IndexOf(col, true) - if i >= 0 && i < len(row.Fields) { - env["COL-"+col] = row.Fields[i] + for _, col := range header.ColumnNames(true) { + idx, ok := header.IndexOf(col, true) + if ok && idx < len(row.Fields) { + env["COL-"+col] = row.Fields[idx] } } @@ -218,8 +219,8 @@ func fqn(ns, n string) string { return ns + "/" + n } -func decorateCpuMemHeaderRows(app *App, data *render.TableData) { - for colIndex, header := range data.Header { +func decorateCpuMemHeaderRows(app *App, data *model1.TableData) { + for colIndex, header := range data.Header() { var check string if header.Name == "%CPU/L" { check = "cpu" @@ -230,26 +231,28 @@ func decorateCpuMemHeaderRows(app *App, data *render.TableData) { if len(check) == 0 { continue } - for _, re := range data.RowEvents { + data.RowsRange(func(_ int, re model1.RowEvent) bool { if re.Row.Fields[colIndex] == render.NAValue { - continue + return true } n, err := strconv.Atoi(re.Row.Fields[colIndex]) if err != nil { - continue + return true } if n > 100 { n = 100 } severity := app.Config.K9s.Thresholds.LevelFor(check, n) if severity == config.SeverityLow { - continue + return true } color := app.Config.K9s.Thresholds.SeverityColor(check, n) if len(color) > 0 { re.Row.Fields[colIndex] = "[" + color + "::b]" + re.Row.Fields[colIndex] } - } + + return true + }) } } diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index 9ec347c8aa..5c2ddbe217 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config/mock" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/tcell/v2" "github.com/rs/zerolog" @@ -149,12 +150,12 @@ func TestK9sEnv(t *testing.T) { KubeConfig: &cfg, } c := client.NewConfig(&flags) - h := render.Header{ + h := model1.Header{ {Name: "A"}, {Name: "B"}, {Name: "C"}, } - r := render.Row{ + r := model1.Row{ Fields: []string{"a1", "b1", "c1"}, } env := defaultEnv(c, "fred/blee", h, &r) diff --git a/internal/view/image_extender.go b/internal/view/image_extender.go index b67764c6a5..bc8f2a7b7c 100644 --- a/internal/view/image_extender.go +++ b/internal/view/image_extender.go @@ -56,13 +56,11 @@ func NewImageExtender(r ResourceViewer) ResourceViewer { return &s } -func (s *ImageExtender) bindKeys(aa ui.KeyActions) { +func (s *ImageExtender) bindKeys(aa *ui.KeyActions) { if s.App().Config.K9s.IsReadOnly() { return } - aa.Add(ui.KeyActions{ - ui.KeyI: ui.NewKeyAction("Set Image", s.setImageCmd, false), - }) + aa.Add(ui.KeyI, ui.NewKeyAction("Set Image", s.setImageCmd, false)) } func (s *ImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/img_scan.go b/internal/view/img_scan.go index f4f5290b8f..58b1002d80 100644 --- a/internal/view/img_scan.go +++ b/internal/view/img_scan.go @@ -41,10 +41,10 @@ func NewImageScan(gvr client.GVR) ResourceViewer { // Name returns the component name. func (s *ImageScan) Name() string { return imgScanTitle } -func (c *ImageScan) bindKeys(aa ui.KeyActions) { +func (c *ImageScan) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlZ, tcell.KeyCtrlW) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyShiftL: ui.NewKeyAction("Sort Lib", c.GetTable().SortColCmd("LIBRARY", false), true), ui.KeyShiftS: ui.NewKeyAction("Sort Severity", c.GetTable().SortColCmd("SEVERITY", false), true), ui.KeyShiftF: ui.NewKeyAction("Sort Fixed-in", c.GetTable().SortColCmd("FIXED-IN", false), true), diff --git a/internal/view/live_view.go b/internal/view/live_view.go index e97a0bc0f2..a928f2e29c 100644 --- a/internal/view/live_view.go +++ b/internal/view/live_view.go @@ -31,7 +31,7 @@ type LiveView struct { title string model model.ResourceViewer text *tview.TextView - actions ui.KeyActions + actions *ui.KeyActions app *App cmdBuff *model.FishBuff currentRegion, maxRegions int @@ -48,7 +48,7 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView { text: tview.NewTextView(), app: app, title: title, - actions: make(ui.KeyActions), + actions: ui.NewKeyActions(), currentRegion: 0, maxRegions: 0, cmdBuff: model.NewFishBuff('/', model.FilterBuffer), @@ -139,7 +139,7 @@ func (v *LiveView) BufferActive(state bool, k model.BufferKind) { } func (v *LiveView) bindKeys() { - v.actions.Set(ui.KeyActions{ + v.actions.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewSharedKeyAction("Filter", v.filterCmd, false), tcell.KeyEscape: ui.NewKeyAction("Back", v.resetCmd, false), tcell.KeyCtrlS: ui.NewKeyAction("Save", v.saveCmd, false), @@ -153,19 +153,13 @@ func (v *LiveView) bindKeys() { }) if !v.app.Config.K9s.IsReadOnly() { - v.actions.Add(ui.KeyActions{ - ui.KeyE: ui.NewKeyAction("Edit", v.editCmd, true), - }) + v.actions.Add(ui.KeyE, ui.NewKeyAction("Edit", v.editCmd, true)) } if v.title == yamlAction { - v.actions.Add(ui.KeyActions{ - ui.KeyM: ui.NewKeyAction("Toggle ManagedFields", v.toggleManagedCmd, true), - }) + v.actions.Add(ui.KeyM, ui.NewKeyAction("Toggle ManagedFields", v.toggleManagedCmd, true)) } if v.model != nil && v.model.GVR().IsDecodable() { - v.actions.Add(ui.KeyActions{ - ui.KeyX: ui.NewKeyAction("Toggle Decode", v.toggleEncodedDecodedCmd, true), - }) + v.actions.Add(ui.KeyX, ui.NewKeyAction("Toggle Decode", v.toggleEncodedDecodedCmd, true)) } } @@ -210,7 +204,7 @@ func (v *LiveView) toggleRefreshCmd(evt *tcell.EventKey) *tcell.EventKey { } func (v *LiveView) keyboard(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := v.actions[ui.AsKey(evt)]; ok { + if a, ok := v.actions.Get(ui.AsKey(evt)); ok { return a.Action(evt) } @@ -225,7 +219,7 @@ func (v *LiveView) StylesChanged(s *config.Styles) { } // Actions returns menu actions. -func (v *LiveView) Actions() ui.KeyActions { +func (v *LiveView) Actions() *ui.KeyActions { return v.actions } diff --git a/internal/view/log.go b/internal/view/log.go index a5df9bceff..be01ed5a5d 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -242,7 +242,7 @@ func (l *Log) Stop() { func (l *Log) Name() string { return logTitle } func (l *Log) bindKeys() { - l.logs.Actions().Set(ui.KeyActions{ + l.logs.Actions().Bulk(ui.KeyMap{ ui.Key0: ui.NewKeyAction("tail", l.sinceCmd(-1), true), ui.Key1: ui.NewKeyAction("head", l.sinceCmd(0), true), ui.Key2: ui.NewKeyAction("1m", l.sinceCmd(60), true), @@ -262,9 +262,7 @@ func (l *Log) bindKeys() { ui.KeyC: ui.NewKeyAction("Copy", cpCmd(l.app.Flash(), l.logs.TextView), true), }) if l.model.HasDefaultContainer() { - l.logs.Actions().Set(ui.KeyActions{ - ui.KeyA: ui.NewKeyAction("Toggle AllContainers", l.toggleAllContainers, true), - }) + l.logs.Actions().Add(ui.KeyA, ui.NewKeyAction("Toggle AllContainers", l.toggleAllContainers, true)) } } diff --git a/internal/view/log_test.go b/internal/view/log_test.go index 6b466cf8ec..5db3fcaf8f 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -5,7 +5,9 @@ package view_test import ( "bytes" + "errors" "fmt" + "io/fs" "os" "testing" @@ -139,7 +141,7 @@ func TestAllContainerKeyBinding(t *testing.T) { t.Run(k, func(t *testing.T) { v := view.NewLog(client.NewGVR("v1/pods"), u.opts) assert.NoError(t, v.Init(makeContext())) - _, got := v.Logs().Actions()[ui.KeyA] + _, got := v.Logs().Actions().Get(ui.KeyA) assert.Equal(t, u.e, got) }) } @@ -154,7 +156,7 @@ func makeApp() *view.App { func ensureDumpDir(n string) error { config.AppDumpsDir = n - if _, err := os.Stat(n); os.IsNotExist(err) { + if _, err := os.Stat(n); errors.Is(err, fs.ErrNotExist) { return os.MkdirAll(n, 0700) } if err := os.RemoveAll(n); err != nil { diff --git a/internal/view/logger.go b/internal/view/logger.go index 7e0526cf36..7fc649f0ec 100644 --- a/internal/view/logger.go +++ b/internal/view/logger.go @@ -17,7 +17,7 @@ import ( type Logger struct { *tview.TextView - actions ui.KeyActions + actions *ui.KeyActions app *App title, subject string cmdBuff *model.FishBuff @@ -28,7 +28,7 @@ func NewLogger(app *App) *Logger { return &Logger{ TextView: tview.NewTextView(), app: app, - actions: make(ui.KeyActions), + actions: ui.NewKeyActions(), cmdBuff: model.NewFishBuff('/', model.FilterBuffer), } } @@ -69,7 +69,7 @@ func (l *Logger) BufferActive(state bool, k model.BufferKind) { } func (l *Logger) bindKeys() { - l.actions.Set(ui.KeyActions{ + l.actions.Bulk(ui.KeyMap{ tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, false), tcell.KeyCtrlS: ui.NewKeyAction("Save", l.saveCmd, false), ui.KeyC: ui.NewKeyAction("Copy", cpCmd(l.app.Flash(), l.TextView), true), @@ -79,7 +79,7 @@ func (l *Logger) bindKeys() { } func (l *Logger) keyboard(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := l.actions[ui.AsKey(evt)]; ok { + if a, ok := l.actions.Get(ui.AsKey(evt)); ok { return a.Action(evt) } @@ -99,7 +99,7 @@ func (l *Logger) SetSubject(s string) { } // Actions returns menu actions. -func (l *Logger) Actions() ui.KeyActions { +func (l *Logger) Actions() *ui.KeyActions { return l.actions } diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 6c20a81848..95e452117d 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -29,8 +29,8 @@ func NewLogsExtender(v ResourceViewer, f LogOptionsFunc) ResourceViewer { } // BindKeys injects new menu actions. -func (l *LogsExtender) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (l *LogsExtender) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true), ui.KeyP: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true), }) diff --git a/internal/view/node.go b/internal/view/node.go index daf9ac4de7..31a2001540 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -39,8 +39,8 @@ func (n *Node) nodeContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyPodCounting, !n.App().Config.K9s.DisablePodCounting) } -func (n *Node) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (n *Node) bindDangerousKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyC: ui.NewKeyActionWithOpts( "Cordon", n.toggleCordonCmd(true), @@ -72,18 +72,16 @@ func (n *Node) bindDangerousKeys(aa ui.KeyActions) { return } if ct.FeatureGates.NodeShell { - aa.Add(ui.KeyActions{ - ui.KeyS: ui.NewKeyAction("Shell", n.sshCmd, true), - }) + aa.Add(ui.KeyS, ui.NewKeyAction("Shell", n.sshCmd, true)) } } -func (n *Node) bindKeys(aa ui.KeyActions) { +func (n *Node) bindKeys(aa *ui.KeyActions) { if !n.App().Config.K9s.IsReadOnly() { n.bindDangerousKeys(aa) } - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyY: ui.NewKeyAction(yamlAction, n.yamlCmd, true), ui.KeyShiftR: ui.NewKeyAction("Sort ROLE", n.GetTable().SortColCmd("ROLE", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort CPU", n.GetTable().SortColCmd(cpuCol, false), false), diff --git a/internal/view/ns.go b/internal/view/ns.go index e86432fd30..1eac09dbef 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -5,8 +5,7 @@ package view import ( "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/config/data" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" ) @@ -33,8 +32,8 @@ func NewNamespace(gvr client.GVR) ResourceViewer { return &n } -func (n *Namespace) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (n *Namespace) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyU: ui.NewKeyAction("Use", n.useNsCmd, true), ui.KeyShiftS: ui.NewKeyAction("Sort Status", n.GetTable().SortColCmd(statusCol, true), false), }) @@ -70,32 +69,37 @@ func (n *Namespace) useNamespace(fqn string) { } } -func (n *Namespace) decorate(td *render.TableData) { - if n.App().Conn() == nil || len(td.RowEvents) == 0 { +func (n *Namespace) decorate(td *model1.TableData) { + if n.App().Conn() == nil || td.RowCount() == 0 { return } - // checks if all ns is in the list if not add it. - if _, ok := td.RowEvents.FindIndex(client.NamespaceAll); !ok { - td.RowEvents = append(td.RowEvents, - render.RowEvent{ - Kind: render.EventUnchanged, - Row: render.Row{ - ID: client.NamespaceAll, - Fields: render.Fields{client.NamespaceAll, "Active", "", "", ""}, - }, + if _, ok := td.FindRow(client.NamespaceAll); !ok { + td.AddRow(model1.RowEvent{ + Kind: model1.EventUnchanged, + Row: model1.Row{ + ID: client.NamespaceAll, + Fields: model1.Fields{client.NamespaceAll, "Active", "", "", ""}, }, + }, ) } - for _, re := range td.RowEvents { - if data.InList(n.App().Config.FavNamespaces(), re.Row.ID) { + favs := make(map[string]struct{}) + for _, ns := range n.App().Config.FavNamespaces() { + favs[ns] = struct{}{} + } + ans := n.App().Config.ActiveNamespace() + td.RowsRange(func(i int, re model1.RowEvent) bool { + _, n := client.Namespaced(re.Row.ID) + if _, ok := favs[n]; ok { re.Row.Fields[0] += favNSIndicator - re.Kind = render.EventUnchanged } - if n.App().Config.ActiveNamespace() == re.Row.ID { + if ans == re.Row.ID { re.Row.Fields[0] += defaultNSIndicator - re.Kind = render.EventUnchanged } - } + re.Kind = model1.EventUnchanged + td.SetRow(i, re) + return true + }) } diff --git a/internal/view/pf.go b/internal/view/pf.go index 40ded9d65a..a92a1824ed 100644 --- a/internal/view/pf.go +++ b/internal/view/pf.go @@ -51,8 +51,8 @@ func (p *PortForward) portForwardContext(ctx context.Context) context.Context { return ctx } -func (p *PortForward) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (p *PortForward) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("View Benchmarks", p.showBenchCmd, true), ui.KeyB: ui.NewKeyAction("Benchmark Run/Stop", p.toggleBenchCmd, true), tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true), diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 627395f76e..2e19cc2694 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -36,8 +36,8 @@ func NewPortForwardExtender(r ResourceViewer) ResourceViewer { return &p } -func (p *PortForwardExtender) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (p *PortForwardExtender) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyF: ui.NewKeyAction("Show PortForward", p.showPFCmd, true), ui.KeyShiftF: ui.NewKeyAction("Port-Forward", p.portFwdCmd, true), }) diff --git a/internal/view/picker.go b/internal/view/picker.go index b16042be95..56e0275181 100644 --- a/internal/view/picker.go +++ b/internal/view/picker.go @@ -38,7 +38,7 @@ func (p *Picker) Init(ctx context.Context) error { } pickerView := app.Styles.Views().Picker - p.actions[tcell.KeyEscape] = ui.NewKeyAction("Back", app.PrevCmd, true) + p.actions.Add(tcell.KeyEscape, ui.NewKeyAction("Back", app.PrevCmd, true)) p.SetBorder(true) p.SetMainTextColor(pickerView.MainColor.Color()) @@ -48,7 +48,7 @@ func (p *Picker) Init(ctx context.Context) error { p.SetTitle(" [aqua::b]Containers Picker ") p.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey { - if a, ok := p.actions[evt.Key()]; ok { + if a, ok := p.actions.Get(evt.Key()); ok { a.Action(evt) evt = nil } diff --git a/internal/view/pod.go b/internal/view/pod.go index de1fc1fe35..cdd2a92428 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io/fs" "os" "strings" @@ -14,6 +15,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" @@ -34,6 +36,7 @@ const ( osBetaSelector = "beta." + osSelector trUpload = "Upload" trDownload = "Download" + pfIndicator = "[orange::b]Ⓕ" ) // Pod represents a pod viewer. @@ -58,20 +61,25 @@ func NewPod(gvr client.GVR) ResourceViewer { return &p } -func (p *Pod) portForwardIndicator(data *render.TableData) { +func (p *Pod) portForwardIndicator(data *model1.TableData) { ff := p.App().factory.Forwarders() - col := data.IndexOfHeader("PF") - for _, re := range data.RowEvents { + defer decorateCpuMemHeaderRows(p.App(), data) + idx, ok := data.IndexOfHeader("PF") + if !ok { + return + } + + data.RowsRange(func(_ int, re model1.RowEvent) bool { if ff.IsPodForwarded(re.Row.ID) { - re.Row.Fields[col] = "[orange::b]Ⓕ" + re.Row.Fields[idx] = pfIndicator } - } - decorateCpuMemHeaderRows(p.App(), data) + return true + }) } -func (p *Pod) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (p *Pod) bindDangerousKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ tcell.KeyCtrlK: ui.NewKeyActionWithOpts( "Kill", p.killCmd, @@ -110,12 +118,12 @@ func (p *Pod) bindDangerousKeys(aa ui.KeyActions) { }) } -func (p *Pod) bindKeys(aa ui.KeyActions) { +func (p *Pod) bindKeys(aa *ui.KeyActions) { if !p.App().Config.K9s.IsReadOnly() { p.bindDangerousKeys(aa) } - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyO: ui.NewKeyAction("Show Node", p.showNode, true), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", p.GetTable().SortColCmd(readyCol, true), false), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd("RESTARTS", false), false), @@ -123,7 +131,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) { ui.KeyShiftI: ui.NewKeyAction("Sort IP", p.GetTable().SortColCmd("IP", true), false), ui.KeyShiftO: ui.NewKeyAction("Sort Node", p.GetTable().SortColCmd("NODE", true), false), }) - aa.Add(resourceSorters(p.GetTable())) + aa.Merge(resourceSorters(p.GetTable())) } func (p *Pod) logOptions(prev bool) (*dao.LogOptions, error) { @@ -307,7 +315,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey { if !download { local = from } - if _, err := os.Stat(local); !download && os.IsNotExist(err) { + if _, err := os.Stat(local); !download && errors.Is(err, fs.ErrNotExist) { p.App().Flash().Err(err) return false } @@ -555,13 +563,13 @@ func osFromSelector(s map[string]string) (string, bool) { return os, ok } -func resourceSorters(t *Table) ui.KeyActions { - return ui.KeyActions{ +func resourceSorters(t *Table) *ui.KeyActions { + return ui.NewKeyActionsFromMap(ui.KeyMap{ ui.KeyShiftC: ui.NewKeyAction("Sort CPU", t.SortColCmd(cpuCol, false), false), ui.KeyShiftM: ui.NewKeyAction("Sort MEM", t.SortColCmd(memCol, false), false), ui.KeyShiftX: ui.NewKeyAction("Sort CPU/R", t.SortColCmd("%CPU/R", false), false), ui.KeyShiftZ: ui.NewKeyAction("Sort MEM/R", t.SortColCmd("%MEM/R", false), false), tcell.KeyCtrlX: ui.NewKeyAction("Sort CPU/L", t.SortColCmd("%CPU/L", false), false), tcell.KeyCtrlQ: ui.NewKeyAction("Sort MEM/L", t.SortColCmd("%MEM/L", false), false), - } + }) } diff --git a/internal/view/policy.go b/internal/view/policy.go index 28fee83e64..b412b388f5 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -46,9 +46,9 @@ func (p *Policy) subjectCtx(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeySubjectName, p.subjectName) } -func (p *Policy) bindKeys(aa ui.KeyActions) { +func (p *Policy) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(nameCol, true), false), ui.KeyShiftA: ui.NewKeyAction("Sort Api-Group", p.GetTable().SortColCmd("API-GROUP", true), false), ui.KeyShiftB: ui.NewKeyAction("Sort Binding", p.GetTable().SortColCmd("BINDING", true), false), diff --git a/internal/view/popeye.go b/internal/view/popeye.go index ca5c273766..e3c6355b38 100644 --- a/internal/view/popeye.go +++ b/internal/view/popeye.go @@ -3,114 +3,114 @@ package view -import ( - "context" - "fmt" - "strconv" - "time" +// import ( +// "context" +// "fmt" +// "strconv" +// "time" - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/ui" - "github.com/derailed/tcell/v2" -) +// "github.com/derailed/k9s/internal" +// "github.com/derailed/k9s/internal/client" +// "github.com/derailed/k9s/internal/render" +// "github.com/derailed/k9s/internal/ui" +// "github.com/derailed/tcell/v2" +// ) -// Popeye represents a sanitizer view. -type Popeye struct { - ResourceViewer -} +// // Popeye represents a sanitizer view. +// type Popeye struct { +// ResourceViewer +// } -// NewPopeye returns a new view. -func NewPopeye(gvr client.GVR) ResourceViewer { - p := Popeye{ - ResourceViewer: NewBrowser(gvr), - } - p.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) - p.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) - p.GetTable().SetSortCol("SCORE%", true) - p.GetTable().SetDecorateFn(p.decorateRows) - p.AddBindKeysFn(p.bindKeys) +// // NewPopeye returns a new view. +// func NewPopeye(gvr client.GVR) ResourceViewer { +// p := Popeye{ +// ResourceViewer: NewBrowser(gvr), +// } +// p.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) +// p.GetTable().SetSelectedStyle(tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorMediumSpringGreen).Attributes(tcell.AttrNone)) +// p.GetTable().SetSortCol("SCORE%", true) +// p.GetTable().SetDecorateFn(p.decorateRows) +// p.AddBindKeysFn(p.bindKeys) - return &p -} +// return &p +// } -// Init initializes the view. -func (p *Popeye) Init(ctx context.Context) error { - if err := p.ResourceViewer.Init(ctx); err != nil { - return err - } - p.GetTable().GetModel().SetRefreshRate(5 * time.Second) +// // Init initializes the view. +// func (p *Popeye) Init(ctx context.Context) error { +// if err := p.ResourceViewer.Init(ctx); err != nil { +// return err +// } +// p.GetTable().GetModel().SetRefreshRate(5 * time.Second) - return nil -} +// return nil +// } -func (p *Popeye) decorateRows(data *render.TableData) { - var sum int - for _, re := range data.RowEvents { - n, err := strconv.Atoi(re.Row.Fields[1]) - if err != nil { - continue - } - sum += n - } - score, letter := 0, render.NAValue - if len(data.RowEvents) > 0 { - score = sum / len(data.RowEvents) - letter = grade(score) - } - p.GetTable().Extras = fmt.Sprintf("Score %d -- %s", score, letter) -} +// func (p *Popeye) decorateRows(data *model1.TableData) { +// var sum int +// for _, re := range data.RowEvents { +// n, err := strconv.Atoi(re.Row.Fields[1]) +// if err != nil { +// continue +// } +// sum += n +// } +// score, letter := 0, render.NAValue +// if len(data.RowEvents) > 0 { +// score = sum / len(data.RowEvents) +// letter = grade(score) +// } +// p.GetTable().Extras = fmt.Sprintf("Score %d -- %s", score, letter) +// } -func (p *Popeye) bindKeys(aa ui.KeyActions) { - aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) - aa.Add(ui.KeyActions{ - tcell.KeyEnter: ui.NewKeyAction("Goto", p.gotoCmd, true), - ui.KeyShiftR: ui.NewKeyAction("Sort Resource", p.GetTable().SortColCmd("RESOURCE", true), false), - ui.KeyShiftS: ui.NewKeyAction("Sort Score", p.GetTable().SortColCmd("SCORE%", true), false), - ui.KeyShiftO: ui.NewKeyAction("Sort OK", p.GetTable().SortColCmd("OK", true), false), - ui.KeyShiftI: ui.NewKeyAction("Sort Info", p.GetTable().SortColCmd("INFO", true), false), - ui.KeyShiftW: ui.NewKeyAction("Sort Warning", p.GetTable().SortColCmd("WARNING", true), false), - ui.KeyShiftE: ui.NewKeyAction("Sort Error", p.GetTable().SortColCmd("ERROR", true), false), - }) -} +// func (p *Popeye) bindKeys(aa ui.KeyActions) { +// aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) +// aa.Add(ui.KeyActions{ +// tcell.KeyEnter: ui.NewKeyAction("Goto", p.gotoCmd, true), +// ui.KeyShiftR: ui.NewKeyAction("Sort Resource", p.GetTable().SortColCmd("RESOURCE", true), false), +// ui.KeyShiftS: ui.NewKeyAction("Sort Score", p.GetTable().SortColCmd("SCORE%", true), false), +// ui.KeyShiftO: ui.NewKeyAction("Sort OK", p.GetTable().SortColCmd("OK", true), false), +// ui.KeyShiftI: ui.NewKeyAction("Sort Info", p.GetTable().SortColCmd("INFO", true), false), +// ui.KeyShiftW: ui.NewKeyAction("Sort Warning", p.GetTable().SortColCmd("WARNING", true), false), +// ui.KeyShiftE: ui.NewKeyAction("Sort Error", p.GetTable().SortColCmd("ERROR", true), false), +// }) +// } -func (p *Popeye) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { - path := p.GetTable().GetSelectedItem() - if path == "" { - return evt - } - v := NewSanitizer(client.NewGVR("sanitizer")) - v.SetContextFn(sanitizerCtx(path)) - if err := p.App().inject(v, false); err != nil { - p.App().Flash().Err(err) - } +// func (p *Popeye) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { +// path := p.GetTable().GetSelectedItem() +// if path == "" { +// return evt +// } +// v := NewSanitizer(client.NewGVR("sanitizer")) +// v.SetContextFn(sanitizerCtx(path)) +// if err := p.App().inject(v, false); err != nil { +// p.App().Flash().Err(err) +// } - return nil -} +// return nil +// } -func sanitizerCtx(path string) ContextFunc { - return func(ctx context.Context) context.Context { - ctx = context.WithValue(ctx, internal.KeyPath, path) - return ctx - } -} +// func sanitizerCtx(path string) ContextFunc { +// return func(ctx context.Context) context.Context { +// ctx = context.WithValue(ctx, internal.KeyPath, path) +// return ctx +// } +// } -// Helpers... +// // Helpers... -func grade(score int) string { - switch { - case score >= 90: - return "A" - case score >= 80: - return "B" - case score >= 70: - return "C" - case score >= 60: - return "D" - case score >= 50: - return "E" - default: - return "F" - } -} +// func grade(score int) string { +// switch { +// case score >= 90: +// return "A" +// case score >= 80: +// return "B" +// case score >= 70: +// return "C" +// case score >= 60: +// return "D" +// case score >= 50: +// return "E" +// default: +// return "F" +// } +// } diff --git a/internal/view/priorityclass.go b/internal/view/priorityclass.go index fc89ef5010..7f9ef90908 100644 --- a/internal/view/priorityclass.go +++ b/internal/view/priorityclass.go @@ -25,10 +25,8 @@ func NewPriorityClass(gvr client.GVR) ResourceViewer { return &s } -func (s *PriorityClass) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyU: ui.NewKeyAction("UsedBy", s.refCmd, true), - }) +func (s *PriorityClass) bindKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyU, ui.NewKeyAction("UsedBy", s.refCmd, true)) } func (s *PriorityClass) refCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/pulse.go b/internal/view/pulse.go index c323f64d01..6d36e7a5fd 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -64,7 +64,7 @@ type Pulse struct { gvr client.GVR model *model.Pulse cancelFn context.CancelFunc - actions ui.KeyActions + actions *ui.KeyActions charts []Graphable } @@ -73,7 +73,7 @@ func NewPulse(gvr client.GVR) ResourceViewer { return &Pulse{ Grid: tview.NewGrid(), model: model.NewPulse(gvr.String()), - actions: make(ui.KeyActions), + actions: ui.NewKeyActions(), } } @@ -207,15 +207,15 @@ func (p *Pulse) PulseFailed(err error) { } func (p *Pulse) bindKeys() { - p.actions.Add(ui.KeyActions{ + p.actions.Merge(ui.NewKeyActionsFromMap(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("Goto", p.enterCmd, true), tcell.KeyTab: ui.NewKeyAction("Next", p.nextFocusCmd(1), true), tcell.KeyBacktab: ui.NewKeyAction("Prev", p.nextFocusCmd(-1), true), - }) + })) for i, v := range p.charts { t := cases.Title(language.Und, cases.NoLower).String(client.NewGVR(v.ID()).R()) - p.actions[ui.NumKeys[i]] = ui.NewKeyAction(t, p.sparkFocusCmd(i), true) + p.actions.Add(ui.NumKeys[i], ui.NewKeyAction(t, p.sparkFocusCmd(i), true)) } } @@ -224,7 +224,7 @@ func (p *Pulse) keyboard(evt *tcell.EventKey) *tcell.EventKey { if key == tcell.KeyRune { key = tcell.Key(evt.Rune()) } - if a, ok := p.actions[key]; ok { + if a, ok := p.actions.Get(key); ok { return a.Action(evt) } @@ -289,7 +289,7 @@ func (p *Pulse) GetTable() *Table { } // Actions returns active menu bindings. -func (p *Pulse) Actions() ui.KeyActions { +func (p *Pulse) Actions() *ui.KeyActions { return p.actions } diff --git a/internal/view/pvc.go b/internal/view/pvc.go index 486fb46383..27d15749de 100644 --- a/internal/view/pvc.go +++ b/internal/view/pvc.go @@ -25,8 +25,8 @@ func NewPersistentVolumeClaim(gvr client.GVR) ResourceViewer { return &v } -func (p *PersistentVolumeClaim) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (p *PersistentVolumeClaim) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyU: ui.NewKeyAction("UsedBy", p.refCmd, true), ui.KeyShiftS: ui.NewKeyAction("Sort Status", p.GetTable().SortColCmd("STATUS", true), false), ui.KeyShiftV: ui.NewKeyAction("Sort Volume", p.GetTable().SortColCmd("VOLUME", true), false), diff --git a/internal/view/rbac.go b/internal/view/rbac.go index 2ea21464e1..5583122861 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -29,11 +29,9 @@ func NewRbac(gvr client.GVR) ResourceViewer { return &r } -func (r *Rbac) bindKeys(aa ui.KeyActions) { +func (r *Rbac) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) - aa.Add(ui.KeyActions{ - ui.KeyShiftA: ui.NewKeyAction("Sort API-Group", r.GetTable().SortColCmd("API-GROUP", true), false), - }) + aa.Add(ui.KeyShiftA, ui.NewKeyAction("Sort API-Group", r.GetTable().SortColCmd("API-GROUP", true), false)) } func showRules(app *App, _ ui.Tabular, gvr client.GVR, path string) { diff --git a/internal/view/reference.go b/internal/view/reference.go index 5519eae907..2f08cc7bba 100644 --- a/internal/view/reference.go +++ b/internal/view/reference.go @@ -38,10 +38,10 @@ func (r *Reference) Init(ctx context.Context) error { return nil } -func (r *Reference) bindKeys(aa ui.KeyActions) { +func (r *Reference) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlZ) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("Goto", r.gotoCmd, true), ui.KeyShiftV: ui.NewKeyAction("Sort GVR", r.GetTable().SortColCmd("GVR", true), false), }) diff --git a/internal/view/registrar.go b/internal/view/registrar.go index cde177b9c1..d199a44988 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -5,7 +5,6 @@ package view import ( "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/ui" ) func loadCustomViewers() MetaViewers { @@ -15,7 +14,7 @@ func loadCustomViewers() MetaViewers { appsViewers(m) rbacViewers(m) batchViewers(m) - extViewers(m) + crdViewers(m) helmViewers(m) return m @@ -91,9 +90,10 @@ func miscViewers(vv MetaViewers) { vv[client.NewGVR("pulses")] = MetaViewer{ viewerFn: NewPulse, } - vv[client.NewGVR("popeye")] = MetaViewer{ - viewerFn: NewPopeye, - } + // !!BOZO!! Popeye + // vv[client.NewGVR("popeye")] = MetaViewer{ + // viewerFn: NewPopeye, + // } vv[client.NewGVR("sanitizer")] = MetaViewer{ viewerFn: NewSanitizer, } @@ -153,13 +153,8 @@ func batchViewers(vv MetaViewers) { } } -func extViewers(vv MetaViewers) { +func crdViewers(vv MetaViewers) { vv[client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")] = MetaViewer{ - enterFn: showCRD, + viewerFn: NewCRD, } } - -func showCRD(app *App, _ ui.Tabular, _ client.GVR, path string) { - _, crd := client.Namespaced(path) - app.gotoResource(crd, "", false) -} diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go index 1e83ced88c..668e9bad71 100644 --- a/internal/view/restart_extender.go +++ b/internal/view/restart_extender.go @@ -29,16 +29,16 @@ func NewRestartExtender(v ResourceViewer) ResourceViewer { } // BindKeys creates additional menu actions. -func (r *RestartExtender) bindKeys(aa ui.KeyActions) { +func (r *RestartExtender) bindKeys(aa *ui.KeyActions) { if r.App().Config.K9s.IsReadOnly() { return } - aa.Add(ui.KeyActions{ - ui.KeyR: ui.NewKeyActionWithOpts("Restart", r.restartCmd, ui.ActionOpts{ + aa.Add(ui.KeyR, ui.NewKeyActionWithOpts("Restart", r.restartCmd, + ui.ActionOpts{ Visible: true, Dangerous: true, - }), - }) + }, + )) } func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/rs.go b/internal/view/rs.go index 3650b8ff15..86e2c5956b 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -29,8 +29,8 @@ func NewReplicaSet(gvr client.GVR) ResourceViewer { return &r } -func (r *ReplicaSet) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (r *ReplicaSet) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyShiftD: ui.NewKeyAction("Sort Desired", r.GetTable().SortColCmd("DESIRED", true), false), ui.KeyShiftC: ui.NewKeyAction("Sort Current", r.GetTable().SortColCmd("CURRENT", true), false), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", r.GetTable().SortColCmd(readyCol, true), false), diff --git a/internal/view/sa.go b/internal/view/sa.go index c1433c2cce..63145e7cd4 100644 --- a/internal/view/sa.go +++ b/internal/view/sa.go @@ -29,8 +29,8 @@ func NewServiceAccount(gvr client.GVR) ResourceViewer { return &s } -func (s *ServiceAccount) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (s *ServiceAccount) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyU: ui.NewKeyAction("UsedBy", s.refCmd, true), tcell.KeyEnter: ui.NewKeyAction("Rules", s.policyCmd, true), }) diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index 51388cebec..9ec116d73e 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -110,7 +110,7 @@ func (s *Sanitizer) ExtraHints() map[string]string { func (s *Sanitizer) SetInstance(string) {} func (s *Sanitizer) bindKeys() { - s.Actions().Add(ui.KeyActions{ + s.Actions().Bulk(ui.KeyMap{ ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", s.activateCmd, false), tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", s.resetCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", s.gotoCmd, true), @@ -209,7 +209,7 @@ func (s *Sanitizer) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if s.CmdBuff().IsActive() { - if ui.IsLabelSelector(s.CmdBuff().GetText()) { + if internal.IsLabelSelector(s.CmdBuff().GetText()) { s.Start() } s.CmdBuff().SetActive(false) @@ -238,16 +238,16 @@ func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode { q := s.CmdBuff().GetText() - if s.CmdBuff().Empty() || ui.IsLabelSelector(q) { + if s.CmdBuff().Empty() || internal.IsLabelSelector(q) { return root } s.UpdateTitle() - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return root.Filter(f, fuzzyFilter) } - if dao.IsInverseSelector(q) { + if internal.IsInverseSelector(q) { return root.Filter(q, rxInverseFilter) } @@ -427,7 +427,7 @@ func (s *Sanitizer) styleTitle() string { if buff == "" { return title } - if ui.IsLabelSelector(buff) { + if internal.IsLabelSelector(buff) { buff = ui.TrimLabelSelector(buff) } diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index c30e289c3e..e402bd5ae8 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -31,17 +31,16 @@ func NewScaleExtender(r ResourceViewer) ResourceViewer { return &s } -func (s *ScaleExtender) bindKeys(aa ui.KeyActions) { +func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) { if s.App().Config.K9s.IsReadOnly() { return } - aa.Add(ui.KeyActions{ - ui.KeyS: ui.NewKeyActionWithOpts("Scale", s.scaleCmd, - ui.ActionOpts{ - Visible: true, - Dangerous: true, - }), - }) + aa.Add(ui.KeyS, ui.NewKeyActionWithOpts("Scale", s.scaleCmd, + ui.ActionOpts{ + Visible: true, + Dangerous: true, + }, + )) } func (s *ScaleExtender) scaleCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/secret.go b/internal/view/secret.go index e37cdb7687..d59e77ee1c 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -28,8 +28,8 @@ func NewSecret(gvr client.GVR) ResourceViewer { return &s } -func (s *Secret) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (s *Secret) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyX: ui.NewKeyAction("Decode", s.decodeCmd, true), ui.KeyU: ui.NewKeyAction("UsedBy", s.refCmd, true), }) diff --git a/internal/view/sts.go b/internal/view/sts.go index e9e04fcae1..816dabb984 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -80,10 +80,8 @@ func (s *StatefulSet) logOptions(prev bool) (*dao.LogOptions, error) { return &opts, nil } -func (s *StatefulSet) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyShiftR: ui.NewKeyAction("Sort Ready", s.GetTable().SortColCmd(readyCol, true), false), - }) +func (s *StatefulSet) bindKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyShiftR, ui.NewKeyAction("Sort Ready", s.GetTable().SortColCmd(readyCol, true), false)) } func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string) { diff --git a/internal/view/svc.go b/internal/view/svc.go index 4abf37d772..1517116819 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -46,8 +46,8 @@ func NewService(gvr client.GVR) ResourceViewer { // Protocol... -func (s *Service) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ +func (s *Service) bindKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ ui.KeyB: ui.NewKeyAction("Bench Run/Stop", s.toggleBenchCmd, true), ui.KeyShiftT: ui.NewKeyAction("Sort Type", s.GetTable().SortColCmd("TYPE", true), false), }) diff --git a/internal/view/table.go b/internal/view/table.go index 48bb9c9bdd..d0f60d37a0 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -93,7 +93,7 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { return evt } - if a, ok := t.Actions()[ui.AsKey(evt)]; ok && !t.app.Content.IsTopDialog() { + if a, ok := t.Actions().Get(ui.AsKey(evt)); ok && !t.app.Content.IsTopDialog() { return a.Action(evt) } @@ -119,7 +119,7 @@ func (t *Table) EnvFn() EnvFunc { func (t *Table) defaultEnv() Env { path := t.GetSelectedItem() row := t.GetSelectedRow(path) - env := defaultEnv(t.app.Conn().Config(), path, t.GetModel().Peek().Header, row) + env := defaultEnv(t.app.Conn().Config(), path, t.GetModel().Peek().Header(), row) env["FILTER"] = t.CmdBuff().GetText() if env["FILTER"] == "" { env["NAMESPACE"], env["FILTER"] = client.Namespaced(path) @@ -186,7 +186,7 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { } func (t *Table) bindKeys() { - t.Actions().Add(ui.KeyActions{ + t.Actions().Bulk(ui.KeyMap{ ui.KeyHelp: ui.NewKeyAction("Help", t.App().helpCmd, true), ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false), tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Mark Range", t.markSpanCmd, false), diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 820f8754e1..34dfc81e0f 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -13,7 +13,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" - "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/ui" "github.com/rs/zerolog/log" ) @@ -41,8 +41,8 @@ func computeFilename(dumpPath, ns, title, path string) (string, error) { return strings.ToLower(filepath.Join(dir, fName)), nil } -func saveTable(dir, title, path string, data *render.TableData) (string, error) { - ns := data.Namespace +func saveTable(dir, title, path string, data *model1.TableData) (string, error) { + ns := data.GetNamespace() if client.IsClusterWide(ns) { ns = client.NamespaceAll } @@ -65,15 +65,12 @@ func saveTable(dir, title, path string, data *render.TableData) (string, error) }() w := csv.NewWriter(out) - if err := w.Write(data.Header.Columns(true)); err != nil { - return "", err - } + _ = w.Write(data.ColumnNames(true)) - for _, re := range data.RowEvents { - if err := w.Write(re.Row.Fields); err != nil { - return "", err - } - } + data.RowsRange(func(_ int, re model1.RowEvent) bool { + _ = w.Write(re.Row.Fields) + return true + }) w.Flush() if err := w.Error(); err != nil { return "", err diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index d87f9e90c3..38231eaee4 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -5,6 +5,8 @@ package view import ( "context" + "errors" + "io/fs" "os" "testing" "time" @@ -15,6 +17,7 @@ import ( "github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" @@ -41,28 +44,30 @@ func TestTableNew(t *testing.T) { v := NewTable(client.NewGVR("test")) assert.NoError(t, v.Init(makeContext())) - data := render.NewTableData() - data.Header = render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "NAME", Align: tview.AlignRight}, - render.HeaderColumn{Name: "FRED"}, - render.HeaderColumn{Name: "AGE", Time: true, Decorator: render.AgeDecorator}, - } - data.RowEvents = render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "a", "10", "3m"}, - }, + data := model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "FRED"}, + model1.HeaderColumn{Name: "AGE", Time: true, Decorator: render.AgeDecorator}, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "b", "15", "1m"}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "a", "10", "3m"}, + }, }, - }, - } - data.Namespace = "" + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "b", "15", "1m"}, + }, + }, + ), + ) + cdata := v.Update(data, false) + v.UpdateUI(cdata, data) - v.Update(data, false) assert.Equal(t, 3, v.GetRowCount()) } @@ -71,6 +76,7 @@ func TestTableViewFilter(t *testing.T) { assert.NoError(t, v.Init(makeContext())) v.SetModel(&mockTableModel{}) v.Refresh() + v.CmdBuff().SetActive(true) v.CmdBuff().SetText("blee", "") @@ -80,7 +86,7 @@ func TestTableViewFilter(t *testing.T) { func TestTableViewSort(t *testing.T) { v := NewTable(client.NewGVR("test")) assert.NoError(t, v.Init(makeContext())) - v.SetModel(&mockTableModel{}) + v.SetModel(new(mockTableModel)) uu := map[string]struct { sortCol string @@ -130,9 +136,9 @@ func (t *mockTableModel) SetInstance(string) {} func (t *mockTableModel) SetLabelFilter(string) {} func (t *mockTableModel) GetLabelFilter() string { return "" } func (t *mockTableModel) Empty() bool { return false } -func (t *mockTableModel) Count() int { return 1 } +func (t *mockTableModel) RowCount() int { return 1 } func (t *mockTableModel) HasMetrics() bool { return true } -func (t *mockTableModel) Peek() *render.TableData { return makeTableData() } +func (t *mockTableModel) Peek() *model1.TableData { return makeTableData() } func (t *mockTableModel) Refresh(context.Context) error { return nil } func (t *mockTableModel) ClusterWide() bool { return false } func (t *mockTableModel) GetNamespace() string { return "blee" } @@ -160,39 +166,39 @@ func (t *mockTableModel) ToYAML(ctx context.Context, path string) (string, error func (t *mockTableModel) InNamespace(string) bool { return true } func (t *mockTableModel) SetRefreshRate(time.Duration) {} -func makeTableData() *render.TableData { - t := render.NewTableData() - t.Header = render.Header{ - render.HeaderColumn{Name: "NAMESPACE"}, - render.HeaderColumn{Name: "NAME", Align: tview.AlignRight}, - render.HeaderColumn{Name: "FRED"}, - render.HeaderColumn{Name: "AGE", Time: true}, - } - t.RowEvents = render.RowEvents{ - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "r3", "10", "3y125d"}, - }, +func makeTableData() *model1.TableData { + return model1.NewTableDataWithRows( + client.NewGVR("test"), + model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME", Align: tview.AlignRight}, + model1.HeaderColumn{Name: "FRED"}, + model1.HeaderColumn{Name: "AGE", Time: true}, }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "r2", "15", "2y12d"}, + model1.NewRowEventsWithEvts( + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "r3", "10", "3y125d"}, + }, }, - Deltas: render.DeltaRow{"", "", "20", ""}, - }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "r1", "20", "19h"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "r2", "15", "2y12d"}, + }, + Deltas: model1.DeltaRow{"", "", "20", ""}, }, - }, - render.RowEvent{ - Row: render.Row{ - Fields: render.Fields{"ns1", "r0", "15", "10s"}, + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "r1", "20", "19h"}, + }, }, - }, - } - - return t + model1.RowEvent{ + Row: model1.Row{ + Fields: model1.Fields{"ns1", "r0", "15", "10s"}, + }, + }, + ), + ) } func makeContext() context.Context { @@ -201,31 +207,9 @@ func makeContext() context.Context { return context.WithValue(ctx, internal.KeyStyles, a.Styles) } -// type ks struct{} - -// func (k ks) CurrentContextName() (string, error) { -// return "test", nil -// } - -// func (k ks) CurrentClusterName() (string, error) { -// return "test", nil -// } - -// func (k ks) CurrentNamespaceName() (string, error) { -// return "test", nil -// } - -// func (k ks) ContextNames() (map[string]struct{}, error) { -// return map[string]struct{}{"test": {}}, nil -// } - -// func (k ks) NamespaceNames(nn []v1.Namespace) []string { -// return []string{"test"} -// } - func ensureDumpDir(n string) error { config.AppDumpsDir = n - if _, err := os.Stat(n); os.IsNotExist(err) { + if _, err := os.Stat(n); errors.Is(err, fs.ErrNotExist) { return os.Mkdir(n, 0700) } if err := os.RemoveAll(n); err != nil { diff --git a/internal/view/types.go b/internal/view/types.go index 070db98c79..3f9d68627a 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -40,7 +40,7 @@ type ( ContextFunc func(context.Context) context.Context // BindKeysFunc adds new menu actions. - BindKeysFunc func(ui.KeyActions) + BindKeysFunc func(*ui.KeyActions) ) // ActionExtender enhances a given viewer by adding new menu actions. @@ -60,7 +60,7 @@ type Viewer interface { model.Component // Actions returns active menu bindings. - Actions() ui.KeyActions + Actions() *ui.KeyActions // App returns an app handle. App() *App diff --git a/internal/view/user.go b/internal/view/user.go index 12477d9cae..94f55fd8ec 100644 --- a/internal/view/user.go +++ b/internal/view/user.go @@ -27,9 +27,9 @@ func NewUser(gvr client.GVR) ResourceViewer { return &u } -func (u *User) bindKeys(aa ui.KeyActions) { +func (u *User) bindKeys(aa *ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace, tcell.KeyCtrlD, ui.KeyE) - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ tcell.KeyEnter: ui.NewKeyAction("Rules", u.policyCmd, true), ui.KeyShiftK: ui.NewKeyAction("Sort Kind", u.GetTable().SortColCmd("KIND", true), false), }) diff --git a/internal/view/value_extender.go b/internal/view/value_extender.go index 795da9f740..8d3a244c00 100644 --- a/internal/view/value_extender.go +++ b/internal/view/value_extender.go @@ -30,10 +30,8 @@ func NewValueExtender(r ResourceViewer) ResourceViewer { return &p } -func (v *ValueExtender) bindKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyV: ui.NewKeyAction("Values", v.valuesCmd, true), - }) +func (v *ValueExtender) bindKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyV, ui.NewKeyAction("Values", v.valuesCmd, true)) } func (v *ValueExtender) valuesCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -72,10 +70,7 @@ func showValues(ctx context.Context, app *App, path string, gvr client.GVR) { } v := NewLiveView(app, "Values", vm) - v.actions.Add(ui.KeyActions{ - ui.KeyV: ui.NewKeyAction("Toggle All Values", toggleValuesCmd, true), - }) - + v.actions.Add(ui.KeyV, ui.NewKeyAction("Toggle All Values", toggleValuesCmd, true)) if err := v.app.inject(v, false); err != nil { v.app.Flash().Err(err) } diff --git a/internal/view/vul_extender.go b/internal/view/vul_extender.go index 5c1774d62c..ebc373b6d6 100644 --- a/internal/view/vul_extender.go +++ b/internal/view/vul_extender.go @@ -25,9 +25,9 @@ func NewVulnerabilityExtender(r ResourceViewer) ResourceViewer { return &v } -func (v *VulnerabilityExtender) bindKeys(aa ui.KeyActions) { +func (v *VulnerabilityExtender) bindKeys(aa *ui.KeyActions) { if v.App().Config.K9s.ImageScans.Enable { - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyV: ui.NewKeyAction("Show Vulnerabilities", v.showVulCmd, true), ui.KeyShiftV: ui.NewKeyAction("Sort Vulnerabilities", v.GetTable().SortColCmd("VS", true), false), }) diff --git a/internal/view/workload.go b/internal/view/workload.go index 0f66508e0f..4f0ba02fcd 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -36,18 +36,14 @@ func NewWorkload(gvr client.GVR) ResourceViewer { return &w } -func (w *Workload) bindDangerousKeys(aa ui.KeyActions) { - aa.Add(ui.KeyActions{ - ui.KeyE: ui.NewKeyActionWithOpts( - "Edit", - w.editCmd, +func (w *Workload) bindDangerousKeys(aa *ui.KeyActions) { + aa.Bulk(ui.KeyMap{ + ui.KeyE: ui.NewKeyActionWithOpts("Edit", w.editCmd, ui.ActionOpts{ Visible: true, Dangerous: true, }), - tcell.KeyCtrlD: ui.NewKeyActionWithOpts( - "Delete", - w.deleteCmd, + tcell.KeyCtrlD: ui.NewKeyActionWithOpts("Delete", w.deleteCmd, ui.ActionOpts{ Visible: true, Dangerous: true, @@ -55,12 +51,12 @@ func (w *Workload) bindDangerousKeys(aa ui.KeyActions) { }) } -func (w *Workload) bindKeys(aa ui.KeyActions) { +func (w *Workload) bindKeys(aa *ui.KeyActions) { if !w.App().Config.K9s.IsReadOnly() { w.bindDangerousKeys(aa) } - aa.Add(ui.KeyActions{ + aa.Bulk(ui.KeyMap{ ui.KeyShiftK: ui.NewKeyAction("Sort Kind", w.GetTable().SortColCmd("KIND", true), false), ui.KeyShiftS: ui.NewKeyAction("Sort Status", w.GetTable().SortColCmd(statusCol, true), false), ui.KeyShiftR: ui.NewKeyAction("Sort Ready", w.GetTable().SortColCmd("READY", true), false), @@ -114,7 +110,7 @@ func (w *Workload) defaultContext(gvr client.GVR, fqn string) context.Context { if fqn != "" { ctx = context.WithValue(ctx, internal.KeyPath, fqn) } - if ui.IsLabelSelector(w.GetTable().CmdBuff().GetText()) { + if internal.IsLabelSelector(w.GetTable().CmdBuff().GetText()) { ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(w.GetTable().CmdBuff().GetText())) } ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(w.App().Config.ActiveNamespace())) @@ -208,34 +204,3 @@ func (w *Workload) yamlCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - -// func (w *Workload) editCmd(evt *tcell.EventKey) *tcell.EventKey { -// path := w.GetTable().GetSelectedItem() -// if path == "" { -// return evt -// } -// gvr, fqn, ok := parsePath(path) -// if !ok { -// w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) -// return evt -// } - -// w.Stop() -// defer w.Start() -// { -// ns, n := client.Namespaced(fqn) -// args := make([]string, 0, 10) -// args = append(args, "edit") -// args = append(args, gvr.R()) -// args = append(args, "-n", ns) -// args = append(args, "--context", w.App().Config.K9s.CurrentContext) -// if cfg := w.App().Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" { -// args = append(args, "--kubeconfig", *cfg) -// } -// if err := runK(w.App(), shellOpts{args: append(args, n)}); err != nil { -// w.App().Flash().Errf("Edit exec failed: %s", err) -// } -// } - -// return evt -// } diff --git a/internal/view/xray.go b/internal/view/xray.go index 8bab5c70c2..34e9e0ccad 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -117,7 +117,7 @@ func (x *Xray) ExtraHints() map[string]string { func (x *Xray) SetInstance(string) {} func (x *Xray) bindKeys() { - x.Actions().Add(ui.KeyActions{ + x.Actions().Bulk(ui.KeyMap{ ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false), tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", x.resetCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", x.gotoCmd, true), @@ -130,7 +130,7 @@ func (x *Xray) keyEntered() { } func (x *Xray) refreshActions() { - aa := make(ui.KeyActions) + aa := ui.NewKeyActions() defer func() { if err := pluginActions(x, aa); err != nil { @@ -140,7 +140,7 @@ func (x *Xray) refreshActions() { log.Warn().Err(err).Msg("HotKeys load failed") } - x.Actions().Add(aa) + x.Actions().Merge(aa) x.app.Menu().HydrateMenu(x.Hints()) }() @@ -162,14 +162,16 @@ func (x *Xray) refreshActions() { } if client.Can(x.meta.Verbs, "edit") { - aa[ui.KeyE] = ui.NewKeyAction("Edit", x.editCmd, true) + aa.Add(ui.KeyE, ui.NewKeyAction("Edit", x.editCmd, true)) } if client.Can(x.meta.Verbs, "delete") { - aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", x.deleteCmd, true) + aa.Add(tcell.KeyCtrlD, ui.NewKeyAction("Delete", x.deleteCmd, true)) } if !dao.IsK9sMeta(x.meta) { - aa[ui.KeyY] = ui.NewKeyAction(yamlAction, x.viewCmd, true) - aa[ui.KeyD] = ui.NewKeyAction("Describe", x.describeCmd, true) + aa.Bulk(ui.KeyMap{ + ui.KeyY: ui.NewKeyAction(yamlAction, x.viewCmd, true), + ui.KeyD: ui.NewKeyAction("Describe", x.describeCmd, true), + }) } switch gvr { @@ -177,16 +179,20 @@ func (x *Xray) refreshActions() { x.Actions().Delete(tcell.KeyEnter) case "containers": x.Actions().Delete(tcell.KeyEnter) - aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true) - aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true) - aa[ui.KeyP] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true) + aa.Bulk(ui.KeyMap{ + ui.KeyS: ui.NewKeyAction("Shell", x.shellCmd, true), + ui.KeyL: ui.NewKeyAction("Logs", x.logsCmd(false), true), + ui.KeyP: ui.NewKeyAction("Logs Previous", x.logsCmd(true), true), + }) case "v1/pods": - aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true) - aa[ui.KeyA] = ui.NewKeyAction("Attach", x.attachCmd, true) - aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true) - aa[ui.KeyP] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true) + aa.Bulk(ui.KeyMap{ + ui.KeyS: ui.NewKeyAction("Shell", x.shellCmd, true), + ui.KeyA: ui.NewKeyAction("Attach", x.attachCmd, true), + ui.KeyL: ui.NewKeyAction("Logs", x.logsCmd(false), true), + ui.KeyP: ui.NewKeyAction("Logs Previous", x.logsCmd(true), true), + }) } - x.Actions().Add(aa) + x.Actions().Merge(aa) } // GetSelectedPath returns the current selection as string. @@ -454,7 +460,7 @@ func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if x.CmdBuff().IsActive() { - if ui.IsLabelSelector(x.CmdBuff().GetText()) { + if internal.IsLabelSelector(x.CmdBuff().GetText()) { x.Start() } x.CmdBuff().SetActive(false) @@ -477,16 +483,16 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { func (x *Xray) filter(root *xray.TreeNode) *xray.TreeNode { q := x.CmdBuff().GetText() - if x.CmdBuff().Empty() || ui.IsLabelSelector(q) { + if x.CmdBuff().Empty() || internal.IsLabelSelector(q) { return root } x.UpdateTitle() - if f, ok := dao.HasFuzzySelector(q); ok { + if f, ok := internal.IsFuzzySelector(q); ok { return root.Filter(f, fuzzyFilter) } - if dao.IsInverseSelector(q) { + if internal.IsInverseSelector(q) { return root.Filter(q, rxInverseFilter) } @@ -661,7 +667,7 @@ func (x *Xray) styleTitle() string { if buff == "" { return title } - if ui.IsLabelSelector(buff) { + if internal.IsLabelSelector(buff) { buff = ui.TrimLabelSelector(buff) } diff --git a/internal/xray/pod.go b/internal/xray/pod.go index 69bb293f12..dbcbf92008 100644 --- a/internal/xray/pod.go +++ b/internal/xray/pod.go @@ -65,7 +65,6 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error { func (p *Pod) validate(node *TreeNode, po v1.Pod) error { var re render.Pod - phase := re.Phase(&po) ss := po.Status.ContainerStatuses cr, _, _ := re.Statuses(ss) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a8b389e7ec..090c2e652d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.31.9' +version: 'v0.32.0' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From b850775d412d17d24c3a7565d2a8cb41052aff8e Mon Sep 17 00:00:00 2001 From: Alexej Disterhoft Date: Mon, 4 Mar 2024 21:56:38 +0100 Subject: [PATCH 117/169] fix: properly initialize key actions in picker (#2586) This commit fixes a bug introcuded in v0.32.0 where the shell-in and attach commands would fail with a nil pointer exception for pods with more than one container. Resolves #2585 --- internal/view/picker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/view/picker.go b/internal/view/picker.go index 56e0275181..ce39b6b945 100644 --- a/internal/view/picker.go +++ b/internal/view/picker.go @@ -23,7 +23,7 @@ type Picker struct { func NewPicker() *Picker { return &Picker{ List: tview.NewList(), - actions: ui.KeyActions{}, + actions: *ui.NewKeyActions(), } } From 38d70d6fd3edbe2a20b1f894ead7b14b2220bb25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:50:35 -0700 Subject: [PATCH 118/169] Bump github.com/sahilm/fuzzy from 0.1.0 to 0.1.1 (#2589) Bumps [github.com/sahilm/fuzzy](https://github.com/sahilm/fuzzy) from 0.1.0 to 0.1.1. - [Release notes](https://github.com/sahilm/fuzzy/releases) - [Commits](https://github.com/sahilm/fuzzy/compare/v0.1.0...v0.1.1) --- updated-dependencies: - dependency-name: github.com/sahilm/fuzzy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 798e70b01f..4a9e1ceff3 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/petergtz/pegomock v2.9.0+incompatible github.com/rakyll/hey v0.1.4 github.com/rs/zerolog v1.32.0 - github.com/sahilm/fuzzy v0.1.0 + github.com/sahilm/fuzzy v0.1.1 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 diff --git a/go.sum b/go.sum index c22b7ad5cb..c5faa11aec 100644 --- a/go.sum +++ b/go.sum @@ -1028,8 +1028,8 @@ github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9c github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= From 6c6fc22393d9880a26df5575a2d40fa602bd3c6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:50:51 -0700 Subject: [PATCH 119/169] Bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#2588) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a9e1ceff3..71f8bf8c71 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/rs/zerolog v1.32.0 github.com/sahilm/fuzzy v0.1.1 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index c5faa11aec..8c15057ee3 100644 --- a/go.sum +++ b/go.sum @@ -1084,8 +1084,9 @@ github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1095,8 +1096,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= From 69cd0cd707a14c68ffab9ac708a7d82a5b53bd2b Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Mon, 4 Mar 2024 17:57:20 -0700 Subject: [PATCH 120/169] K9s/release v0.32.1 (#2591) * [Bug] Fix #2579 * [Bug] Fix #2584 * [Exp] make pf address configurable via K9S_DEFAULT_PF_ADDRESS * v0.32.1 release --- Makefile | 2 +- change_logs/release_v0.32.1.md | 51 +++++++++++++++++++++++++++++++ internal/config/data/context.go | 21 +++++-------- internal/config/data/helpers.go | 13 ++++++++ internal/dao/registry.go | 2 +- internal/model1/table_data.go | 1 + internal/ui/dialog/transfer.go | 53 ++++++++++++++++++++------------- internal/view/exec.go | 12 ++++++-- internal/view/pf_dialog.go | 2 +- internal/view/pod.go | 50 ++++++++++++++++--------------- snap/snapcraft.yaml | 2 +- 11 files changed, 146 insertions(+), 63 deletions(-) create mode 100644 change_logs/release_v0.32.1.md diff --git a/Makefile b/Makefile index 36c29177ce..bf7861e6ac 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.32.0 +VERSION ?= v0.32.1 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.1.md b/change_logs/release_v0.32.1.md new file mode 100644 index 0000000000..6018dd45e2 --- /dev/null +++ b/change_logs/release_v0.32.1.md @@ -0,0 +1,51 @@ + + +# Release v0.32.1 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +The aftermath ;( + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption +* [#2579](https://github.com/derailed/k9s/issues/2579) Default sorting behavior changed to descending sort bug + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2586](https://github.com/derailed/k9s/pull/2586) Properly initialize key actions in picker + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 591f072f7b..48fbe7c3b0 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -10,9 +10,6 @@ import ( "k8s.io/client-go/tools/clientcmd/api" ) -// DefaultPFAddress specifies the default PortForward host address. -const DefaultPFAddress = "localhost" - // Context tracks K9s context configuration. type Context struct { ClusterName string `yaml:"cluster,omitempty"` @@ -30,20 +27,18 @@ func NewContext() *Context { return &Context{ Namespace: NewNamespace(), View: NewView(), - PortForwardAddress: DefaultPFAddress, + PortForwardAddress: defaultPFAddress(), FeatureGates: NewFeatureGates(), } } // NewContextFromConfig returns a config based on a kubecontext. func NewContextFromConfig(cfg *api.Context) *Context { - return &Context{ - Namespace: NewActiveNamespace(cfg.Namespace), - ClusterName: cfg.Cluster, - View: NewView(), - PortForwardAddress: DefaultPFAddress, - FeatureGates: NewFeatureGates(), - } + ct := NewContext() + ct.Namespace, ct.ClusterName = NewActiveNamespace(cfg.Namespace), cfg.Cluster + + return ct + } // NewContextFromKubeConfig returns a new instance based on kubesettings or an error. @@ -61,8 +56,8 @@ func (c *Context) merge(old *Context) { return } c.Namespace.merge(old.Namespace) - } + func (c *Context) GetClusterName() string { c.mx.RLock() defer c.mx.RUnlock() @@ -76,7 +71,7 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { defer c.mx.Unlock() if c.PortForwardAddress == "" { - c.PortForwardAddress = DefaultPFAddress + c.PortForwardAddress = defaultPFAddress() } if cl, err := ks.CurrentClusterName(); err == nil { c.ClusterName = cl diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go index ae6cc6e9c6..5295972d65 100644 --- a/internal/config/data/helpers.go +++ b/internal/config/data/helpers.go @@ -11,6 +11,11 @@ import ( "regexp" ) +const ( + envPFAddress = "K9S_DEFAULT_PF_ADDRESS" + defaultPortFwdAddress = "localhost" +) + var invalidPathCharsRX = regexp.MustCompile(`[:/]+`) // SanitizeContextSubpath ensure cluster/context produces a valid path. @@ -23,6 +28,14 @@ func SanitizeFileName(name string) string { return invalidPathCharsRX.ReplaceAllString(name, "-") } +func defaultPFAddress() string { + if a := os.Getenv(envPFAddress); a != "" { + return a + } + + return defaultPortFwdAddress +} + // InList check if string is in a collection of strings. func InList(ll []string, n string) bool { for _, l := range ll { diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 3aaf1bf5ac..fd19c6c4e9 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -101,7 +101,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { client.NewGVR("v1/pods"): &Pod{}, client.NewGVR("v1/nodes"): &Node{}, client.NewGVR("v1/namespaces"): &Namespace{}, - client.NewGVR("v1/configmap"): &ConfigMap{}, + client.NewGVR("v1/configmaps"): &ConfigMap{}, client.NewGVR("v1/secrets"): &Secret{}, client.NewGVR("apps/v1/deployments"): &Deployment{}, client.NewGVR("apps/v1/daemonsets"): &DaemonSet{}, diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go index 2b8cd096fe..10b0c3c654 100644 --- a/internal/model1/table_data.go +++ b/internal/model1/table_data.go @@ -376,6 +376,7 @@ func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) { psc.Name = t.header[0].Name } } + psc.ASC = true return psc, nil } diff --git a/internal/ui/dialog/transfer.go b/internal/ui/dialog/transfer.go index 7db559c0d6..41c60e0e41 100644 --- a/internal/ui/dialog/transfer.go +++ b/internal/ui/dialog/transfer.go @@ -4,6 +4,7 @@ package dialog import ( + "strconv" "strings" "github.com/derailed/k9s/internal/config" @@ -13,12 +14,19 @@ import ( const confirmKey = "confirm" -type TransferFn func(from, to, co string, download, no_preserve bool) bool +type TransferFn func(TransferArgs) bool + +type TransferArgs struct { + From, To, CO string + Download, NoPreserve bool + Retries int +} type TransferDialogOpts struct { Containers []string Pod string Title, Message string + Retries int Ack TransferFn Cancel cancelFunc } @@ -38,44 +46,49 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts) modal := tview.NewModalForm("<"+opts.Title+">", f) - from, to := opts.Pod, "" + args := TransferArgs{ + From: opts.Pod, + Retries: opts.Retries, + } var fromField, toField *tview.InputField - download := true - f.AddCheckbox("Download:", download, func(_ string, flag bool) { + args.Download = true + f.AddCheckbox("Download:", args.Download, func(_ string, flag bool) { if flag { modal.SetText(strings.Replace(opts.Message, "Upload", "Download", 1)) } else { modal.SetText(strings.Replace(opts.Message, "Download", "Upload", 1)) } - download = flag - from, to = to, from - fromField.SetText(from) - toField.SetText(to) + args.Download = flag + args.From, args.To = args.To, args.From + fromField.SetText(args.From) + toField.SetText(args.To) }) - f.AddInputField("From:", from, 40, nil, func(t string) { - from = t + f.AddInputField("From:", args.From, 40, nil, func(v string) { + args.From = v }) - f.AddInputField("To:", to, 40, nil, func(t string) { - to = t + f.AddInputField("To:", args.To, 40, nil, func(v string) { + args.To = v }) fromField, _ = f.GetFormItemByLabel("From:").(*tview.InputField) toField, _ = f.GetFormItemByLabel("To:").(*tview.InputField) - var no_preserve bool - f.AddCheckbox("NoPreserve:", no_preserve, func(_ string, f bool) { - no_preserve = f + f.AddCheckbox("NoPreserve:", args.NoPreserve, func(_ string, f bool) { + args.NoPreserve = f }) - var co string if len(opts.Containers) > 0 { - co = opts.Containers[0] + args.CO = opts.Containers[0] } - f.AddInputField("Container:", co, 30, nil, func(t string) { - co = t + f.AddInputField("Container:", args.CO, 30, nil, func(v string) { + args.CO = v + }) + retries := strconv.Itoa(opts.Retries) + f.AddInputField("Retries:", retries, 30, nil, func(v string) { + retries = v }) f.AddButton("OK", func() { - if !opts.Ack(from, to, co, download, no_preserve) { + if !opts.Ack(args) { return } dismissConfirm(pages) diff --git a/internal/view/exec.go b/internal/view/exec.go index 520731816b..e692e8b5d9 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -79,10 +79,13 @@ func runK(a *App, opts shellOpts) error { } opts.binary = bin - suspended, errChan, _ := run(a, opts) + suspended, errChan, stChan := run(a, opts) if !suspended { return fmt.Errorf("unable to run command") } + for v := range stChan { + log.Debug().Msgf(" - %s", v) + } var errs error for e := range errChan { errs = errors.Join(errs, e) @@ -474,7 +477,7 @@ func asResource(r config.Limits) v1.ResourceRequirements { } } -func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.Writer, cmds ...*exec.Cmd) error { +func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *bytes.Buffer, cmds ...*exec.Cmd) error { if len(cmds) == 0 { return nil } @@ -487,6 +490,11 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e io.W if err := cmd.Run(); err != nil { log.Error().Err(err).Msgf("Command failed: %s", err) } else { + for _, l := range strings.Split(w.String(), "\n") { + if l != "" { + statusChan <- fmt.Sprintf("[output] %s", l) + } + } statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20)) log.Info().Msgf("Command completed successfully: %q", cmd.String()) } diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index 8a79167ff0..27f0d29b0e 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -38,7 +38,6 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe log.Error().Err(err).Msgf("No active context detected") return } - address := ct.PortForwardAddress pf, err := aa.PreferredPorts(ports) if err != nil { @@ -62,6 +61,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe if loField.GetText() == "" { loField.SetPlaceholder("Enter a local port") } + address := ct.PortForwardAddress f.AddInputField("Address:", address, fieldLen, nil, func(h string) { address = h }) diff --git a/internal/view/pod.go b/internal/view/pod.go index cdd2a92428..fe223b33fe 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -30,13 +30,14 @@ import ( ) const ( - windowsOS = "windows" - powerShell = "powershell" - osSelector = "kubernetes.io/os" - osBetaSelector = "beta." + osSelector - trUpload = "Upload" - trDownload = "Download" - pfIndicator = "[orange::b]Ⓕ" + windowsOS = "windows" + powerShell = "powershell" + osSelector = "kubernetes.io/os" + osBetaSelector = "beta." + osSelector + trUpload = "Upload" + trDownload = "Download" + pfIndicator = "[orange::b]Ⓕ" + defaultTxRetries = 999 ) // Pod represents a pod viewer. @@ -310,36 +311,36 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey { } ns, n := client.Namespaced(path) - ack := func(from, to, co string, download, no_preserve bool) bool { - local := to - if !download { - local = from + ack := func(args dialog.TransferArgs) bool { + local := args.To + if !args.Download { + local = args.From } - if _, err := os.Stat(local); !download && errors.Is(err, fs.ErrNotExist) { + if _, err := os.Stat(local); !args.Download && errors.Is(err, fs.ErrNotExist) { p.App().Flash().Err(err) return false } - args := make([]string, 0, 10) - args = append(args, "cp") - args = append(args, strings.TrimSpace(from)) - args = append(args, strings.TrimSpace(to)) - args = append(args, fmt.Sprintf("--no-preserve=%t", no_preserve)) - if co != "" { - args = append(args, "-c="+co) + opts := make([]string, 0, 10) + opts = append(opts, "cp") + opts = append(opts, strings.TrimSpace(args.From)) + opts = append(opts, strings.TrimSpace(args.To)) + opts = append(opts, fmt.Sprintf("--no-preserve=%t", args.NoPreserve)) + if args.CO != "" { + opts = append(opts, "-c="+args.CO) } - opts := shellOpts{ + cliOpts := shellOpts{ background: true, - args: args, + args: opts, } op := trUpload - if download { + if args.Download { op = trDownload } - fqn := path + ":" + co - if err := runK(p.App(), opts); err != nil { + fqn := path + ":" + args.CO + if err := runK(p.App(), cliOpts); err != nil { p.App().cowCmd(err.Error()) } else { p.App().Flash().Infof("%s successful on %s!", op, fqn) @@ -359,6 +360,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey { Message: "Download Files", Pod: fmt.Sprintf("%s/%s:", ns, n), Ack: ack, + Retries: defaultTxRetries, Cancel: func() {}, } dialog.ShowUploads(p.App().Styles.Dialog(), p.App().Content.Pages, opts) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 090c2e652d..5c2fdaa4ca 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.32.0' +version: 'v0.32.1' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From ecd33ff48d5b4c02998cddc12274b1f0afb4ff0c Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 5 Mar 2024 17:11:31 -0700 Subject: [PATCH 121/169] K9s release v0.32.2 (#2598) * [Maint] cleaning up * [Bug] Fix #2593 * [Bug] Fix #2582 * Release v0.32.2 --- Makefile | 2 +- change_logs/release_v0.32.2.md | 43 ++++++++++++++ internal/client/client.go | 15 ++++- internal/client/types.go | 3 + internal/config/alias.go | 3 +- internal/config/alias_test.go | 7 ++- internal/config/testdata/aliases/plain.yaml | 4 +- internal/dao/dp.go | 4 +- internal/dao/ds.go | 4 +- internal/dao/node.go | 4 +- internal/dao/pod.go | 4 +- internal/dao/port_forwarder.go | 2 +- internal/dao/registry.go | 66 ++++++--------------- internal/dao/sts.go | 4 +- internal/model/table.go | 4 ++ internal/model1/table_data.go | 2 +- internal/view/browser.go | 2 +- snap/snapcraft.yaml | 2 +- 18 files changed, 105 insertions(+), 70 deletions(-) create mode 100644 change_logs/release_v0.32.2.md diff --git a/Makefile b/Makefile index bf7861e6ac..9a0824facf 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.32.1 +VERSION ?= v0.32.2 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.2.md b/change_logs/release_v0.32.2.md new file mode 100644 index 0000000000..96affe5958 --- /dev/null +++ b/change_logs/release_v0.32.2.md @@ -0,0 +1,43 @@ + + +# Release v0.32.2 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Mo aftermath ;( + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2582](https://github.com/derailed/k9s/issues/2582) Slowness due to client-side throttling in v0.32.0 (Maybe??) +* [#2593](https://github.com/derailed/k9s/issues/2593) Popeye not working in 0.32.X + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/internal/client/client.go b/internal/client/client.go index e12f900530..aaac43e8e4 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -246,7 +246,7 @@ func (a *APIClient) ValidNamespaceNames() (NamespaceNames, error) { } } - ok, err := a.CanI(ClusterScope, "v1/namespaces", "", []string{ListVerb}) + ok, err := a.CanI(ClusterScope, "v1/namespaces", "", ListAccess) if !ok || err != nil { return nil, fmt.Errorf("user not authorized to list all namespaces") } @@ -524,12 +524,25 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) { return a.getMxsClient(), err } +func (a *APIClient) invalidateCache() error { + dial, err := a.CachedDiscovery() + if err != nil { + return err + } + dial.Invalidate() + + return nil +} + // SwitchContext handles kubeconfig context switches. func (a *APIClient) SwitchContext(name string) error { log.Debug().Msgf("Switching context %q", name) if err := a.config.SwitchContext(name); err != nil { return err } + if err := a.invalidateCache(); err != nil { + return err + } a.reset() ResetMetrics() diff --git a/internal/client/types.go b/internal/client/types.go index 8dad38a06a..b5a7d3cf76 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -55,6 +55,9 @@ const ( ) var ( + // PatchAccess patch a resource. + PatchAccess = []string{PatchVerb} + // GetAccess reads a resource. GetAccess = []string{GetVerb} diff --git a/internal/config/alias.go b/internal/config/alias.go index be53f4027f..426d41d76f 100644 --- a/internal/config/alias.go +++ b/internal/config/alias.go @@ -177,7 +177,8 @@ func (a *Aliases) loadDefaultAliases() { a.declare("help", "h", "?") a.declare("quit", "q", "q!", "qa", "Q") a.declare("aliases", "alias", "a") - a.declare("popeye", "pop") + // !!BOZO!! + // a.declare("popeye", "pop") a.declare("helm", "charts", "chart", "hm") a.declare("dir", "d") a.declare("contexts", "context", "ctx") diff --git a/internal/config/alias_test.go b/internal/config/alias_test.go index d8551bfccf..c67f4f5824 100644 --- a/internal/config/alias_test.go +++ b/internal/config/alias_test.go @@ -6,6 +6,7 @@ package config_test import ( "fmt" "os" + "path" "slices" "testing" @@ -109,8 +110,8 @@ func TestAliasesLoad(t *testing.T) { config.AppConfigDir = "testdata/aliases" a := config.NewAliases() - assert.Nil(t, a.Load("testdata/aliases/plain.yaml")) - assert.Equal(t, 56, len(a.Alias)) + assert.Nil(t, a.Load(path.Join(config.AppConfigDir, "plain.yaml"))) + assert.Equal(t, 54, len(a.Alias)) } func TestAliasesSave(t *testing.T) { @@ -123,7 +124,7 @@ func TestAliasesSave(t *testing.T) { assert.Equal(t, c, len(a.Alias)) assert.Nil(t, a.Save()) - assert.Nil(t, a.LoadFile("/tmp/test-aliases/aliases.yaml")) + assert.Nil(t, a.LoadFile(config.AppAliasesFile)) assert.Equal(t, c, len(a.Alias)) } diff --git a/internal/config/testdata/aliases/plain.yaml b/internal/config/testdata/aliases/plain.yaml index 185113e7da..4291a3c7b6 100644 --- a/internal/config/testdata/aliases/plain.yaml +++ b/internal/config/testdata/aliases/plain.yaml @@ -1,3 +1,3 @@ aliases: - dp: "apps.v1.deployments" - pe: ".v1.pods" + dp: "apps/v1/deployments" + pe: "v1/pods" diff --git a/internal/dao/dp.go b/internal/dao/dp.go index f3273399e9..5df3432113 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -86,7 +86,7 @@ func (d *Deployment) Restart(ctx context.Context, path string) error { return err } - auth, err := d.Client().CanI(dp.Namespace, "apps/v1/deployments", dp.Name, []string{client.PatchVerb}) + auth, err := d.Client().CanI(dp.Namespace, "apps/v1/deployments", dp.Name, client.PatchAccess) if err != nil { return err } @@ -261,7 +261,7 @@ func (d *Deployment) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (d *Deployment) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/deployments", n, []string{client.PatchVerb}) + auth, err := d.Client().CanI(ns, "apps/v1/deployments", n, client.PatchAccess) if err != nil { return err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index b0bf8c0b69..3bbd7827c0 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -63,7 +63,7 @@ func (d *DaemonSet) Restart(ctx context.Context, path string) error { return err } - auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", ds.Name, []string{client.PatchVerb}) + auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", ds.Name, client.PatchAccess) if err != nil { return err } @@ -280,7 +280,7 @@ func (d *DaemonSet) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (d *DaemonSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/daemonset", n, []string{client.PatchVerb}) + auth, err := d.Client().CanI(ns, "apps/v1/daemonset", n, client.PatchAccess) if err != nil { return err } diff --git a/internal/dao/node.go b/internal/dao/node.go index 1ee29ed143..e55f40c5de 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -248,7 +248,7 @@ func (n *Node) ensureCordoned(path string) (bool, error) { // FetchNode retrieves a node. func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) { _, n := client.Namespaced(path) - auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", n, []string{"get"}) + auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", n, client.GetAccess) if err != nil { return nil, err } @@ -272,7 +272,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) { // FetchNodes retrieves all nodes. func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) { - auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", "", []string{client.ListVerb}) + auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", "", client.ListAccess) if err != nil { return nil, err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 4b03d66ed7..d65854498b 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -121,7 +121,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { // Logs fetch container logs for a given pod and container. func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) { ns, n := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods:log", n, []string{client.GetVerb}) + auth, err := p.Client().CanI(ns, "v1/pods:log", n, client.GetAccess) if err != nil { return nil, err } @@ -426,7 +426,7 @@ func (p *Pod) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pod", n, []string{client.PatchVerb}) + auth, err := p.Client().CanI(ns, "v1/pod", n, client.PatchAccess) if err != nil { return err } diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 345ad3bb13..454af94285 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -117,7 +117,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por p.path, p.tunnel, p.age = path, tt, time.Now() ns, n := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods", n, []string{client.GetVerb}) + auth, err := p.Client().CanI(ns, "v1/pods", n, client.GetAccess) if err != nil { return nil, err } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index fd19c6c4e9..e19ad58600 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -21,50 +21,24 @@ const ( crdCat = "crd" k9sCat = "k9s" helmCat = "helm" + crdGVR = "apiextensions.k8s.io/v1/customresourcedefinitions" ) // MetaAccess tracks resources metadata. var MetaAccess = NewMeta() -var stdGroups = []string{ - "admissionregistration.k8s.io/v1", - "admissionregistration.k8s.io/v1beta1", - "apiextensions.k8s.io/v1", - "apiextensions.k8s.io/v1beta1", - "apiregistration.k8s.io/v1", - "apiregistration.k8s.io/v1beta1", - "apps/v1", - "authentication.k8s.io/v1", - "authentication.k8s.io/v1beta1", - "authorization.k8s.io/v1", - "authorization.k8s.io/v1beta1", - "autoscaling/v1", - "autoscaling/v2beta1", - "autoscaling/v2beta2", - "batch/v1", - "batch/v1beta1", - "certificates.k8s.io/v1", - "certificates.k8s.io/v1beta1", - "coordination.k8s.io/v1", - "coordination.k8s.io/v1beta1", - "discovery.k8s.io/v1beta1", - "dynatrace.com/v1alpha1", - "events.k8s.io/v1", - "extensions/v1beta1", - "flowcontrol.apiserver.k8s.io/v1beta1", - "metrics.k8s.io/v1beta1", - "networking.k8s.io/v1", - "networking.k8s.io/v1beta1", - "node.k8s.io/v1", - "node.k8s.io/v1beta1", - "policy/v1beta1", - "rbac.authorization.k8s.io/v1", - "rbac.authorization.k8s.io/v1beta1", - "scheduling.k8s.io/v1", - "scheduling.k8s.io/v1beta1", - "storage.k8s.io/v1", - "storage.k8s.io/v1beta1", - "v1", +var stdGroups = map[string]struct{}{ + "apps/v1": {}, + "autoscaling/v1": {}, + "autoscaling/v2": {}, + "autoscaling/v2beta1": {}, + "autoscaling/v2beta2": {}, + "batch/v1": {}, + "batch/v1beta1": {}, + "extensions/v1beta1": {}, + "policy/v1beta1": {}, + "policy/v1": {}, + "v1": {}, } func (m ResourceMetas) clear() { @@ -372,7 +346,6 @@ func loadPreferred(f Factory, m ResourceMetas) error { if err != nil { return err } - dial.Invalidate() rr, err := dial.ServerPreferredResources() if err != nil { log.Debug().Err(err).Msgf("Failed to load preferred resources") @@ -387,7 +360,7 @@ func loadPreferred(f Factory, m ResourceMetas) error { if res.SingularName == "" { res.SingularName = strings.ToLower(res.Kind) } - if !isStandardGroup(res.Group) { + if !isStandardGroup(r.GroupVersion) { res.Categories = append(res.Categories, crdCat) } m[gvr] = res @@ -397,14 +370,12 @@ func loadPreferred(f Factory, m ResourceMetas) error { return nil } -func isStandardGroup(r string) bool { - for _, res := range stdGroups { - if strings.Index(res, r) == 0 { - return true - } +func isStandardGroup(gv string) bool { + if _, ok := stdGroups[gv]; ok { + return true } - return false + return strings.Contains(gv, "k8s.io") } var deprecatedGVRs = map[client.GVR]struct{}{ @@ -420,7 +391,6 @@ func loadCRDs(f Factory, m ResourceMetas) { if f.Client() == nil || !f.Client().ConnectionOK() { return } - const crdGVR = "apiextensions.k8s.io/v1/customresourcedefinitions" oo, err := f.List(crdGVR, client.ClusterScope, false, labels.Everything()) if err != nil { log.Warn().Err(err).Msgf("Fail CRDs load") diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 0137640957..9c111d523f 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -91,7 +91,7 @@ func (s *StatefulSet) Restart(ctx context.Context, path string) error { s.Forwarders().Kill(client.FQN(p.Namespace, p.Name)) } - auth, err := s.Client().CanI(sts.Namespace, "apps/v1/statefulsets", n, []string{client.PatchVerb}) + auth, err := s.Client().CanI(sts.Namespace, "apps/v1/statefulsets", n, client.PatchAccess) if err != nil { return err } @@ -291,7 +291,7 @@ func (s *StatefulSet) GetPodSpec(path string) (*v1.PodSpec, error) { // SetImages sets container images. func (s *StatefulSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error { ns, n := client.Namespaced(path) - auth, err := s.Client().CanI(ns, "apps/v1/statefulset", n, []string{client.PatchVerb}) + auth, err := s.Client().CanI(ns, "apps/v1/statefulset", n, client.PatchAccess) if err != nil { return err } diff --git a/internal/model/table.go b/internal/model/table.go index ce848724ab..be1157b587 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -204,6 +204,10 @@ func (t *Table) updater(ctx context.Context) { } func (t *Table) refresh(ctx context.Context) error { + defer func(ti time.Time) { + log.Trace().Msgf("Refresh [%s](%d) %s ", t.gvr, t.data.RowCount(), time.Since(ti)) + }(time.Now()) + if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) { log.Debug().Msgf("Dropping update...") return nil diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go index 10b0c3c654..13ef48ce81 100644 --- a/internal/model1/table_data.go +++ b/internal/model1/table_data.go @@ -244,7 +244,6 @@ func (t *TableData) Reset(ns string) { func (t *TableData) Reconcile(ctx context.Context, r Renderer, oo []runtime.Object) error { var rows Rows - if len(oo) > 0 { if r.IsGeneric() { table, ok := oo[0].(*metav1.Table) @@ -399,6 +398,7 @@ func (t *TableData) Clone() *TableData { header: t.header.Clone(), rowEvents: t.rowEvents.Clone(), namespace: t.namespace, + gvr: t.gvr, } } diff --git a/internal/view/browser.go b/internal/view/browser.go index b6045a6de5..7f0876d1ab 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -442,7 +442,7 @@ func editRes(app *App, gvr client.GVR, path string) error { if gvr.String() == "v1/namespaces" { ns = n } - if ok, err := app.Conn().CanI(ns, gvr.String(), n, []string{"patch"}); !ok || err != nil { + if ok, err := app.Conn().CanI(ns, gvr.String(), n, client.PatchAccess); !ok || err != nil { return fmt.Errorf("current user can't edit resource %s", gvr) } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 5c2fdaa4ca..de4bd294a8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.32.1' +version: 'v0.32.2' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 394fec34531b36941a04a93fd458258e8d672bfb Mon Sep 17 00:00:00 2001 From: derailed Date: Tue, 5 Mar 2024 17:53:10 -0700 Subject: [PATCH 122/169] blee1 --- internal/client/client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/client/client.go b/internal/client/client.go index aaac43e8e4..9a39f583d7 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -169,7 +169,12 @@ func (a *APIClient) CanI(ns, gvr, name string, verbs []string) (auth bool, err e for _, v := range verbs { sar.Spec.ResourceAttributes.Verb = v resp, err := client.Create(ctx, sar, metav1.CreateOptions{}) - log.Trace().Msgf("[CAN] %s(%s) %v <<%v>>", gvr, verbs, resp, err) + log.Trace().Msgf("[CAN] %s(%q/%q) <%v>", gvr, ns, name, verbs) + if resp != nil { + log.Trace().Msgf(" Spec: %#v", resp.Spec) + log.Trace().Msgf(" Auth: %t [%q]", resp.Status.Allowed, resp.Status.Reason) + } + log.Trace().Msgf(" <<%v>>", err) if err != nil { log.Warn().Err(err).Msgf(" Dial Failed!") a.cache.Add(key, false, cacheExpiry) From 00213115bee9144cbab452ab152c911e431624e6 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Tue, 5 Mar 2024 23:34:06 -0700 Subject: [PATCH 123/169] K9s release v0.32.3 (#2599) * [Bug] Fix #2584 (with feelings) * Release v0.32.3 --- Makefile | 2 +- change_logs/release_v0.32.3.md | 42 ++++++++++++++++++++++++++++++++++ internal/view/pod.go | 1 + snap/snapcraft.yaml | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 change_logs/release_v0.32.3.md diff --git a/Makefile b/Makefile index 9a0824facf..59c702504b 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.32.2 +VERSION ?= v0.32.3 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.3.md b/change_logs/release_v0.32.3.md new file mode 100644 index 0000000000..c4d85c6b4d --- /dev/null +++ b/change_logs/release_v0.32.3.md @@ -0,0 +1,42 @@ + + +# Release v0.32.3 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +Look like v0.32.2 drop release bins are toast. So m'o aftermath ;( + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2584](https://github.com/derailed/k9s/issues/2584) Transfer of file doesn't detect corruption (with feelings!) + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/internal/view/pod.go b/internal/view/pod.go index fe223b33fe..6808c9a9f0 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -329,6 +329,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey { if args.CO != "" { opts = append(opts, "-c="+args.CO) } + opts = append(opts, fmt.Sprintf("--retries=%d", args.Retries)) cliOpts := shellOpts{ background: true, diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index de4bd294a8..92c1929cd6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core20 -version: 'v0.32.2' +version: 'v0.32.3' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 0403a9310db13baf4d131cd2f27202cb87ebba4a Mon Sep 17 00:00:00 2001 From: Alexej Disterhoft Date: Wed, 6 Mar 2024 20:47:29 +0100 Subject: [PATCH 124/169] fix: pass set retries to transfer command (#2596) The newly introduced retries field in the transfer dialog was not passed to the transfer command. This commit fixes that. Follow-up of #2584 --- internal/ui/dialog/transfer.go | 4 ++++ internal/view/pod.go | 1 + 2 files changed, 5 insertions(+) diff --git a/internal/ui/dialog/transfer.go b/internal/ui/dialog/transfer.go index 41c60e0e41..3141a8943c 100644 --- a/internal/ui/dialog/transfer.go +++ b/internal/ui/dialog/transfer.go @@ -85,6 +85,10 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts) retries := strconv.Itoa(opts.Retries) f.AddInputField("Retries:", retries, 30, nil, func(v string) { retries = v + + if retriesInt, err := strconv.Atoi(retries); err == nil { + args.Retries = retriesInt + } }) f.AddButton("OK", func() { diff --git a/internal/view/pod.go b/internal/view/pod.go index 6808c9a9f0..a1971682fc 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -326,6 +326,7 @@ func (p *Pod) transferCmd(evt *tcell.EventKey) *tcell.EventKey { opts = append(opts, strings.TrimSpace(args.From)) opts = append(opts, strings.TrimSpace(args.To)) opts = append(opts, fmt.Sprintf("--no-preserve=%t", args.NoPreserve)) + opts = append(opts, fmt.Sprintf("--retries=%d", args.Retries)) if args.CO != "" { opts = append(opts, "-c="+args.CO) } From 67ccf685385dc9cd5d52026f788caf25f4153ee8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Mar 2024 08:57:02 -0600 Subject: [PATCH 125/169] Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#2614) Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71f8bf8c71..b75925e143 100644 --- a/go.mod +++ b/go.mod @@ -308,7 +308,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 8c15057ee3..ffb61ebe53 100644 --- a/go.sum +++ b/go.sum @@ -1784,8 +1784,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 081311d910fc52502ac8efcf7c01b3232666f78b Mon Sep 17 00:00:00 2001 From: Chris Gutekanst Date: Sun, 17 Mar 2024 07:58:23 -0700 Subject: [PATCH 126/169] Update brew install steps (#2611) Update brew install steps to match website install steps. https://k9scli.io/topics/install/ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efb02d3155..abbb632c33 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Binaries for Linux, Windows and Mac are available as tarballs in the [release pa * Via [Homebrew](https://brew.sh/) for macOS or Linux ```shell - brew install k9s + brew install derailed/k9s/k9s ``` * Via [MacPorts](https://www.macports.org) From c1180730b9e89c1fb09e3f083c9161fc8f2395ca Mon Sep 17 00:00:00 2001 From: Joshua Sizer Date: Sun, 17 Mar 2024 11:07:01 -0400 Subject: [PATCH 127/169] fix: match node label kubernetes.io/role as suffix (#2606) --- internal/render/node.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/render/node.go b/internal/render/node.go index 4a43c43e11..b207f61a2b 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -22,7 +22,7 @@ import ( const ( labelNodeRolePrefix = "node-role.kubernetes.io/" - nodeLabelRole = "kubernetes.io/role" + labelNodeRoleSuffix = "kubernetes.io/role" ) // Node renders a K8s Node to screen. @@ -181,7 +181,7 @@ func nodeRoles(node *v1.Node, res []string) { res[index] = role index++ } - case k == nodeLabelRole && v != "": + case strings.HasSuffix(k, labelNodeRoleSuffix) && v != "": res[index] = v index++ } From d93409828fe6f8c759f619fe4c0e6615b986bb4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:48:44 -0600 Subject: [PATCH 128/169] Bump actions/checkout from 4.1.1 to 4.1.2 (#2632) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7c9cdad467..5d115b472b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@4.1.1 + uses: actions/checkout@v4.1.2 - name: Install Go uses: actions/setup-go@v5.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ed30d22ff..e278bcd8ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Install Go uses: actions/setup-go@v5.0.0 From 036d95ed457b068c04e61dd1b2027e4dc2cd222b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:48:59 -0600 Subject: [PATCH 129/169] Bump k8s.io/kubectl from 0.29.2 to 0.29.3 (#2630) Bumps [k8s.io/kubectl](https://github.com/kubernetes/kubectl) from 0.29.2 to 0.29.3. - [Commits](https://github.com/kubernetes/kubectl/compare/v0.29.2...v0.29.3) --- updated-dependencies: - dependency-name: k8s.io/kubectl dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index b75925e143..4ae02be169 100644 --- a/go.mod +++ b/go.mod @@ -31,14 +31,14 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.2 - k8s.io/api v0.29.2 + k8s.io/api v0.29.3 k8s.io/apiextensions-apiserver v0.29.2 - k8s.io/apimachinery v0.29.2 - k8s.io/cli-runtime v0.29.2 - k8s.io/client-go v0.29.2 + k8s.io/apimachinery v0.29.3 + k8s.io/cli-runtime v0.29.3 + k8s.io/client-go v0.29.3 k8s.io/klog/v2 v2.120.1 - k8s.io/kubectl v0.29.2 - k8s.io/metrics v0.29.2 + k8s.io/kubectl v0.29.3 + k8s.io/metrics v0.29.3 sigs.k8s.io/yaml v1.4.0 ) @@ -139,7 +139,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -314,7 +314,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.5 // indirect k8s.io/apiserver v0.29.2 // indirect - k8s.io/component-base v0.29.2 // indirect + k8s.io/component-base v0.29.3 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect modernc.org/libc v1.29.0 // indirect diff --git a/go.sum b/go.sum index ffb61ebe53..a23fd06bc9 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +567,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -1829,28 +1829,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= -k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= +k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= +k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= -k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= -k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= -k8s.io/cli-runtime v0.29.2 h1:smfsOcT4QujeghsNjECKN3lwyX9AwcFU0nvJ7sFN3ro= -k8s.io/cli-runtime v0.29.2/go.mod h1:KLisYYfoqeNfO+MkTWvpqIyb1wpJmmFJhioA0xd4MW8= -k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= -k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= -k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= -k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= +k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= +k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= +k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= +k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= +k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= +k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.2 h1:uaDYaBhumvkwz0S2XHt36fK0v5IdNgL7HyUniwb2IUo= -k8s.io/kubectl v0.29.2/go.mod h1:BhizuYBGcKaHWyq+G7txGw2fXg576QbPrrnQdQDZgqI= -k8s.io/metrics v0.29.2 h1:oLSTHEr40V7c7C8wDRRhiAefjGRHROK5zeV8NT0tpzc= -k8s.io/metrics v0.29.2/go.mod h1:cWzACDpKElWhm0CElwfK+7I39wDNbmDDCX7hywjvgR4= +k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= +k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= +k8s.io/metrics v0.29.3 h1:nN+eavbMQ7Kuif2tIdTr2/F2ec2E/SIAWSruTZ+Ye6U= +k8s.io/metrics v0.29.3/go.mod h1:kb3tGGC4ZcIDIuvXyUE291RwJ5WmDu0tB4wAVZM6h2I= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= From 521e9d7b9317fbece7d291d695aa0709a4bae2f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 18:06:47 -0600 Subject: [PATCH 130/169] Bump helm.sh/helm/v3 from 3.14.2 to 3.14.3 (#2628) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.14.2 to 3.14.3. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.14.2...v3.14.3) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 4ae02be169..b8c7672f28 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.14.2 + helm.sh/helm/v3 v3.14.3 k8s.io/api v0.29.3 k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.3 @@ -89,7 +89,7 @@ require ( github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups v1.1.0 // indirect - github.com/containerd/containerd v1.7.11 // indirect + github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -211,6 +211,7 @@ require ( github.com/moby/sys/mountinfo v0.6.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -225,8 +226,7 @@ require ( github.com/onsi/gomega v1.29.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect - github.com/opencontainers/runc v1.1.12 // indirect - github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/openvex/go-vex v0.2.5 // indirect github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect diff --git a/go.sum b/go.sum index a23fd06bc9..9813866105 100644 --- a/go.sum +++ b/go.sum @@ -203,8 +203,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -349,8 +349,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= @@ -892,6 +892,8 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -930,10 +932,8 @@ 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/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= -github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w= -github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= @@ -1820,8 +1820,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= -helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= +helm.sh/helm/v3 v3.14.3 h1:HmvRJlwyyt9HjgmAuxHbHv3PhMz9ir/XNWHyXfmnOP4= +helm.sh/helm/v3 v3.14.3/go.mod h1:v6myVbyseSBJTzhmeE39UcPLNv6cQK6qss3dvgAySaE= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c1a943a6397f90b86dbccc03856d6bd4fa37f6e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 18:07:04 -0600 Subject: [PATCH 131/169] Bump k8s.io/apiextensions-apiserver from 0.29.2 to 0.29.3 (#2627) Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.29.2 to 0.29.3. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.29.2...v0.29.3) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b8c7672f28..e50ac1e6ed 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.3 k8s.io/api v0.29.3 - k8s.io/apiextensions-apiserver v0.29.2 + k8s.io/apiextensions-apiserver v0.29.3 k8s.io/apimachinery v0.29.3 k8s.io/cli-runtime v0.29.3 k8s.io/client-go v0.29.3 @@ -313,7 +313,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.29.2 // indirect + k8s.io/apiserver v0.29.3 // indirect k8s.io/component-base v0.29.3 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/go.sum b/go.sum index 9813866105..f3a5ef6d88 100644 --- a/go.sum +++ b/go.sum @@ -1831,12 +1831,12 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= -k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= -k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= +k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= +k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= -k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= -k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= +k8s.io/apiserver v0.29.3 h1:xR7ELlJ/BZSr2n4CnD3lfA4gzFivh0wwfNfz9L0WZcE= +k8s.io/apiserver v0.29.3/go.mod h1:hrvXlwfRulbMbBgmWRQlFru2b/JySDpmzvQwwk4GUOs= k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= From c31a48fbee0deb4540de53786b20d0fa288ead15 Mon Sep 17 00:00:00 2001 From: Sergey Mikhaltsov Date: Tue, 19 Mar 2024 18:46:18 +0300 Subject: [PATCH 132/169] fix build snap (#2621) Co-authored-by: Sergei Mikhaltsov --- snap/snapcraft.yaml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 92c1929cd6..6fe232a669 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: k9s -base: core20 +base: core22 version: 'v0.32.3' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | @@ -14,33 +14,21 @@ architectures: - armhf - i386 -plugs: - kube-config: - interface: personal-files - read: - - $HOME/.kube/config - apps: k9s: command: bin/k9s - plugs: - - network - - network-bind - - home - - kube-config parts: build: - plugins: go - source: https://github.com/derailed/k9s.git + plugin: go + source: https://github.com/derailed/k9s + source-type: git source-tag: $SNAPCRAFT_PROJECT_VERSION override-build: | make test make build install $SNAPCRAFT_PART_BUILD/execs/k9s -D $SNAPCRAFT_PART_INSTALL/bin/k9s - mkdir -p $SNAPCRAFT_PART_INSTALL/bin - if [ ! -e $SNAPCRAFT_PART_INSTALL/bin/k9s ]; then - ln -s $SNAPCRAFT_PART_INSTALL/bin/k9s $SNAPCRAFT_PART_INSTALL/bin/k9s - fi build-packages: - build-essential + build-snaps: + - go From d3027c8f2916b23606f647f47b434b08fc34bdf8 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Wed, 20 Mar 2024 13:00:34 -0600 Subject: [PATCH 133/169] K9s/release v0.32.4 (#2637) * [Bug] fix #2605 * [Bug] fix #2604 * [Bug] fix #2592 * [Bug] fix #2608 * [Bug] Fix #2612 * Rel v0.32.4 --- Makefile | 2 +- change_logs/release_v0.32.4.md | 65 +++++++++++++++++++++++ internal/client/client.go | 6 +-- internal/config/data/context.go | 4 ++ internal/config/data/ns.go | 1 + internal/config/json/schemas/plugins.json | 1 + internal/config/plugin.go | 1 + internal/dao/pod.go | 18 +++---- internal/render/sts.go | 10 ++-- internal/view/actions.go | 21 +++++--- internal/view/app.go | 21 ++------ internal/view/details.go | 1 + internal/view/dp.go | 41 ++------------ internal/view/ds.go | 38 ++++++++++--- internal/view/job.go | 29 +++++++++- internal/view/logs_extender.go | 26 +++++++++ internal/view/pod.go | 25 ++------- internal/view/sts.go | 33 +----------- plugins/debug-container.yaml | 1 + plugins/helm-purge.yaml | 1 + plugins/job-suspend.yaml | 1 + plugins/k3d-root-shell.yaml | 1 + plugins/liveMigration.yaml | 3 +- plugins/remove-finalizers.yaml | 3 +- plugins/rm-ns.yaml | 1 + snap/snapcraft.yaml | 2 +- 26 files changed, 214 insertions(+), 142 deletions(-) create mode 100644 change_logs/release_v0.32.4.md diff --git a/Makefile b/Makefile index 59c702504b..a1b57045fc 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.32.3 +VERSION ?= v0.32.4 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.4.md b/change_logs/release_v0.32.4.md new file mode 100644 index 0000000000..15d012321d --- /dev/null +++ b/change_logs/release_v0.32.4.md @@ -0,0 +1,65 @@ + + +# Release v0.32.4 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +--- + +## ♫ Sounds Behind The Release ♭ + +Thinking of all you at KubeCon Paris!! +May I suggest a nice glass of `cold Merlote` or other fine grape juices from my country? + +* [Le Gorille - George Brassens](https://www.youtube.com/watch?v=KVfwvk_yVyA) +* [Les Funerailles D'antan (Love this guy!) - George Brassens](https://www.youtube.com/watch?v=bwb5k4k2EMc) +* [Poinconneur Des Lilas - Serge Gainsbourg](https://www.youtube.com/watch?v=eWkWCFzkOvU) +* [Mon Legionaire (Yup! same guy??) - Serge Gainsbourg](https://www.youtube.com/watch?v=gl8gopryqWI) +* [Les Cornichons - Nino Ferrer](https://www.youtube.com/watch?v=N7JSW4NhM8I) +* [Paris s'eveille - Jacques Dutronc](https://www.youtube.com/watch?v=3WcCg6rm3uM) + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2608](https://github.com/derailed/k9s/issues/2608) Make the sanitize feature easier to use +* [#2605](https://github.com/derailed/k9s/issues/2605) Built-in shortcuts being overridden by plugins result in excessive logging +* [#2604](https://github.com/derailed/k9s/issues/2604) Ability to mark a plugin as Dangerous/destructive +* [#2592](https://github.com/derailed/k9s/issues/2592) "list access denied" when switching contexts within k9s since 0.32.0 + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2621](https://github.com/derailed/k9s/pull/2621) Fix snap build + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/internal/client/client.go b/internal/client/client.go index 9a39f583d7..e43a561aa6 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -217,10 +217,10 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { return info, nil } -func (a *APIClient) IsValidNamespace(n string) bool { - ok, err := a.isValidNamespace(n) +func (a *APIClient) IsValidNamespace(ns string) bool { + ok, err := a.isValidNamespace(ns) if err != nil { - log.Warn().Err(err).Msgf("namespace validation failed for: %q", n) + log.Warn().Err(err).Msgf("namespace validation failed for: %q", ns) } return ok diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 48fbe7c3b0..e4fa1c9506 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -4,6 +4,7 @@ package data import ( + "os" "sync" "github.com/derailed/k9s/internal/client" @@ -70,6 +71,9 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { c.mx.Lock() defer c.mx.Unlock() + if a := os.Getenv(envPFAddress); a != "" { + c.PortForwardAddress = a + } if c.PortForwardAddress == "" { c.PortForwardAddress = defaultPFAddress() } diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 57f9d3e678..819430356b 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -32,6 +32,7 @@ func NewActiveNamespace(n string) *Namespace { if n == client.BlankNamespace { n = client.DefaultNamespace } + return &Namespace{ Active: n, Favorites: []string{client.DefaultNamespace}, diff --git a/internal/config/json/schemas/plugins.json b/internal/config/json/schemas/plugins.json index 5c41eb4883..8ef55509b5 100644 --- a/internal/config/json/schemas/plugins.json +++ b/internal/config/json/schemas/plugins.json @@ -14,6 +14,7 @@ "override": { "type": "boolean" }, "description": { "type": "string" }, "confirm": { "type": "boolean" }, + "dangerous": { "type": "boolean" }, "scopes": { "type": "array", "items": { "type": "string" } diff --git a/internal/config/plugin.go b/internal/config/plugin.go index e57aa53a85..9d45fc14eb 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -36,6 +36,7 @@ type Plugin struct { Command string `yaml:"command"` Confirm bool `yaml:"confirm"` Background bool `yaml:"background"` + Dangerous bool `yaml:"dangerous"` } func (p Plugin) String() string { diff --git a/internal/dao/pod.go b/internal/dao/pod.go index d65854498b..c836137dd6 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -207,19 +207,19 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) return append(outs, tailLogs(ctx, p, opts)), nil } for _, co := range po.Spec.InitContainers { - o := opts.Clone() - o.Container = co.Name - outs = append(outs, tailLogs(ctx, p, o)) + cfg := opts.Clone() + cfg.Container = co.Name + outs = append(outs, tailLogs(ctx, p, cfg)) } for _, co := range po.Spec.Containers { - o := opts.Clone() - o.Container = co.Name - outs = append(outs, tailLogs(ctx, p, o)) + cfg := opts.Clone() + cfg.Container = co.Name + outs = append(outs, tailLogs(ctx, p, cfg)) } for _, co := range po.Spec.EphemeralContainers { - o := opts.Clone() - o.Container = co.Name - outs = append(outs, tailLogs(ctx, p, o)) + cfg := opts.Clone() + cfg.Container = co.Name + outs = append(outs, tailLogs(ctx, p, cfg)) } return outs, nil diff --git a/internal/render/sts.go b/internal/render/sts.go index e35560ce04..c83ffa7ba6 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -59,16 +59,20 @@ func (s StatefulSet) Render(o interface{}, ns string, r *model1.Row) error { podContainerNames(sts.Spec.Template.Spec, true), podImageNames(sts.Spec.Template.Spec, true), mapToStr(sts.Labels), - AsStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)), + AsStatus(s.diagnose(sts.Spec.Replicas, sts.Status.Replicas, sts.Status.ReadyReplicas)), ToAge(sts.GetCreationTimestamp()), } return nil } -func (StatefulSet) diagnose(d, r int32) error { +func (StatefulSet) diagnose(w *int32, d, r int32) error { if d != r { - return fmt.Errorf("desiring %d replicas got %d available", d, r) + return fmt.Errorf("desired %d replicas got %d available", d, r) } + if w != nil && *w != r { + return fmt.Errorf("want %d replicas got %d available", *w, r) + } + return nil } diff --git a/internal/view/actions.go b/internal/view/actions.go index 7d76619889..234aa86961 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -80,7 +80,7 @@ func hotKeyActions(r Runner, aa *ui.KeyActions) error { errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k)) continue } - log.Info().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k) + log.Debug().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k) } command, err := r.EnvFn()().Substitute(hk.Command) @@ -110,7 +110,6 @@ func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler { } func pluginActions(r Runner, aa *ui.KeyActions) error { - pp := config.NewPlugins() aa.Range(func(k tcell.Key, a ui.KeyAction) { if a.Opts.Plugin { aa.Delete(k) @@ -121,12 +120,16 @@ func pluginActions(r Runner, aa *ui.KeyActions) error { if err != nil { return err } + pp := config.NewPlugins() if err := pp.Load(path); err != nil { return err } - var errs error - aliases := r.Aliases() + var ( + errs error + aliases = r.Aliases() + ro = r.App().Config.K9s.IsReadOnly() + ) for k, plugin := range pp.Plugins { if !inScope(plugin.Scopes, aliases) { continue @@ -141,15 +144,19 @@ func pluginActions(r Runner, aa *ui.KeyActions) error { errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k)) continue } - log.Info().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) + log.Debug().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) } + if plugin.Dangerous && ro { + continue + } aa.Add(key, ui.NewKeyActionWithOpts( plugin.Description, pluginAction(r, plugin), ui.ActionOpts{ - Visible: true, - Plugin: true, + Visible: true, + Plugin: true, + Dangerous: plugin.Dangerous, }, )) } diff --git a/internal/view/app.go b/internal/view/app.go index fef223111a..055c95c8a6 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -111,10 +111,6 @@ func (a *App) Init(version string, rate int) error { ns := a.Config.ActiveNamespace() a.factory = watch.NewFactory(a.Conn()) - ok, err := a.isValidNS(ns) - if !ok && err == nil { - return fmt.Errorf("app-init - invalid namespace: %q", ns) - } a.initFactory(ns) a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s) @@ -438,18 +434,6 @@ func (a *App) switchNS(ns string) error { return a.factory.SetActiveNS(ns) } -func (a *App) isValidNS(ns string) (bool, error) { - if ns == client.BlankNamespace || ns == client.NamespaceAll { - return true, nil - } - - if !a.Conn().IsValidNamespace(ns) { - return false, fmt.Errorf("invalid namespace: %q", ns) - } - - return true, nil -} - func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { name, ok := ci.HasContext() if !ok || a.Config.ActiveContextName() == name { @@ -477,12 +461,13 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { } ns := a.Config.ActiveNamespace() if !a.Conn().IsValidNamespace(ns) { - a.Flash().Errf("Unable to validate namespace %q. Using %q namespace", ns, client.DefaultNamespace) - ns = client.DefaultNamespace + log.Warn().Msgf("Unable to validate namespace: %q. Using %q as active namespace", ns, ns) if err := a.Config.SetActiveNamespace(ns); err != nil { return err } } + a.Flash().Errf("Using %q namespace", ns) + if err := a.Config.Save(true); err != nil { log.Error().Err(err).Msg("config save failed!") } else { diff --git a/internal/view/details.go b/internal/view/details.go index c07c6171b3..235b5d2bcb 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -168,6 +168,7 @@ func (d *Details) StylesChanged(s *config.Styles) { // Update updates the view content. func (d *Details) Update(buff string) *Details { d.model.SetText(buff) + return d } diff --git a/internal/view/dp.go b/internal/view/dp.go index f9cd93d64d..6db577dd07 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -53,49 +53,16 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) { if path == "" { return nil, errors.New("you must provide a selection") } - - sts, err := d.dp(path) + dp, err := d.getInstance(path) if err != nil { return nil, err } - cc := sts.Spec.Template.Spec.Containers - var ( - co, dco string - allCos bool - ) - if c, ok := dao.GetDefaultContainer(sts.Spec.Template.ObjectMeta, sts.Spec.Template.Spec); ok { - co, dco = c, c - } else if len(cc) == 1 { - co = cc[0].Name - } else { - dco, allCos = cc[0].Name, true - } - - cfg := d.App().Config.K9s.Logger - opts := dao.LogOptions{ - Path: path, - Container: co, - Lines: int64(cfg.TailCount), - SinceSeconds: cfg.SinceSeconds, - SingleContainer: len(cc) == 1, - AllContainers: allCos, - ShowTimestamp: cfg.ShowTime, - Previous: prev, - } - if co == "" { - opts.AllContainers = true - } - opts.DefaultContainer = dco - - return &opts, nil + return podLogOptions(d.App(), path, prev, dp.ObjectMeta, dp.Spec.Template.Spec), nil } func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string) { - var ddp dao.Deployment - ddp.Init(d.App().factory, d.GVR()) - - dp, err := ddp.GetInstance(fqn) + dp, err := d.getInstance(fqn) if err != nil { app.Flash().Err(err) return @@ -104,7 +71,7 @@ func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string showPodsFromSelector(app, fqn, dp.Spec.Selector) } -func (d *Deploy) dp(fqn string) (*appsv1.Deployment, error) { +func (d *Deploy) getInstance(fqn string) (*appsv1.Deployment, error) { var dp dao.Deployment dp.Init(d.App().factory, d.GVR()) diff --git a/internal/view/ds.go b/internal/view/ds.go index a9e24abd73..6e89f4215b 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -4,9 +4,12 @@ package view import ( + "errors" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" + appsv1 "k8s.io/api/apps/v1" ) // DaemonSet represents a daemon set custom viewer. @@ -16,17 +19,16 @@ type DaemonSet struct { // NewDaemonSet returns a new viewer. func NewDaemonSet(gvr client.GVR) ResourceViewer { - d := DaemonSet{ - ResourceViewer: NewPortForwardExtender( - NewVulnerabilityExtender( - NewRestartExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), nil), - ), + var d DaemonSet + d.ResourceViewer = NewPortForwardExtender( + NewVulnerabilityExtender( + NewRestartExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), d.logOptions), ), ), ), - } + ) d.AddBindKeysFn(d.bindKeys) d.GetTable().SetEnterFn(d.showPods) @@ -55,3 +57,23 @@ func (d *DaemonSet) showPods(app *App, model ui.Tabular, _ client.GVR, path stri showPodsFromSelector(app, path, ds.Spec.Selector) } + +func (d *DaemonSet) logOptions(prev bool) (*dao.LogOptions, error) { + path := d.GetTable().GetSelectedItem() + if path == "" { + return nil, errors.New("you must provide a selection") + } + ds, err := d.getInstance(path) + if err != nil { + return nil, err + } + + return podLogOptions(d.App(), path, prev, ds.ObjectMeta, ds.Spec.Template.Spec), nil +} + +func (d *DaemonSet) getInstance(fqn string) (*appsv1.DaemonSet, error) { + var ds dao.DaemonSet + ds.Init(d.App().factory, client.NewGVR("apps/v1/daemonsets")) + + return ds.GetInstance(fqn) +} diff --git a/internal/view/job.go b/internal/view/job.go index 09ff8d6854..e414e3e5c9 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -4,7 +4,10 @@ package view import ( + "errors" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -19,7 +22,11 @@ type Job struct { // NewJob returns a new viewer. func NewJob(gvr client.GVR) ResourceViewer { - j := Job{ResourceViewer: NewVulnerabilityExtender(NewLogsExtender(NewBrowser(gvr), nil))} + var j Job + + j.ResourceViewer = NewVulnerabilityExtender( + NewLogsExtender(NewBrowser(gvr), j.logOptions), + ) j.GetTable().SetEnterFn(j.showPods) j.GetTable().SetSortCol("AGE", true) @@ -42,3 +49,23 @@ func (*Job) showPods(app *App, model ui.Tabular, gvr client.GVR, path string) { showPodsFromSelector(app, path, job.Spec.Selector) } + +func (j *Job) logOptions(prev bool) (*dao.LogOptions, error) { + path := j.GetTable().GetSelectedItem() + if path == "" { + return nil, errors.New("you must provide a selection") + } + job, err := j.getInstance(path) + if err != nil { + return nil, err + } + + return podLogOptions(j.App(), path, prev, job.ObjectMeta, job.Spec.Template.Spec), nil +} + +func (j *Job) getInstance(fqn string) (*batchv1.Job, error) { + var job dao.Job + job.Init(j.App().factory, client.NewGVR("batch/v1/jobs")) + + return job.GetInstance(fqn) +} diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 95e452117d..d2a8ad4f39 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -8,6 +8,8 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tcell/v2" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // LogsExtender adds log actions to a given viewer. @@ -91,3 +93,27 @@ func (l *LogsExtender) buildLogOpts(path, co string, prevLogs bool) *dao.LogOpti return &opts } + +func podLogOptions(app *App, fqn string, prev bool, m metav1.ObjectMeta, spec v1.PodSpec) *dao.LogOptions { + var ( + cc = fetchContainers(m, spec, true) + cfg = app.Config.K9s.Logger + opts = dao.LogOptions{ + Path: fqn, + Lines: int64(cfg.TailCount), + SinceSeconds: cfg.SinceSeconds, + SingleContainer: len(cc) == 1, + ShowTimestamp: cfg.ShowTime, + Previous: prev, + } + ) + if c, ok := dao.GetDefaultContainer(m, spec); ok { + opts.Container, opts.DefaultContainer = c, c + } else if len(cc) == 1 { + opts.Container = cc[0] + } else { + opts.AllContainers = true + } + + return &opts +} diff --git a/internal/view/pod.go b/internal/view/pod.go index a1971682fc..418029e542 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -38,6 +38,7 @@ const ( trDownload = "Download" pfIndicator = "[orange::b]Ⓕ" defaultTxRetries = 999 + magicPrompt = "Yes Please!" ) // Pod represents a pod viewer. @@ -146,24 +147,7 @@ func (p *Pod) logOptions(prev bool) (*dao.LogOptions, error) { return nil, err } - cc, cfg := fetchContainers(pod.ObjectMeta, pod.Spec, true), p.App().Config.K9s.Logger - opts := dao.LogOptions{ - Path: path, - Lines: int64(cfg.TailCount), - SinceSeconds: cfg.SinceSeconds, - SingleContainer: len(cc) == 1, - ShowTimestamp: cfg.ShowTime, - Previous: prev, - } - if c, ok := dao.GetDefaultContainer(pod.ObjectMeta, pod.Spec); ok { - opts.Container, opts.DefaultContainer = c, c - } else if len(cc) == 1 { - opts.Container = cc[0] - } else { - opts.AllContainers = true - } - - return &opts, nil + return podLogOptions(p.App(), path, prev, pod.ObjectMeta, pod.Spec), nil } func (p *Pod) showContainers(app *App, _ ui.Tabular, _ client.GVR, _ string) { @@ -287,9 +271,8 @@ func (p *Pod) sanitizeCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - ack := "sanitize me pods!" - msg := fmt.Sprintf("Sanitize deletes all pods in completed/error state\nPlease enter [orange::b]%s[-::-] to proceed.", ack) - dialog.ShowConfirmAck(p.App().App, p.App().Content.Pages, ack, true, "Sanitize", msg, func() { + msg := fmt.Sprintf("Sanitize deletes all pods in completed/error state\nPlease enter [orange::b]%s[-::-] to proceed.", magicPrompt) + dialog.ShowConfirmAck(p.App().App, p.App().Content.Pages, magicPrompt, true, "Sanitize", msg, func() { ctx, cancel := context.WithTimeout(context.Background(), 5*p.App().Conn().Config().CallTimeout()) defer cancel() total, err := s.Sanitize(ctx, p.GetTable().GetModel().GetNamespace()) diff --git a/internal/view/sts.go b/internal/view/sts.go index 816dabb984..18636d9a2e 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -42,42 +42,12 @@ func (s *StatefulSet) logOptions(prev bool) (*dao.LogOptions, error) { if path == "" { return nil, errors.New("you must provide a selection") } - sts, err := s.getInstance(path) if err != nil { return nil, err } - cc := sts.Spec.Template.Spec.Containers - var ( - co, dco string - allCos bool - ) - if c, ok := dao.GetDefaultContainer(sts.Spec.Template.ObjectMeta, sts.Spec.Template.Spec); ok { - co, dco = c, c - } else if len(cc) == 1 { - co = cc[0].Name - } else { - dco, allCos = cc[0].Name, true - } - - cfg := s.App().Config.K9s.Logger - opts := dao.LogOptions{ - Path: path, - Container: co, - Lines: int64(cfg.TailCount), - SingleContainer: len(cc) == 1, - SinceSeconds: cfg.SinceSeconds, - AllContainers: allCos, - ShowTimestamp: cfg.ShowTime, - Previous: prev, - } - if co == "" { - opts.AllContainers = true - } - opts.DefaultContainer = dco - - return &opts, nil + return podLogOptions(s.App(), path, prev, sts.ObjectMeta, sts.Spec.Template.Spec), nil } func (s *StatefulSet) bindKeys(aa *ui.KeyActions) { @@ -96,5 +66,6 @@ func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string func (s *StatefulSet) getInstance(path string) (*appsv1.StatefulSet, error) { var sts dao.StatefulSet + return sts.GetInstance(s.App().factory, path) } diff --git a/plugins/debug-container.yaml b/plugins/debug-container.yaml index f8561ca29b..aefba8801c 100644 --- a/plugins/debug-container.yaml +++ b/plugins/debug-container.yaml @@ -4,6 +4,7 @@ plugins: debug: shortCut: Shift-D description: Add debug container + dangerous: true scopes: - containers command: bash diff --git a/plugins/helm-purge.yaml b/plugins/helm-purge.yaml index 300053a7fa..c84a8eaa30 100644 --- a/plugins/helm-purge.yaml +++ b/plugins/helm-purge.yaml @@ -4,6 +4,7 @@ plugins: helm-purge: shortCut: Ctrl-P description: Helm Purge + dangerous: true scopes: - po command: kubectl diff --git a/plugins/job-suspend.yaml b/plugins/job-suspend.yaml index abee83bc52..799f884efd 100644 --- a/plugins/job-suspend.yaml +++ b/plugins/job-suspend.yaml @@ -3,6 +3,7 @@ plugins: toggleCronjob: shortCut: Ctrl-S confirm: true + dangerous: true scopes: - cj description: Toggle to suspend or resume a running cronjob diff --git a/plugins/k3d-root-shell.yaml b/plugins/k3d-root-shell.yaml index 79707b4b5a..d44304284c 100644 --- a/plugins/k3d-root-shell.yaml +++ b/plugins/k3d-root-shell.yaml @@ -3,6 +3,7 @@ plugins: k3d-root-shell: shortCut: Shift-S confirm: false + dangerous: true description: "Root Shell" scopes: - containers diff --git a/plugins/liveMigration.yaml b/plugins/liveMigration.yaml index 2ddd08cfc7..a0fbbe72b4 100644 --- a/plugins/liveMigration.yaml +++ b/plugins/liveMigration.yaml @@ -8,7 +8,7 @@ plugins: # Require `virtctl` cli in your PATH, # can be downloaded from Openshift `Command Line Tools` page # or from kubevirt site https://kubevirt.io/user-guide/operations/virtctl_client_tool/ - # + # # liveMigration: # Can be triggered from the VMI (VirtualMachineInstance) view, with shortcut `m` @@ -17,6 +17,7 @@ plugins: description: Live Migrate moves VM to another compute node # Enable confirmation dialog confirm: true + dangerous: true # Collections of views that support this shortcut. (You can use `all`) scopes: - virtualmachineinstance diff --git a/plugins/remove-finalizers.yaml b/plugins/remove-finalizers.yaml index 4abb7d46ac..b7a83d67d5 100644 --- a/plugins/remove-finalizers.yaml +++ b/plugins/remove-finalizers.yaml @@ -11,11 +11,12 @@ plugins: remove_finalizers: shortCut: Ctrl-F confirm: true + dangerous: true scopes: - all description: | Removes all finalizers from selected resource. Be careful when using it, - it may leave dangling resources or delete them + it may leave dangling resources or delete them command: kubectl background: true args: diff --git a/plugins/rm-ns.yaml b/plugins/rm-ns.yaml index 88509dc98c..a73592c040 100644 --- a/plugins/rm-ns.yaml +++ b/plugins/rm-ns.yaml @@ -3,6 +3,7 @@ plugins: rm-ns: shortCut: n confirm: true + dangerous: true description: Remove NS Finalizers scopes: - namespace diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 6fe232a669..233f9c0ac7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.32.3' +version: 'v0.32.4' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 618511537e7e97256f15d795c04869c0f33f0653 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Apr 2024 11:35:12 -0600 Subject: [PATCH 134/169] Bump github.com/cenkalti/backoff/v4 from 4.2.1 to 4.3.0 (#2647) Bumps [github.com/cenkalti/backoff/v4](https://github.com/cenkalti/backoff) from 4.2.1 to 4.3.0. - [Commits](https://github.com/cenkalti/backoff/compare/v4.2.1...v4.3.0) --- updated-dependencies: - dependency-name: github.com/cenkalti/backoff/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e50ac1e6ed..f9302097a3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/anchore/grype v0.74.0 github.com/anchore/syft v0.100.0 github.com/atotto/clipboard v0.1.4 - github.com/cenkalti/backoff/v4 v4.2.1 + github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 github.com/derailed/tcell/v2 v2.3.1-rc.3 github.com/derailed/tview v0.8.3 diff --git a/go.sum b/go.sum index f3a5ef6d88..e275687f08 100644 --- a/go.sum +++ b/go.sum @@ -314,8 +314,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= From 363e1405e7be9492e97f2c7b8933e61e13abefec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:04:01 -0600 Subject: [PATCH 135/169] Bump golang.org/x/net from 0.19.0 to 0.23.0 (#2664) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.19.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index f9302097a3..0aa5244b78 100644 --- a/go.mod +++ b/go.mod @@ -291,14 +291,14 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/goleak v1.2.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect diff --git a/go.sum b/go.sum index e275687f08..111e3db926 100644 --- a/go.sum +++ b/go.sum @@ -1217,8 +1217,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1319,8 +1319,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1459,8 +1459,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1469,8 +1469,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 1a25109d257d7ffbd16b685067723c3349a45e8e Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sun, 21 Apr 2024 01:05:54 +0800 Subject: [PATCH 136/169] checking for nil pointer in merge namespace (#2659) --- internal/config/data/context.go | 5 ++++- internal/config/data/context_int_test.go | 5 +++++ internal/view/app.go | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/config/data/context.go b/internal/config/data/context.go index e4fa1c9506..960a423048 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -53,9 +53,12 @@ func NewContextFromKubeConfig(ks KubeSettings) (*Context, error) { } func (c *Context) merge(old *Context) { - if old == nil { + if old == nil || old.Namespace == nil { return } + if c.Namespace == nil { + c.Namespace = NewNamespace() + } c.Namespace.merge(old.Namespace) } diff --git a/internal/config/data/context_int_test.go b/internal/config/data/context_int_test.go index 1a37ff99a5..b4a78b2421 100644 --- a/internal/config/data/context_int_test.go +++ b/internal/config/data/context_int_test.go @@ -70,6 +70,11 @@ func Test_contextMerge(t *testing.T) { }, }, }, + "no-namespace": { + c1: NewContext(), + c2: &Context{}, + e: NewContext(), + }, } for k, u := range uu { diff --git a/internal/view/app.go b/internal/view/app.go index 055c95c8a6..c0c6c41ef9 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -466,7 +466,7 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { return err } } - a.Flash().Errf("Using %q namespace", ns) + a.Flash().Infof("Using %q namespace", ns) if err := a.Config.Save(true); err != nil { log.Error().Err(err).Msg("config save failed!") From 2e6f16910af88eb9d8e2087d294c6a119be98092 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:06:05 -0600 Subject: [PATCH 137/169] Bump helm.sh/helm/v3 from 3.14.3 to 3.14.4 (#2657) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.14.3 to 3.14.4. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.14.3...v3.14.4) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0aa5244b78..a2d4bcbe97 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.14.3 + helm.sh/helm/v3 v3.14.4 k8s.io/api v0.29.3 k8s.io/apiextensions-apiserver v0.29.3 k8s.io/apimachinery v0.29.3 @@ -102,7 +102,7 @@ require ( github.com/distribution/reference v0.5.0 // indirect github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect diff --git a/go.sum b/go.sum index 111e3db926..b481feebbd 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,8 @@ github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWT github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -1820,8 +1820,8 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.14.3 h1:HmvRJlwyyt9HjgmAuxHbHv3PhMz9ir/XNWHyXfmnOP4= -helm.sh/helm/v3 v3.14.3/go.mod h1:v6myVbyseSBJTzhmeE39UcPLNv6cQK6qss3dvgAySaE= +helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= +helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 5fdaa6cdd35b380a946d5e07a59cf949d44b5be2 Mon Sep 17 00:00:00 2001 From: crazehang <165746307+crazehang@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:52:06 +0800 Subject: [PATCH 138/169] chore: remove the repetitive word (#2650) Signed-off-by: crazehang --- internal/ui/select_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index d1df36c480..b0dfdeaae9 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -59,7 +59,7 @@ func (s *SelectTable) GetSelectedItems() []string { return items } -// GetRowID returns the row id at at given location. +// GetRowID returns the row id at given location. func (s *SelectTable) GetRowID(index int) (string, bool) { cell := s.GetCell(index, 0) if cell == nil { From 21e091b2372a4a75ac124b7b8b1c1ef56c9219d0 Mon Sep 17 00:00:00 2001 From: Ruslan Timofieiev Date: Sat, 20 Apr 2024 21:02:26 +0300 Subject: [PATCH 139/169] Allow overwriting plugin output with command's stdout (#2644) * Allow overwriting plugin output with command's stdout * Update README.md * remove 1 indentation level --- README.md | 1 + internal/config/json/schemas/plugins.json | 1 + internal/config/plugin.go | 21 ++++++++-------- internal/config/plugin_test.go | 30 ++++++++++++----------- internal/view/actions.go | 8 +++++- internal/view/exec.go | 7 +++--- 6 files changed, 40 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index abbb632c33..236f36fa78 100644 --- a/README.md +++ b/README.md @@ -650,6 +650,7 @@ A plugin is defined as follows: * Command represents ad-hoc commands the plugin runs upon activation * Background specifies whether or not the command runs in the background * Args specifies the various arguments that should apply to the command above +* OverwriteOutput options allows plugin developers to provide custom messages on plugin execution K9s does provide additional environment variables for you to customize your plugins arguments. Currently, the available environment variables are as follows: diff --git a/internal/config/json/schemas/plugins.json b/internal/config/json/schemas/plugins.json index 8ef55509b5..c1ba587a6c 100644 --- a/internal/config/json/schemas/plugins.json +++ b/internal/config/json/schemas/plugins.json @@ -21,6 +21,7 @@ }, "command": { "type": "string" }, "background": { "type": "boolean" }, + "overwriteOutput": { "type": "boolean" }, "args": { "type": "array", "items": { "type": ["string", "number"] } diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 9d45fc14eb..c676f87fad 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -27,16 +27,17 @@ type Plugins struct { // Plugin describes a K9s plugin. type Plugin struct { - Scopes []string `yaml:"scopes"` - Args []string `yaml:"args"` - ShortCut string `yaml:"shortCut"` - Override bool `yaml:"override"` - Pipes []string `yaml:"pipes"` - Description string `yaml:"description"` - Command string `yaml:"command"` - Confirm bool `yaml:"confirm"` - Background bool `yaml:"background"` - Dangerous bool `yaml:"dangerous"` + Scopes []string `yaml:"scopes"` + Args []string `yaml:"args"` + ShortCut string `yaml:"shortCut"` + Override bool `yaml:"override"` + Pipes []string `yaml:"pipes"` + Description string `yaml:"description"` + Command string `yaml:"command"` + Confirm bool `yaml:"confirm"` + Background bool `yaml:"background"` + Dangerous bool `yaml:"dangerous"` + OverwriteOutput bool `yaml:"overwriteOutput"` } func (p Plugin) String() string { diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go index bd95fec5ec..1634760eff 100644 --- a/internal/config/plugin_test.go +++ b/internal/config/plugin_test.go @@ -22,23 +22,25 @@ var pluginYmlTestData = Plugin{ } var test1YmlTestData = Plugin{ - Scopes: []string{"po", "dp"}, - Args: []string{"-n", "$NAMESPACE", "-boolean"}, - ShortCut: "shift-s", - Description: "blee", - Command: "duh", - Confirm: true, - Background: false, + Scopes: []string{"po", "dp"}, + Args: []string{"-n", "$NAMESPACE", "-boolean"}, + ShortCut: "shift-s", + Description: "blee", + Command: "duh", + Confirm: true, + Background: false, + OverwriteOutput: true, } var test2YmlTestData = Plugin{ - Scopes: []string{"svc", "ing"}, - Args: []string{"-n", "$NAMESPACE", "-oyaml"}, - ShortCut: "shift-r", - Description: "bla", - Command: "duha", - Confirm: false, - Background: true, + Scopes: []string{"svc", "ing"}, + Args: []string{"-n", "$NAMESPACE", "-oyaml"}, + ShortCut: "shift-r", + Description: "bla", + Command: "duha", + Confirm: false, + Background: true, + OverwriteOutput: false, } func TestPluginLoad(t *testing.T) { diff --git a/internal/view/actions.go b/internal/view/actions.go index 234aa86961..d86d0098ad 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -206,7 +206,13 @@ func pluginAction(r Runner, p config.Plugin) ui.ActionHandler { } go func() { for st := range statusChan { - r.App().Flash().Infof("Plugin command launched successfully: %q", st) + if !p.OverwriteOutput { + r.App().Flash().Infof("Plugin command launched successfully: %q", st) + } else if strings.Contains(st, outputPrefix) { + infoMsg := strings.TrimPrefix(st, outputPrefix) + r.App().Flash().Info(strings.TrimSpace(infoMsg)) + return + } } }() diff --git a/internal/view/exec.go b/internal/view/exec.go index e692e8b5d9..618dc74623 100644 --- a/internal/view/exec.go +++ b/internal/view/exec.go @@ -34,8 +34,9 @@ import ( ) const ( - shellCheck = `command -v bash >/dev/null && exec bash || exec sh` - bannerFmt = "<> Pod: %s | Container: %s \n" + shellCheck = `command -v bash >/dev/null && exec bash || exec sh` + bannerFmt = "<> Pod: %s | Container: %s \n" + outputPrefix = "[output]" ) var editorEnvVars = []string{"KUBE_EDITOR", "K9S_EDITOR", "EDITOR"} @@ -492,7 +493,7 @@ func pipe(_ context.Context, opts shellOpts, statusChan chan<- string, w, e *byt } else { for _, l := range strings.Split(w.String(), "\n") { if l != "" { - statusChan <- fmt.Sprintf("[output] %s", l) + statusChan <- fmt.Sprintf("%s %s", outputPrefix, l) } } statusChan <- fmt.Sprintf("Command completed successfully: %q", render.Truncate(cmd.String(), 20)) From 7a45005bb04a8c072b92b260753324adba514782 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:16:58 -0600 Subject: [PATCH 140/169] Bump github.com/docker/docker (#2638) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 2d950ac0ac79620ff06c794d5b88e817156766a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 07:27:07 -0600 Subject: [PATCH 141/169] Bump golangci/golangci-lint-action from 4.0.0 to 5.1.0 (#2684) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4.0.0 to 5.1.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v4.0.0...v5.1.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5d115b472b..c8824dbc1f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: cache-dependency-path: go.sum - name: Lint - uses: golangci/golangci-lint-action@v4.0.0 + uses: golangci/golangci-lint-action@v5.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check \ No newline at end of file From f10962b24868370fc8841e7c1e7fd2a0c002952a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 07:27:21 -0600 Subject: [PATCH 142/169] Bump actions/checkout from 4.1.2 to 4.1.4 (#2683) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.2...v4.1.4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c8824dbc1f..04369f158c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Install Go uses: actions/setup-go@v5.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e278bcd8ff..e8c2571c20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Install Go uses: actions/setup-go@v5.0.0 From e8fbbc119405872217dd1d5bd06b8c719679521d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 07:27:33 -0600 Subject: [PATCH 143/169] Bump github.com/hashicorp/go-getter from 1.7.3 to 1.7.4 (#2682) Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/hashicorp/go-getter/releases) - [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml) - [Commits](https://github.com/hashicorp/go-getter/compare/v1.7.3...v1.7.4) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-getter dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a2d4bcbe97..cf66b06538 100644 --- a/go.mod +++ b/go.mod @@ -161,7 +161,7 @@ require ( github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.3 // indirect + github.com/hashicorp/go-getter v1.7.4 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect diff --git a/go.sum b/go.sum index b481feebbd..3c299871bb 100644 --- a/go.sum +++ b/go.sum @@ -682,8 +682,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= -github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= +github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= From a0ffa8f39220aaa7074820b2bc0ea13fd81903d2 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Fri, 3 May 2024 21:31:35 +0800 Subject: [PATCH 144/169] save config when closing k9s with ctrl-c (#2666) --- internal/ui/app.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/ui/app.go b/internal/ui/app.go index e0c261ee64..bb9eaa501e 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -151,6 +151,10 @@ func (a *App) bindKeys() { // BailOut exits the application. func (a *App) BailOut() { + if err := a.Config.Save(true); err != nil { + log.Error().Err(err).Msg("config save failed!") + } + a.Stop() os.Exit(0) } From a0c03703b1fad28bfb2ac21c0bde3285011dfe38 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Fri, 3 May 2024 15:32:00 +0200 Subject: [PATCH 145/169] fix: do not hard-code path to kubectl in jq plugin (#2678) --- plugins/kubectl-plugins/kubectl-jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kubectl-plugins/kubectl-jq b/plugins/kubectl-plugins/kubectl-jq index abbd3dda66..44a8cd61fe 100755 --- a/plugins/kubectl-plugins/kubectl-jq +++ b/plugins/kubectl-plugins/kubectl-jq @@ -1,3 +1,3 @@ #!/bin/bash -/usr/local/bin/kubectl logs -f $1 -n $2 --context $3 | jq -rR '. as $raw | try (fromjson | .message) catch ("\u001b[31m" + $raw + "\u001b[0m")' \ No newline at end of file +kubectl logs -f $1 -n $2 --context $3 | jq -rR '. as $raw | try (fromjson | .message) catch ("\u001b[31m" + $raw + "\u001b[0m")' From 3ef5415d6264bd0005f1db4dd66847b1d793c0e6 Mon Sep 17 00:00:00 2001 From: Julien Heroguelle Date: Fri, 3 May 2024 15:32:16 +0200 Subject: [PATCH 146/169] Add kanagawa skin (#2676) --- skins/kanagawa.yaml | 114 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 skins/kanagawa.yaml diff --git a/skins/kanagawa.yaml b/skins/kanagawa.yaml new file mode 100644 index 0000000000..11b4452260 --- /dev/null +++ b/skins/kanagawa.yaml @@ -0,0 +1,114 @@ +# ----------------------------------------------------------------------------- +# Kanagawa Skin +# ----------------------------------------------------------------------------- + +# Styles... +foreground: &foreground "#dcd7ba" +background: &background "#1f1f28" +black: &black "#090618" +blue: &blue "#7e9cd8" +green: &green "#76946a" +grey: &grey "#727169" +orange: &orange "#ffa066" +purple: &purple "#957fb8" +red: &red "#c34043" +yellow: &yellow "#c0a36e" +yellow_bright: &yellow_bright "#e6c384" + +# Skin... +k9s: + body: + fgColor: *foreground + bgColor: *background + logoColor: *green + prompt: + fgColor: *foreground + bgColor: *background + suggestColor: *orange + info: + fgColor: *grey + sectionColor: *green + help: + fgColor: *foreground + bgColor: *background + keyColor: *yellow + numKeyColor: *blue + sectionColor: *purple + dialog: + fgColor: *black + bgColor: *background + buttonFgColor: *foreground + buttonBgColor: *green + buttonFocusFgColor: *black + buttonFocusBgColor: *blue + labelFgColor: *orange + fieldFgColor: *blue + frame: + border: + fgColor: *green + focusColor: *green + menu: + fgColor: *grey + keyColor: *yellow + numKeyColor: *yellow + crumbs: + fgColor: *black + bgColor: *green + activeColor: *yellow + status: + newColor: *blue + modifyColor: *green + addColor: *grey + pendingColor: *orange + errorColor: *red + highlightColor: *yellow + killColor: *purple + completedColor: *grey + title: + fgColor: *blue + bgColor: *background + highlightColor: *purple + counterColor: *foreground + filterColor: *blue + views: + charts: + bgColor: *background + defaultDialColors: + - *green + - *red + defaultChartColors: + - *green + - *red + table: + fgColor: *yellow + bgColor: *background + cursorFgColor: *black + cursorBgColor: *blue + markColor: *yellow_bright + header: + fgColor: *grey + bgColor: *background + sorterColor: *orange + xray: + fgColor: *blue + bgColor: *background + cursorColor: *foreground + graphicColor: *yellow_bright + showIcons: false + yaml: + keyColor: *red + colonColor: *grey + valueColor: *grey + logs: + fgColor: *grey + bgColor: *background + indicator: + fgColor: *blue + bgColor: *background + toggleOnColor: *red + toggleOffColor: *grey + help: + fgColor: *grey + bgColor: *background + indicator: + fgColor: *blue From 2ff050b7f868dcf0e4ca0eb198b211c200568a6c Mon Sep 17 00:00:00 2001 From: Martin Montes Date: Sat, 18 May 2024 16:45:02 +0200 Subject: [PATCH 147/169] Added cert-manager and openssl plugins. (#2699) Signed-off-by: Martin Montes --- plugins/cert-manager.yaml | 36 ++++++++++++++++++++++++++++++++++++ plugins/openssl.yaml | 25 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 plugins/cert-manager.yaml create mode 100644 plugins/openssl.yaml diff --git a/plugins/cert-manager.yaml b/plugins/cert-manager.yaml new file mode 100644 index 0000000000..9c3f1aff41 --- /dev/null +++ b/plugins/cert-manager.yaml @@ -0,0 +1,36 @@ +# Manage cert-manager Certificate resouces via cmctl. +# See: https://github.com/cert-manager/cmctl +plugins: + cert-status: + shortCut: Shift-S + confirm: false + description: Certificate status + scopes: + - certificates + command: bash + background: false + args: + - -c + - "cmctl status certificate --context $CONTEXT -n $NAMESPACE $NAME |& less" + cert-renew: + shortCut: Shift-R + confirm: false + description: Certificate renew + scopes: + - certificates + command: bash + background: false + args: + - -c + - "cmctl renew --context $CONTEXT -n $NAMESPACE $NAME |& less" + secret-inspect: + shortCut: Shift-I + confirm: false + description: Inspect secret + scopes: + - secrets + command: bash + background: false + args: + - -c + - "cmctl inspect secret --context $CONTEXT -n $NAMESPACE $NAME |& less" \ No newline at end of file diff --git a/plugins/openssl.yaml b/plugins/openssl.yaml new file mode 100644 index 0000000000..c21bf31631 --- /dev/null +++ b/plugins/openssl.yaml @@ -0,0 +1,25 @@ +# Inspect certificate chains with openssl. +# See: https://github.com/openssl/openssl. +plugins: + secret-openssl-ca: + shortCut: Ctrl-O + confirm: false + description: Openssl ca.crt + scopes: + - secrets + command: bash + background: false + args: + - -c + - kubectl get secret --context $CONTEXT -n $NAMESPACE $NAME -o jsonpath='{.data.ca\.crt}' | base64 -d | openssl storeutl -noout -text -certs /dev/stdin |& less + secret-openssl-tls: + shortCut: Shift-O + confirm: false + description: Openssl tls.crt + scopes: + - secrets + command: bash + background: false + args: + - -c + - kubectl get secret --context $CONTEXT -n $NAMESPACE $NAME -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl storeutl -noout -text -certs /dev/stdin |& less \ No newline at end of file From 3f901df47aa09ac4536814635aa54342ce83ea9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 08:48:01 -0600 Subject: [PATCH 148/169] Bump actions/checkout from 4.1.4 to 4.1.5 (#2691) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.4...v4.1.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 04369f158c..9de7abe235 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - name: Install Go uses: actions/setup-go@v5.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8c2571c20..773e280bca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.5 - name: Install Go uses: actions/setup-go@v5.0.0 From d468d4b783b3eca61163de6f74ea9ad486ceca79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 08:48:17 -0600 Subject: [PATCH 149/169] Bump actions/setup-go from 5.0.0 to 5.0.1 (#2690) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9de7abe235..a0e5286d7e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v4.1.5 - name: Install Go - uses: actions/setup-go@v5.0.0 + uses: actions/setup-go@v5.0.1 with: go-version-file: go.mod cache-dependency-path: go.sum diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 773e280bca..f0ad1b9a73 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4.1.5 - name: Install Go - uses: actions/setup-go@v5.0.0 + uses: actions/setup-go@v5.0.1 with: go-version-file: go.mod cache-dependency-path: go.sum From 2fca1a1655d78cc65e81a689bd390781f2a3a6eb Mon Sep 17 00:00:00 2001 From: KasperHeyndrickx <32434893+KasperHeyndrickx@users.noreply.github.com> Date: Sat, 18 May 2024 16:49:20 +0200 Subject: [PATCH 150/169] fix: job color based on failures (#2686) (#2698) * fix: job color based on failures (#2686) * fix: don't show error when job succeeds in next attempt --- internal/render/job.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/render/job.go b/internal/render/job.go index 451f811ca6..7b25c49b17 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -13,7 +13,6 @@ import ( "github.com/derailed/k9s/internal/model1" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/duration" @@ -65,19 +64,18 @@ func (j Job) Render(o interface{}, ns string, r *model1.Row) error { jobSelector(job.Spec), cc, ii, - AsStatus(j.diagnose(ready, job.Status.CompletionTime)), + AsStatus(j.diagnose(ready, job.Status)), ToAge(job.GetCreationTimestamp()), } return nil } -func (Job) diagnose(ready string, completed *metav1.Time) error { +func (Job) diagnose(ready string, status batchv1.JobStatus) error { tokens := strings.Split(ready, "/") - if tokens[0] != tokens[1] { - return fmt.Errorf("expecting %s completion got %s", tokens[1], tokens[0]) + if tokens[0] != tokens[1] && status.Failed > 0 { + return fmt.Errorf("%d pods failed", status.Failed) } - return nil } From f802d3948a40821fbf5cdf20aae4be2ca5248504 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sat, 18 May 2024 22:49:41 +0800 Subject: [PATCH 151/169] allow jumping to the owner of the resource (#2700) --- internal/dao/registry.go | 15 ++++ internal/ui/dialog/selection.go | 30 ++++++++ internal/ui/modal_list.go | 109 +++++++++++++++++++++++++++++ internal/ui/pages.go | 3 +- internal/view/help_test.go | 2 +- internal/view/job.go | 4 +- internal/view/owner_extender.go | 118 ++++++++++++++++++++++++++++++++ internal/view/pod.go | 8 ++- internal/view/pod_test.go | 2 +- internal/view/rs.go | 6 +- 10 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 internal/ui/dialog/selection.go create mode 100644 internal/ui/modal_list.go create mode 100644 internal/view/owner_extender.go diff --git a/internal/dao/registry.go b/internal/dao/registry.go index e19ad58600..047a0d9f5b 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -123,6 +124,20 @@ func (m *Meta) AllGVRs() client.GVRs { return kk } +// GVK2GVR convert gvk to gvr +func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool) { + m.mx.RLock() + defer m.mx.RUnlock() + + for gvr, meta := range m.resMetas { + if gv.Group == meta.Group && gv.Version == meta.Version && kind == meta.Kind { + return gvr, true + } + } + + return client.NoGVR, false +} + // IsCRD checks if resource represents a CRD func IsCRD(r metav1.APIResource) bool { for _, c := range r.Categories { diff --git a/internal/ui/dialog/selection.go b/internal/ui/dialog/selection.go new file mode 100644 index 0000000000..fc6e545cca --- /dev/null +++ b/internal/ui/dialog/selection.go @@ -0,0 +1,30 @@ +package dialog + +import ( + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tview" +) + +type SelectAction func(index int) + +func ShowSelection(styles config.Dialog, pages *ui.Pages, title string, options []string, action SelectAction) { + list := tview.NewList() + list.ShowSecondaryText(false) + list.SetSelectedTextColor(styles.ButtonFocusFgColor.Color()) + list.SetSelectedBackgroundColor(styles.ButtonFocusBgColor.Color()) + + for _, option := range options { + list.AddItem(option, "", 0, nil) + list.AddItem(option, "", 0, nil) + } + + modal := ui.NewModalList("<"+title+">", list) + modal.SetDoneFunc(func(i int, s string) { + dismiss(pages) + action(i) + }) + + pages.AddPage(dialogKey, modal, false, false) + pages.ShowPage(dialogKey) +} diff --git a/internal/ui/modal_list.go b/internal/ui/modal_list.go new file mode 100644 index 0000000000..1827561a36 --- /dev/null +++ b/internal/ui/modal_list.go @@ -0,0 +1,109 @@ +package ui + +import ( + "github.com/derailed/tcell/v2" + "github.com/derailed/tview" +) + +type ModalList struct { + *tview.Box + + // The list embedded in the modal's frame. + list *tview.List + + // The frame embedded in the modal. + frame *tview.Frame + + // The optional callback for when the user clicked one of the items. It + // receives the index of the clicked item and the item's text. + done func(int, string) +} + +func NewModalList(title string, list *tview.List) *ModalList { + m := &ModalList{Box: tview.NewBox()} + + m.list = list + m.list.SetBackgroundColor(tview.Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0) + m.list.SetSelectedFunc(func(i int, main string, _ string, _ rune) { + if m.done != nil { + m.done(i, main) + } + }) + m.list.SetDoneFunc(func() { + if m.done != nil { + m.done(-1, "") + } + }) + + m.frame = tview.NewFrame(m.list).SetBorders(0, 0, 1, 0, 0, 0) + m.frame.SetBorder(true). + SetBackgroundColor(tview.Styles.ContrastBackgroundColor). + SetBorderPadding(1, 1, 1, 1) + m.frame.SetTitle(title) + m.frame.SetTitleColor(tcell.ColorAqua) + + return m +} + +// Draw draws this primitive onto the screen. +func (m *ModalList) Draw(screen tcell.Screen) { + // Calculate the width of this modal. + width := 0 + for i := 0; i < m.list.GetItemCount(); i++ { + main, secondary := m.list.GetItemText(i) + width = max(width, len(main)+len(secondary)+2) + } + + screenWidth, screenHeight := screen.Size() + + // Set the modal's position and size. + height := m.list.GetItemCount() + 4 + width += 2 + x := (screenWidth - width) / 2 + y := (screenHeight - height) / 2 + m.SetRect(x, y, width, height) + + // Draw the frame. + m.frame.SetRect(x, y, width, height) + m.frame.Draw(screen) +} + +func (m *ModalList) SetDoneFunc(handler func(int, string)) *ModalList { + m.done = handler + return m +} + +// Focus is called when this primitive receives focus. +func (m *ModalList) Focus(delegate func(p tview.Primitive)) { + delegate(m.list) +} + +// HasFocus returns whether this primitive has focus. +func (m *ModalList) HasFocus() bool { + return m.list.HasFocus() +} + +// MouseHandler returns the mouse handler for this primitive. +func (m *ModalList) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { + return m.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { + // Pass mouse events on to the form. + consumed, capture = m.list.MouseHandler()(action, event, setFocus) + if !consumed && action == tview.MouseLeftClick && m.InRect(event.Position()) { + setFocus(m) + consumed = true + } + return + }) +} + +// InputHandler returns the handler for this primitive. +func (m *ModalList) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { + return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { + if m.frame.HasFocus() { + if handler := m.frame.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + }) +} diff --git a/internal/ui/pages.go b/internal/ui/pages.go index 446457156c..b5864e3bec 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -5,7 +5,6 @@ package ui import ( "fmt" - "github.com/derailed/k9s/internal/model" "github.com/derailed/tview" "github.com/rs/zerolog/log" @@ -32,7 +31,7 @@ func NewPages() *Pages { func (p *Pages) IsTopDialog() bool { _, pa := p.GetFrontPage() switch pa.(type) { - case *tview.ModalForm: + case *tview.ModalForm, *ModalList: return true default: return false diff --git a/internal/view/help_test.go b/internal/view/help_test.go index f45a2cf839..b6f19c7831 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -24,7 +24,7 @@ func TestHelp(t *testing.T) { v := view.NewHelp(app) assert.Nil(t, v.Init(ctx)) - assert.Equal(t, 28, v.GetRowCount()) + assert.Equal(t, 29, v.GetRowCount()) assert.Equal(t, 8, v.GetColumnCount()) assert.Equal(t, "", strings.TrimSpace(v.GetCell(1, 0).Text)) assert.Equal(t, "Attach", strings.TrimSpace(v.GetCell(1, 1).Text)) diff --git a/internal/view/job.go b/internal/view/job.go index e414e3e5c9..c456227392 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -25,7 +25,9 @@ func NewJob(gvr client.GVR) ResourceViewer { var j Job j.ResourceViewer = NewVulnerabilityExtender( - NewLogsExtender(NewBrowser(gvr), j.logOptions), + NewOwnerExtender( + NewLogsExtender(NewBrowser(gvr), j.logOptions), + ), ) j.GetTable().SetEnterFn(j.showPods) j.GetTable().SetSortCol("AGE", true) diff --git a/internal/view/owner_extender.go b/internal/view/owner_extender.go new file mode 100644 index 0000000000..5fcc3129a0 --- /dev/null +++ b/internal/view/owner_extender.go @@ -0,0 +1,118 @@ +package view + +import ( + "context" + "fmt" + "github.com/derailed/k9s/internal/ui/dialog" + "github.com/rs/zerolog/log" + + "github.com/derailed/tcell/v2" + "github.com/go-errors/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/ui" +) + +// OwnerExtender adds owner actions to a given viewer. +type OwnerExtender struct { + ResourceViewer +} + +// NewOwnerExtender returns a new extender. +func NewOwnerExtender(r ResourceViewer) ResourceViewer { + v := &OwnerExtender{ResourceViewer: r} + v.AddBindKeysFn(v.bindKeys) + + return v +} + +func (v *OwnerExtender) bindKeys(aa *ui.KeyActions) { + aa.Add(ui.KeyShiftJ, ui.NewKeyAction("Jump Owner", v.ownerCmd, true)) +} + +func (v *OwnerExtender) ownerCmd(evt *tcell.EventKey) *tcell.EventKey { + path := v.GetTable().GetSelectedItem() + if path == "" { + return evt + } + + if err := v.findOwnerFor(path); err != nil { + log.Warn().Msgf("Unable to jump to the owner of resource %q: %s", path, err) + v.App().Flash().Warnf("Unable to jump owner: %s", err) + } + return nil +} + +func (v *OwnerExtender) findOwnerFor(path string) error { + res, err := dao.AccessorFor(v.App().factory, v.GVR()) + if err != nil { + return err + } + + o, err := res.Get(v.defaultCtx(), path) + if err != nil { + return err + } + + u, ok := v.asUnstructuredObject(o) + if !ok { + return errors.Errorf("unsupported object type: %t", o) + } + + ns, _ := client.Namespaced(path) + ownerReferences := u.GetOwnerReferences() + if len(ownerReferences) == 1 { + return v.jumpOwner(ns, ownerReferences[0]) + } else if len(ownerReferences) > 1 { + owners := make([]string, 0, len(ownerReferences)) + for idx, ownerRef := range ownerReferences { + owners = append(owners, fmt.Sprintf("%d: %s", idx, ownerRef.Kind)) + } + + dialog.ShowSelection(v.App().Styles.Dialog(), v.App().Content.Pages, "Jump To", owners, func(index int) { + if index >= 0 { + err = v.jumpOwner(ns, ownerReferences[index]) + } + }) + return err + } + + return errors.Errorf("no owner found") +} + +func (v *OwnerExtender) jumpOwner(ns string, owner metav1.OwnerReference) error { + gv, err := schema.ParseGroupVersion(owner.APIVersion) + if err != nil { + return err + } + + gvr, found := dao.MetaAccess.GVK2GVR(gv, owner.Kind) + if !found { + return errors.Errorf("unsupported GVK: %s/%s", owner.APIVersion, owner.Kind) + } + + v.App().gotoResource(gvr.String(), client.FQN(ns, owner.Name), false) + return nil +} + +func (v *OwnerExtender) defaultCtx() context.Context { + return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory) +} + +func (v *OwnerExtender) asUnstructuredObject(o runtime.Object) (*unstructured.Unstructured, bool) { + switch v := o.(type) { + case *unstructured.Unstructured: + return v, true + case *render.PodWithMetrics: + return v.Raw, true + default: + return nil, false + } +} diff --git a/internal/view/pod.go b/internal/view/pod.go index 418029e542..75e5a2fb04 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -50,9 +50,11 @@ type Pod struct { func NewPod(gvr client.GVR) ResourceViewer { var p Pod p.ResourceViewer = NewPortForwardExtender( - NewVulnerabilityExtender( - NewImageExtender( - NewLogsExtender(NewBrowser(gvr), p.logOptions), + NewOwnerExtender( + NewVulnerabilityExtender( + NewImageExtender( + NewLogsExtender(NewBrowser(gvr), p.logOptions), + ), ), ), ) diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index bbbf0378b9..23bdebaf74 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -19,7 +19,7 @@ func TestPodNew(t *testing.T) { assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "Pods", po.Name()) - assert.Equal(t, 27, len(po.Hints())) + assert.Equal(t, 28, len(po.Hints())) } // Helpers... diff --git a/internal/view/rs.go b/internal/view/rs.go index 86e2c5956b..07178563a1 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -21,7 +21,11 @@ type ReplicaSet struct { // NewReplicaSet returns a new viewer. func NewReplicaSet(gvr client.GVR) ResourceViewer { r := ReplicaSet{ - ResourceViewer: NewVulnerabilityExtender(NewBrowser(gvr)), + ResourceViewer: NewOwnerExtender( + NewVulnerabilityExtender( + NewBrowser(gvr), + ), + ), } r.AddBindKeysFn(r.bindKeys) r.GetTable().SetEnterFn(r.showPods) From 554360f11028b0f61d5ea604c95a93bf8437c064 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 08:49:56 -0600 Subject: [PATCH 152/169] Bump golangci/golangci-lint-action from 5.1.0 to 6.0.1 (#2702) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5.1.0 to 6.0.1. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v5.1.0...v6.0.1) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a0e5286d7e..f25bea70da 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: cache-dependency-path: go.sum - name: Lint - uses: golangci/golangci-lint-action@v5.1.0 + uses: golangci/golangci-lint-action@v6.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check \ No newline at end of file From 0afea245b7af8f21b46169ef6b817ea5769baab2 Mon Sep 17 00:00:00 2001 From: Luca Mattiello Date: Sat, 25 May 2024 17:33:11 +0200 Subject: [PATCH 153/169] feat: Add plugins for argo-rollouts (#2711) Signed-off-by: Luca Mattiello --- plugins/argo-rollouts.yaml | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 plugins/argo-rollouts.yaml diff --git a/plugins/argo-rollouts.yaml b/plugins/argo-rollouts.yaml new file mode 100644 index 0000000000..b6e7fec8f5 --- /dev/null +++ b/plugins/argo-rollouts.yaml @@ -0,0 +1,51 @@ +# Manage argo-rollouts +# See https://argoproj.github.io/argo-rollouts/ +# Get rollout details +# Watch rollout progress +#

(with confirmation) Promote rollout +# (with confirmation) Restart rollout +plugins: + argo-rollouts-get: + shortCut: g + confirm: false + description: Get details + scopes: + - rollouts + command: bash + background: false + args: + - -c + - kubectl argo rollouts get rollout $NAME --context $CONTEXT -n $NAMESPACE |& less + argo-rollouts-watch: + shortCut: w + confirm: false + description: Watch progress + scopes: + - rollouts + command: bash + background: false + args: + - -c + - kubectl argo rollouts get rollout $NAME --context $CONTEXT -n $NAMESPACE -w |& less + argo-rollouts-promote: + shortCut: p + confirm: true + description: Promote + scopes: + - rollouts + command: bash + background: false + args: + - -c + - kubectl argo rollouts promote $NAME --context $CONTEXT -n $NAMESPACE |& less + argo-rollouts-restart: + shortCut: r + confirm: true + description: Restart + scopes: + - rollouts + command: bash + background: false + args: + - -c + - kubectl argo rollouts restart $NAME --context $CONTEXT -n $NAMESPACE |& less From 9594065f41583766b4180151ba72788f4358721b Mon Sep 17 00:00:00 2001 From: gitolicious <26963495+gitolicious@users.noreply.github.com> Date: Sun, 26 May 2024 16:56:00 +0200 Subject: [PATCH 154/169] fix: jump to namespaceless owner reference (#2718) --- internal/dao/registry.go | 6 +++--- internal/view/owner_extender.go | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 047a0d9f5b..85416a752b 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -125,17 +125,17 @@ func (m *Meta) AllGVRs() client.GVRs { } // GVK2GVR convert gvk to gvr -func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool) { +func (m *Meta) GVK2GVR(gv schema.GroupVersion, kind string) (client.GVR, bool, bool) { m.mx.RLock() defer m.mx.RUnlock() for gvr, meta := range m.resMetas { if gv.Group == meta.Group && gv.Version == meta.Version && kind == meta.Kind { - return gvr, true + return gvr, meta.Namespaced, true } } - return client.NoGVR, false + return client.NoGVR, false, false } // IsCRD checks if resource represents a CRD diff --git a/internal/view/owner_extender.go b/internal/view/owner_extender.go index 5fcc3129a0..89b42449ec 100644 --- a/internal/view/owner_extender.go +++ b/internal/view/owner_extender.go @@ -93,12 +93,19 @@ func (v *OwnerExtender) jumpOwner(ns string, owner metav1.OwnerReference) error return err } - gvr, found := dao.MetaAccess.GVK2GVR(gv, owner.Kind) + gvr, namespaced, found := dao.MetaAccess.GVK2GVR(gv, owner.Kind) if !found { return errors.Errorf("unsupported GVK: %s/%s", owner.APIVersion, owner.Kind) } - v.App().gotoResource(gvr.String(), client.FQN(ns, owner.Name), false) + var ownerFQN string + if namespaced { + ownerFQN = client.FQN(ns, owner.Name) + } else { + ownerFQN = owner.Name + } + + v.App().gotoResource(gvr.String(), ownerFQN, false) return nil } From 8fe7a1ac8c1a9f24ae9cc59cc24423375c43ecee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 09:05:20 -0600 Subject: [PATCH 155/169] Bump alpine from 3.19.1 to 3.20.0 (#2721) Bumps alpine from 3.19.1 to 3.20.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6677c86fd6..c76d4ba3b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl && make bu # ----------------------------------------------------------------------------- # Build the final Docker image -FROM alpine:3.19.1 +FROM alpine:3.20.0 ARG KUBECTL_VERSION="v1.29.0" COPY --from=build /k9s/execs/k9s /bin/k9s From c4ff75c81a1dff06ba897ba119acef8dc5893778 Mon Sep 17 00:00:00 2001 From: Prasad Katti Date: Sat, 8 Jun 2024 08:39:29 -0700 Subject: [PATCH 156/169] use policy/v1 instead of policy/v1beta1 (#2732) --- internal/model/registry.go | 2 +- internal/render/pdb.go | 4 ++-- internal/render/testdata/pdb.json | 6 +++--- internal/xray/tree_node.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/model/registry.go b/internal/model/registry.go index 4ad382da5a..97c2539455 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -184,7 +184,7 @@ var Registry = map[string]ResourceMeta{ }, // Policy... - "policy/v1beta1/poddisruptionbudgets": { + "policy/v1/poddisruptionbudgets": { Renderer: &render.PodDisruptionBudget{}, }, diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 656b49b8bd..f80fb4eef7 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -10,7 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" "github.com/derailed/tview" - v1beta1 "k8s.io/api/policy/v1beta1" + v1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -44,7 +44,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *model1.Row) err if !ok { return fmt.Errorf("expected PodDisruptionBudget, but got %T", o) } - var pdb v1beta1.PodDisruptionBudget + var pdb v1.PodDisruptionBudget err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pdb) if err != nil { return err diff --git a/internal/render/testdata/pdb.json b/internal/render/testdata/pdb.json index 0e4a36010d..4753694d0e 100644 --- a/internal/render/testdata/pdb.json +++ b/internal/render/testdata/pdb.json @@ -1,16 +1,16 @@ { - "apiVersion": "policy/v1beta1", + "apiVersion": "policy/v1", "kind": "PodDisruptionBudget", "metadata": { "annotations": { - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"policy/v1beta1\",\"kind\":\"PodDisruptionBudget\",\"metadata\":{\"annotations\":{},\"name\":\"fred\",\"namespace\":\"default\"},\"spec\":{\"minAvailable\":2,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}}}}\n" + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"policy/v1\",\"kind\":\"PodDisruptionBudget\",\"metadata\":{\"annotations\":{},\"name\":\"fred\",\"namespace\":\"default\"},\"spec\":{\"minAvailable\":2,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}}}}\n" }, "creationTimestamp": "2019-08-31T03:48:10Z", "generation": 1, "name": "fred", "namespace": "default", "resourceVersion": "49885429", - "selfLink": "/apis/policy/v1beta1/namespaces/default/poddisruptionbudgets/fred", + "selfLink": "/apis/policy/v1/namespaces/default/poddisruptionbudgets/fred", "uid": "26b6cf70-cba2-11e9-990f-42010a800218" }, "spec": { diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go index fb1a820f48..7a62e48e79 100644 --- a/internal/xray/tree_node.go +++ b/internal/xray/tree_node.go @@ -489,7 +489,7 @@ func toEmoji(gvr string) string { return "👨🏻‍" case "networking.k8s.io/v1/networkpolicies": return "📕" - case "policy/v1beta1/poddisruptionbudgets": + case "policy/v1/poddisruptionbudgets": return "🏷 " case "policy/v1beta1/podsecuritypolicies": return "👮‍♂️" From 7380be9cf8a678d454cb9f9f0fae5a57e9cbb129 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sat, 15 Jun 2024 23:03:13 +0800 Subject: [PATCH 157/169] fix view sorting being reset (#2736) --- internal/config/views.go | 12 ++++++++++++ internal/config/views_test.go | 21 +++++++++++++++++++++ internal/ui/select_table.go | 2 +- internal/ui/table.go | 16 +++++++++++----- internal/view/types.go | 4 ++-- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/internal/config/views.go b/internal/config/views.go index cfa1b7caa1..cafb9053da 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -4,10 +4,12 @@ package config import ( + "cmp" "errors" "fmt" "io/fs" "os" + "slices" "strings" "github.com/derailed/k9s/internal/config/data" @@ -48,6 +50,16 @@ func (v *ViewSetting) SortCol() (string, bool, error) { return tt[0], tt[1] == "desc", nil } +func (v *ViewSetting) Equals(vs *ViewSetting) bool { + if v == nil || vs == nil { + return v == nil && vs == nil + } + if c := slices.Compare(v.Columns, vs.Columns); c != 0 { + return false + } + return cmp.Compare(v.SortColumn, vs.SortColumn) == 0 +} + // CustomView represents a collection of view customization. type CustomView struct { Views map[string]ViewSetting `yaml:"views"` diff --git a/internal/config/views_test.go b/internal/config/views_test.go index 2afeea2854..0764d2435e 100644 --- a/internal/config/views_test.go +++ b/internal/config/views_test.go @@ -17,3 +17,24 @@ func TestViewSettingsLoad(t *testing.T) { assert.Equal(t, 1, len(cfg.Views)) assert.Equal(t, 4, len(cfg.Views["v1/pods"].Columns)) } + +func TestViewSetting_Equals(t *testing.T) { + tests := []struct { + v1, v2 *config.ViewSetting + equals bool + }{ + {nil, nil, true}, + {&config.ViewSetting{}, nil, false}, + {nil, &config.ViewSetting{}, false}, + {&config.ViewSetting{}, &config.ViewSetting{}, true}, + {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{}, false}, + {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{Columns: []string{"A"}}, true}, + {&config.ViewSetting{Columns: []string{"A"}}, &config.ViewSetting{Columns: []string{"B"}}, false}, + {&config.ViewSetting{SortColumn: "A"}, &config.ViewSetting{SortColumn: "B"}, false}, + {&config.ViewSetting{SortColumn: "A"}, &config.ViewSetting{SortColumn: "A"}, true}, + } + + for _, tt := range tests { + assert.Equalf(t, tt.equals, tt.v1.Equals(tt.v2), "%#v and %#v", tt.v1, tt.v2) + } +} diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index b0dfdeaae9..155ef371d2 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -218,7 +218,7 @@ func (s *SelectTable) markRange(prev, curr int) { } // IsMarked returns true if this item was marked. -func (s *Table) IsMarked(item string) bool { +func (s *SelectTable) IsMarked(item string) bool { _, ok := s.marks[item] return ok } diff --git a/internal/ui/table.go b/internal/ui/table.go index 0af73164ec..a1cd7365ef 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -106,11 +106,16 @@ func (t *Table) getMSort() bool { return t.manualSort } -func (t *Table) setVs(vs *config.ViewSetting) { +func (t *Table) setVs(vs *config.ViewSetting) bool { t.mx.Lock() defer t.mx.Unlock() - t.viewSetting = vs + if !t.viewSetting.Equals(vs) { + t.viewSetting = vs + return true + } + + return false } func (t *Table) getVs() *config.ViewSetting { @@ -150,9 +155,10 @@ func (t *Table) GVR() client.GVR { return t.gvr } // ViewSettingsChanged notifies listener the view configuration changed. func (t *Table) ViewSettingsChanged(vs config.ViewSetting) { - t.setVs(&vs) - t.setMSort(false) - t.Refresh() + if t.setVs(&vs) { + t.setMSort(false) + t.Refresh() + } } // StylesChanged notifies the skin changed. diff --git a/internal/view/types.go b/internal/view/types.go index 3f9d68627a..76a4c9a624 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -73,7 +73,7 @@ type Viewer interface { type TableViewer interface { Viewer - // Table returns a table component. + // GetTable returns a table component. GetTable() *Table } @@ -90,7 +90,7 @@ type ResourceViewer interface { // SetContextFn provision a custom context. SetContextFn(ContextFunc) - // AddBindKeys provision additional key bindings. + // AddBindKeysFn provision additional key bindings. AddBindKeysFn(BindKeysFunc) // SetInstance sets a parent FQN From 14d6bd45f5eac85a0768b45ff086bd3a626434a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Jun 2024 09:19:57 -0600 Subject: [PATCH 158/169] --- (#2707) updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f25bea70da..76e5a481bd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Install Go uses: actions/setup-go@v5.0.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0ad1b9a73..8b229ce36c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4.1.5 + uses: actions/checkout@v4.1.6 - name: Install Go uses: actions/setup-go@v5.0.1 From 3f30a706887ccd42e85344824132e3d3a9354b97 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sat, 15 Jun 2024 10:14:57 -0600 Subject: [PATCH 159/169] K9s/release v0.32.5 (#2740) * [MAINT] Bump grype rev * add env vars to override pf address and nodeshell * mv bailout to app view * rel v0.32.5 --- Makefile | 2 +- README.md | 6 +- change_logs/release_v0.32.5.md | 64 +++++++ go.mod | 117 +++++++------ go.sum | 284 ++++++++++++++++++------------- internal/config/config.go | 16 ++ internal/config/data/context.go | 3 + internal/config/data/helpers.go | 9 + internal/ui/app.go | 14 -- internal/ui/app_test.go | 2 +- internal/view/app.go | 14 ++ internal/view/cmd/interpreter.go | 7 + internal/view/cmd/types.go | 5 + internal/view/command.go | 15 ++ internal/vul/scanner.go | 6 +- snap/snapcraft.yaml | 2 +- 16 files changed, 365 insertions(+), 201 deletions(-) create mode 100644 change_logs/release_v0.32.5.md diff --git a/Makefile b/Makefile index a1b57045fc..a2b5a10ba3 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.32.4 +VERSION ?= v0.32.5 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/README.md b/README.md index 236f36fa78..5ff67c5ca4 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ K9s uses aliases to navigate most K8s resources. |---------------------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------| | Show active keyboard mnemonics and help | `?` | | | Show all available resource alias | `ctrl-a` | | -| To bail out of K9s | `:q`, `ctrl-c` | | +| To bail out of K9s | `:quit`, `:q`, `ctrl-c` | | | View a Kubernetes resource using singular/plural or short-name | `:`pod⏎ | accepts singular, plural, short-name or alias ie pod or pods | | View a Kubernetes resource in a given namespace | `:`pod ns-x⏎ | | | View filtered pods (New v0.30.0!) | `:`pod /fred⏎ | View all pods filtered by fred | @@ -374,6 +374,8 @@ K9s uses aliases to navigate most K8s resources. > NOTE: This is still in flux and will change while in pre-release stage! +You can now override the context portForward default address configuration by setting an env variable that can override all clusters portForward local address using `K9S_DEFAULT_PF_ADDRESS=a.b.c.d` + ```yaml # $XDG_CONFIG_HOME/k9s/config.yaml k9s: @@ -450,6 +452,8 @@ K9s has integration with [Popeye](https://popeyecli.io/), which is a Kubernetes By enabling the nodeShell feature gate on a given cluster, K9s allows you to shell into your cluster nodes. Once enabled, you will have a new `s` for `shell` menu option while in node view. K9s will launch a pod on the selected node using a special k9s_shell pod. Furthermore, you can refine your shell pod by using a custom docker image preloaded with the shell tools you love. By default k9s uses a BusyBox image, but you can configure it as follows: +Alternatively, you can now override the context configuration by setting an env variable that can override all clusters node shell gate using `K9S_FEATURE_GATE_NODE_SHELL=true|false` + ```yaml # $XDG_CONFIG_HOME/k9s/config.yaml k9s: diff --git a/change_logs/release_v0.32.5.md b/change_logs/release_v0.32.5.md new file mode 100644 index 0000000000..0948e3a5c2 --- /dev/null +++ b/change_logs/release_v0.32.5.md @@ -0,0 +1,64 @@ + + +# Release v0.32.5 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#2734](https://github.com/derailed/k9s/issues/2734) Incorrect pod containers displayed when using custom resource columns +* [#2733](https://github.com/derailed/k9s/issues/2733) Toggle Wide and Toggle Faults broken for PDB view +* [#2656](https://github.com/derailed/k9s/issues/2656) nil pointer dereference when switching contexts +* [#2617](https://github.com/derailed/k9s/issues/2617) Plugin command execution output + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#2736](https://github.com/derailed/k9s/pull/2736) fix view sorting being reset +* [#2732](https://github.com/derailed/k9s/pull/2732) use policy/v1 instead of policy/v1beta1 +* [#2728](https://github.com/derailed/k9s/pull/2728) feat: add pool col to node view +* [#2718](https://github.com/derailed/k9s/pull/2718) fix: jump to namespaceless owner reference +* [#2711](https://github.com/derailed/k9s/pull/2711) Add plugins for argo-rollouts +* [#2700](https://github.com/derailed/k9s/pull/2700) feat: allow jumping to the owner of the resource +* [#2699](https://github.com/derailed/k9s/pull/2699) Added cert-manager and openssl plugins +* [#2711](https://github.com/derailed/k9s/pull/2711) Add plugins for argo-rollouts +* [#2698](https://github.com/derailed/k9s/pull/2698) fix: job color based on failures (#2686) +* [#2685](https://github.com/derailed/k9s/pull/2685) feat: support cluster and cmp view +* [#2678](https://github.com/derailed/k9s/pull/2678) fix: do not hard-code path to kubectl in jq plugin +* [#2676](https://github.com/derailed/k9s/pull/2676) Add kanagawa skin +* [#2666](https://github.com/derailed/k9s/pull/2666) save config when closing k9s with ctrl-c +* [#2644](https://github.com/derailed/k9s/pull/2644) Allow overwriting plugin output with command's stdout + +--- + + © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/go.mod b/go.mod index cf66b06538..37d48c50dd 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,12 @@ module github.com/derailed/k9s -go 1.21.1 - -toolchain go1.21.4 +go 1.22.0 require ( github.com/adrg/xdg v0.4.0 - github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc - github.com/anchore/grype v0.74.0 - github.com/anchore/syft v0.100.0 + github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 + github.com/anchore/grype v0.77.0 + github.com/anchore/syft v1.2.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 @@ -17,6 +15,7 @@ require ( github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/fvbommel/sortorder v1.1.0 + github.com/go-errors/errors v1.4.2 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-runewidth v0.0.15 github.com/olekukonko/tablewriter v0.0.5 @@ -31,14 +30,14 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.4 - k8s.io/api v0.29.3 - k8s.io/apiextensions-apiserver v0.29.3 - k8s.io/apimachinery v0.29.3 - k8s.io/cli-runtime v0.29.3 - k8s.io/client-go v0.29.3 + k8s.io/api v0.30.1 + k8s.io/apiextensions-apiserver v0.30.1 + k8s.io/apimachinery v0.30.1 + k8s.io/cli-runtime v0.30.1 + k8s.io/client-go v0.30.1 k8s.io/klog/v2 v2.120.1 - k8s.io/kubectl v0.29.3 - k8s.io/metrics v0.29.3 + k8s.io/kubectl v0.30.1 + k8s.io/metrics v0.30.1 sigs.k8s.io/yaml v1.4.0 ) @@ -51,10 +50,10 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect - github.com/DataDog/zstd v1.4.5 // indirect + github.com/DataDog/zstd v1.5.5 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect @@ -63,15 +62,16 @@ require ( github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b // indirect + github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a // indirect github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect - github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect - github.com/anchore/stereoscope v0.0.1 // indirect + github.com/anchore/packageurl-go v0.1.1-0.20240312213626-055233e539b4 // indirect + github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect @@ -86,7 +86,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/charmbracelet/lipgloss v0.9.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd v1.7.12 // indirect @@ -99,18 +99,19 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v24.0.6+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v25.0.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v24.0.9+incompatible // indirect + github.com/docker/docker v26.0.1+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/elliotchance/phpserialize v1.4.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect @@ -123,11 +124,10 @@ require ( github.com/gdamore/encoding v1.0.0 // indirect github.com/github/go-spdx/v2 v2.2.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect - github.com/glebarez/sqlite v1.10.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/glebarez/sqlite v1.11.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -144,13 +144,13 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-containerregistry v0.17.0 // indirect + github.com/google/go-containerregistry v0.19.1 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/licensecheck v0.3.1 // indirect github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gookit/color v1.5.4 // indirect @@ -180,11 +180,11 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect - github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b // indirect + github.com/knqyf263/go-rpmdb v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -206,9 +206,10 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect github.com/moby/sys/user v0.1.0 // indirect @@ -216,16 +217,16 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nwaples/rardecode v1.1.0 // indirect - github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/gomega v1.29.0 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.31.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/image-spec v1.1.0-rc6 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/openvex/go-vex v0.2.5 // indirect @@ -245,19 +246,19 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.3 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/saferwall/pe v1.4.8 // indirect + github.com/saferwall/pe v1.5.2 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect - github.com/sassoftware/go-rpmutils v0.2.0 // indirect + github.com/sassoftware/go-rpmutils v0.3.0 // indirect github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spdx/tools-golang v0.5.3 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -268,7 +269,7 @@ require ( github.com/sylabs/sif/v2 v2.11.5 // indirect github.com/sylabs/squashfs v0.6.1 // indirect github.com/therootcompany/xz v1.0.1 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/go-mtree v0.5.3 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/vifraa/gopom v1.0.0 // indirect @@ -289,21 +290,19 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - go.uber.org/goleak v1.2.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.152.0 // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect @@ -312,16 +311,16 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gorm.io/gorm v1.25.5 // indirect - k8s.io/apiserver v0.29.3 // indirect - k8s.io/component-base v0.29.3 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + gorm.io/gorm v1.25.9 // indirect + k8s.io/apiserver v0.30.1 // indirect + k8s.io/component-base v0.30.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - modernc.org/libc v1.29.0 // indirect + modernc.org/libc v1.41.0 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect - oras.land/oras-go v1.2.4 // indirect + modernc.org/sqlite v1.29.6 // indirect + oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/go.sum b/go.sum index 3c299871bb..49cb4ce384 100644 --- a/go.sum +++ b/go.sum @@ -193,8 +193,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -206,8 +206,8 @@ github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7B github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -227,8 +227,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= @@ -239,10 +239,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ= -github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU= +github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 h1:u9XrEabKlGPsrmRvAER+kUKkwXiJfLyqGhmOTFsXjX4= +github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65/go.mod h1:8Jr7CjmwFVcBPtkJdTpaAGHimoGJGfbExypjzOu87Og= github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A= github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo= +github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q= +github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8= github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw= github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= @@ -253,14 +255,14 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= -github.com/anchore/grype v0.74.0 h1:YesFYnishQEC646iCt0hAvuEfQTvLrhCGJrANyB0c2Q= -github.com/anchore/grype v0.74.0/go.mod h1:kxRA1NCUGjTyO0C+babd46oSH2VL3PnF8b+tWGcT21k= -github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= -github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.1 h1:OxF7PaxMltnAxjLnDMyka+SKRIQar/bBkDdavsnjyxM= -github.com/anchore/stereoscope v0.0.1/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU= -github.com/anchore/syft v0.100.0 h1:XUpV4xWmD2cBS9hhhEdJEppItz0AxG8f5W3JhI2tQvY= -github.com/anchore/syft v0.100.0/go.mod h1:laFRFA/okrA+ut+wPCU32hNkdPEwQfXyaB7E21ymWFc= +github.com/anchore/grype v0.77.0 h1:HoTdZ67INrEpEiSKL713zY+j77HxoEAcsMPIZDZ4yP4= +github.com/anchore/grype v0.77.0/go.mod h1:k6QLcebOqPm+90y8mMesOJM6A6DYQllOic6Tmz507sc= +github.com/anchore/packageurl-go v0.1.1-0.20240312213626-055233e539b4 h1:SjemQ90fgflz39HG+VMkNfrpUVJpcFW6ZFA3TDXqzBM= +github.com/anchore/packageurl-go v0.1.1-0.20240312213626-055233e539b4/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= +github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84 h1:/E74wU51M87fX5UWHubLZiENXbuAci+xtbSb+JFsIYg= +github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84/go.mod h1:evQiJMQG56Z7/L5uhA8kfhhjF6ESJUZzUH9ms6bQ2Co= +github.com/anchore/syft v1.2.0 h1:e6cJVzHErrZuYTWlSjxI/JbXS5ipaN8cdjXwGpd34MQ= +github.com/anchore/syft v1.2.0/go.mod h1:0oY5LHY9MC/Mui6ZTjd0jcJRU6U6HNxaoQPWbZ4RhhY= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -325,8 +327,12 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.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/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= +github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= +github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -349,6 +355,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= @@ -390,18 +398,18 @@ github.com/derailed/tview v0.8.3/go.mod h1:q+odnnhO6QDPpBT+0dqaWj+X+uoJ6MJehXj9s github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= -github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +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/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= +github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.0.1+incompatible h1:t39Hm6lpXuXtgkF0dm1t9a5HkbUfdGy6XbWexmGr+hA= +github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -419,6 +427,8 @@ github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= +github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= 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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -459,6 +469,7 @@ github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6 github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -476,14 +487,14 @@ github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvp github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.4.12 h1:YeMgKOm0XW3f/Pt2rYpUlpyF8nG6lYGe9oXFJw5LdME= -github.com/gkampitakis/go-snaps v0.4.12/go.mod h1:PpnF1KPXQAHBdb/DHoi/1VmlwE+ZkVHzl+QHmgzMSz8= +github.com/gkampitakis/go-snaps v0.5.3 h1:2cJnBgHzJhh0Jk5XBIyDYDe1Ylfncoa9r9bVJ5qvOAE= +github.com/gkampitakis/go-snaps v0.5.3/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= -github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= -github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= 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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -492,8 +503,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -519,6 +530,7 @@ github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsX github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= @@ -598,8 +610,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= -github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -637,8 +649,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -670,7 +682,10 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -716,6 +731,7 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -766,10 +782,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -777,8 +792,8 @@ github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GX github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= -github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b h1:boYyvL3tbUuKcMN029mpCl7oYYJ7yIXujLj+fiW4Alc= -github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= +github.com/knqyf263/go-rpmdb v0.1.0 h1:pOgjtOGtW0B+ibY905hP3ETrYFmLZsHiReKsplcs+to= +github.com/knqyf263/go-rpmdb v0.1.0/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -818,8 +833,8 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ= -github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -840,14 +855,16 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= +github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -882,12 +899,14 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= @@ -907,6 +926,10 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= @@ -916,22 +939,29 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= +github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= @@ -1006,12 +1036,12 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -1021,8 +1051,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/saferwall/pe v1.4.8 h1:ey/L8FGBMrJ1Xh+Rltj1MAFPZ4LOQYGJqNa5B1Na6B0= -github.com/saferwall/pe v1.4.8/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= +github.com/saferwall/pe v1.5.2 h1:h5lLtLsyxGHQ9dN6cd8EfeLEBEo5gdqJpkuw4o4vTMY= +github.com/saferwall/pe v1.5.2/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= @@ -1034,8 +1064,8 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7 github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= -github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= -github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI= +github.com/sassoftware/go-rpmutils v0.3.0 h1:tE4TZ8KcOXay5iIP64P291s6Qxd9MQCYhI7DU+f3gFA= +github.com/sassoftware/go-rpmutils v0.3.0/go.mod h1:hM9wdxFsjUFR/tJ6SMsLrJuChcucCa0DsCzE9RMfwMo= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -1043,8 +1073,8 @@ github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624 github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -1056,8 +1086,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1121,8 +1151,9 @@ github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w= @@ -1186,17 +1217,24 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -1210,15 +1248,14 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 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.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1229,8 +1266,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1259,10 +1296,11 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1288,6 +1326,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1319,8 +1358,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1347,8 +1386,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1364,11 +1403,12 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1382,12 +1422,14 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1410,6 +1452,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1459,9 +1502,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1469,8 +1511,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1507,7 +1549,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1538,6 +1579,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1548,8 +1590,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1617,7 +1659,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1794,6 +1835,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1816,8 +1858,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= +gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= @@ -1829,40 +1871,40 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= -k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= -k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= -k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc= -k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= -k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= -k8s.io/apiserver v0.29.3 h1:xR7ELlJ/BZSr2n4CnD3lfA4gzFivh0wwfNfz9L0WZcE= -k8s.io/apiserver v0.29.3/go.mod h1:hrvXlwfRulbMbBgmWRQlFru2b/JySDpmzvQwwk4GUOs= -k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= -k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= -k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= -k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= -k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= -k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= +k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8= +k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo= +k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw= +k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= +k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= -k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= -k8s.io/metrics v0.29.3 h1:nN+eavbMQ7Kuif2tIdTr2/F2ec2E/SIAWSruTZ+Ye6U= -k8s.io/metrics v0.29.3/go.mod h1:kb3tGGC4ZcIDIuvXyUE291RwJ5WmDu0tB4wAVZM6h2I= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY= +k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0= +k8s.io/metrics v0.30.1 h1:PeA9cP0kxVtaC8Wkzp4sTkr7YSkd9R0UYP6cCHOOY1M= +k8s.io/metrics v0.30.1/go.mod h1:gVAhTTgfNKsn9D1kB7Nmb1T31relBuXzzGUE7klyOkM= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= -modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= +modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= -oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= +modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= +modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= +oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/config/config.go b/internal/config/config.go index 5f7c2471d1..a5da1dfa89 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,6 +12,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/json" + "github.com/derailed/k9s/internal/view/cmd" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -87,6 +88,7 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c switch { case k9sFlags != nil && IsBoolSet(k9sFlags.AllNamespaces): ns = client.NamespaceAll + c.ResetActiveView() case isStringSet(flags.Namespace): ns = *flags.Namespace default: @@ -180,6 +182,20 @@ func (c *Config) ActiveView() string { return cmd } +func (c *Config) ResetActiveView() { + if isStringSet(c.K9s.manualCommand) { + return + } + v := c.ActiveView() + if v == "" { + return + } + p := cmd.NewInterpreter(v) + if p.HasNS() { + c.SetActiveView(p.Cmd()) + } +} + // SetActiveView sets current context active view. func (c *Config) SetActiveView(view string) { if ct, err := c.K9s.ActiveContext(); err == nil { diff --git a/internal/config/data/context.go b/internal/config/data/context.go index 960a423048..8feb0a9bd0 100644 --- a/internal/config/data/context.go +++ b/internal/config/data/context.go @@ -83,6 +83,9 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) { if cl, err := ks.CurrentClusterName(); err == nil { c.ClusterName = cl } + if b := os.Getenv(envFGNodeShell); b != "" { + c.FeatureGates.NodeShell = defaultFGNodeShell() + } if c.Namespace == nil { c.Namespace = NewNamespace() diff --git a/internal/config/data/helpers.go b/internal/config/data/helpers.go index 5295972d65..de2d7c7c1b 100644 --- a/internal/config/data/helpers.go +++ b/internal/config/data/helpers.go @@ -13,6 +13,7 @@ import ( const ( envPFAddress = "K9S_DEFAULT_PF_ADDRESS" + envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL" defaultPortFwdAddress = "localhost" ) @@ -36,6 +37,14 @@ func defaultPFAddress() string { return defaultPortFwdAddress } +func defaultFGNodeShell() bool { + if a := os.Getenv(envFGNodeShell); a != "" { + return a == "true" + } + + return false +} + // InList check if string is in a collection of strings. func InList(ll []string, n string) bool { for _, l := range ll { diff --git a/internal/ui/app.go b/internal/ui/app.go index bb9eaa501e..d3f0353fc3 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -143,7 +143,6 @@ func (a *App) bindKeys() { KeyColon: NewKeyAction("Cmd", a.activateCmd, false), tcell.KeyCtrlR: NewKeyAction("Redraw", a.redrawCmd, false), tcell.KeyCtrlP: NewKeyAction("Persist", a.saveCmd, false), - tcell.KeyCtrlC: NewKeyAction("Quit", a.quitCmd, false), tcell.KeyCtrlU: NewSharedKeyAction("Clear Filter", a.clearCmd, false), tcell.KeyCtrlQ: NewSharedKeyAction("Clear Filter", a.clearCmd, false), }) @@ -201,19 +200,6 @@ func (a *App) HasCmd() bool { return a.cmdBuff.IsActive() && !a.cmdBuff.Empty() } -func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey { - if a.InCmdMode() { - return evt - } - - if !a.Config.K9s.NoExitOnCtrlC { - a.BailOut() - } - - // overwrite the default ctrl-c behavior of tview - return nil -} - // InCmdMode check if command mode is active. func (a *App) InCmdMode() bool { return a.Prompt().InCmdMode() diff --git a/internal/ui/app_test.go b/internal/ui/app_test.go index 47f96fb401..2c4b137241 100644 --- a/internal/ui/app_test.go +++ b/internal/ui/app_test.go @@ -56,7 +56,7 @@ func TestAppGetActions(t *testing.T) { a.GetActions().Add(ui.KeyZ, ui.KeyAction{Description: "zorg"}) - assert.Equal(t, 7, a.GetActions().Len()) + assert.Equal(t, 6, a.GetActions().Len()) } func TestAppViews(t *testing.T) { diff --git a/internal/view/app.go b/internal/view/app.go index c0c6c41ef9..4ac7e7c2b1 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -246,6 +246,7 @@ func (a *App) bindKeys() { ui.KeyHelp: ui.NewSharedKeyAction("Help", a.helpCmd, false), tcell.KeyCtrlA: ui.NewSharedKeyAction("Aliases", a.aliasCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, false), + tcell.KeyCtrlC: ui.NewKeyAction("Quit", a.quitCmd, false), })) } @@ -658,6 +659,19 @@ func (a *App) dirCmd(path string) error { return a.inject(NewDir(path), true) } +func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey { + if a.InCmdMode() { + return evt + } + + if !a.Config.K9s.NoExitOnCtrlC { + a.BailOut() + } + + // overwrite the default ctrl-c behavior of tview + return nil +} + func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey { if evt != nil && evt.Rune() == '?' && a.Prompt().InCmdMode() { return evt diff --git a/internal/view/cmd/interpreter.go b/internal/view/cmd/interpreter.go index e47a911d30..d7af1bafd2 100644 --- a/internal/view/cmd/interpreter.go +++ b/internal/view/cmd/interpreter.go @@ -114,6 +114,13 @@ func (c *Interpreter) IsContextCmd() bool { return ok } +// IsNamespaceCmd returns true if ns cmd is detected. +func (c *Interpreter) IsNamespaceCmd() bool { + _, ok := namespaceCmd[c.cmd] + + return ok +} + // IsDirCmd returns true if dir cmd is detected. func (c *Interpreter) IsDirCmd() bool { _, ok := dirCmd[c.cmd] diff --git a/internal/view/cmd/types.go b/internal/view/cmd/types.go index 122a3ab42c..3ea52771d5 100644 --- a/internal/view/cmd/types.go +++ b/internal/view/cmd/types.go @@ -23,6 +23,11 @@ var ( "context": {}, "contexts": {}, } + namespaceCmd = map[string]struct{}{ + "ns": {}, + "namespace": {}, + "namespaces": {}, + } dirCmd = map[string]struct{}{ "dir": {}, "d": {}, diff --git a/internal/view/command.go b/internal/view/command.go index bb437355c8..befa0b8dca 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -101,6 +101,19 @@ func (c *Command) contextCmd(p *cmd.Interpreter) error { return c.exec(p, gvr, c.componentFor(gvr, ct, v), true) } +func (c *Command) namespaceCmd(p *cmd.Interpreter) bool { + ns, ok := p.NSArg() + if !ok { + return false + } + + if ns != "" { + _ = p.Reset("pod " + ns) + } + + return false +} + func (c *Command) aliasCmd(p *cmd.Interpreter) error { filter, _ := p.FilterArg() @@ -244,6 +257,8 @@ func (c *Command) specialCmd(p *cmd.Interpreter) bool { if err := c.contextCmd(p); err != nil { c.app.Flash().Err(err) } + case p.IsNamespaceCmd(): + return c.namespaceCmd(p) case p.IsDirCmd(): if a, ok := p.DirArg(); !ok { c.app.Flash().Errf("Invalid command. Use `dir xxx`") diff --git a/internal/vul/scanner.go b/internal/vul/scanner.go index aeed2f612f..82d3e47abb 100644 --- a/internal/vul/scanner.go +++ b/internal/vul/scanner.go @@ -28,7 +28,7 @@ import ( "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/store" "github.com/anchore/grype/grype/vex" - "github.com/anchore/syft/syft/pkg/cataloger" + "github.com/anchore/syft/syft" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -177,7 +177,7 @@ func (s *imageScanner) scan(ctx context.Context, img string, sc *Scan) error { Store: *s.store, IgnoreRules: s.opts.Ignore, NormalizeByCVE: s.opts.ByCVE, - FailSeverity: s.opts.FailOnServerity(), + FailSeverity: s.opts.FailOnSeverity(), Matchers: getMatchers(s.opts), VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ Documents: s.opts.VexDocuments, @@ -199,9 +199,9 @@ func (s *imageScanner) scan(ctx context.Context, img string, sc *Scan) error { func getProviderConfig(opts *options.Grype) pkg.ProviderConfig { return pkg.ProviderConfig{ SyftProviderConfig: pkg.SyftProviderConfig{ + SBOMOptions: syft.DefaultCreateSBOMConfig(), RegistryOptions: opts.Registry.ToOptions(), Exclusions: opts.Exclusions, - CatalogingOptions: cataloger.DefaultConfig(), Platform: opts.Platform, Name: opts.Name, DefaultImagePullSource: opts.DefaultImagePullSource, diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 233f9c0ac7..d6ef0648a2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.32.4' +version: 'v0.32.5' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session. From 25a310ac963c71365db59e1d359efd9cab23564f Mon Sep 17 00:00:00 2001 From: derailed Date: Sat, 15 Jun 2024 09:05:28 -0600 Subject: [PATCH 160/169] [Maint] Bump grype rev --- go.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 37d48c50dd..90bf23ec36 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/adrg/xdg v0.4.0 github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 github.com/anchore/grype v0.77.0 - github.com/anchore/syft v1.2.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 @@ -72,6 +71,7 @@ require ( github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20240312213626-055233e539b4 // indirect github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84 // indirect + github.com/anchore/syft v1.2.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect @@ -125,6 +125,7 @@ require ( github.com/github/go-spdx/v2 v2.2.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/sqlite v1.11.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect From f66bcbf993bec75543e38b6f69e5fa293764c245 Mon Sep 17 00:00:00 2001 From: derailed Date: Sat, 15 Jun 2024 10:19:43 -0600 Subject: [PATCH 161/169] update deps --- go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 90bf23ec36..37d48c50dd 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/adrg/xdg v0.4.0 github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 github.com/anchore/grype v0.77.0 + github.com/anchore/syft v1.2.0 github.com/atotto/clipboard v0.1.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/derailed/popeye v0.11.3 @@ -71,7 +72,6 @@ require ( github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20240312213626-055233e539b4 // indirect github.com/anchore/stereoscope v0.0.2-0.20240229175558-fe426d1b1c84 // indirect - github.com/anchore/syft v1.2.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect @@ -125,7 +125,6 @@ require ( github.com/github/go-spdx/v2 v2.2.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/sqlite v1.11.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect From 626bde11f31e08cf8081bced7d911f6d121582fc Mon Sep 17 00:00:00 2001 From: Prasad Katti Date: Sun, 16 Jun 2024 08:46:34 -0700 Subject: [PATCH 162/169] fix status for completed pods in workload view (#2729) --- internal/dao/workload.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/dao/workload.go b/internal/dao/workload.go index dfe4aa05b4..604c6ca9ad 100644 --- a/internal/dao/workload.go +++ b/internal/dao/workload.go @@ -158,7 +158,9 @@ func readiness(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefiniti func status(gvr client.GVR, r metav1.TableRow, h []metav1.TableColumnDefinition) string { switch gvr { case PodGVR: - if !isReady(r.Cells[indexOf("Ready", h)].(string)) || r.Cells[indexOf("Status", h)] != render.PhaseRunning { + if status := r.Cells[indexOf("Status", h)]; status == render.PhaseCompleted { + return StatusOK + } else if !isReady(r.Cells[indexOf("Ready", h)].(string)) || status != render.PhaseRunning { return DegradedStatus } case DpGVR, StsGVR: From 3901673dfbf94fa7a7ff96eaae00a5645f4092d7 Mon Sep 17 00:00:00 2001 From: Adrian Bridgett Date: Sun, 7 Jul 2024 15:15:06 +0100 Subject: [PATCH 163/169] install copyright file into correct location (#2780) --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 3788ceed95..b2ffc71807 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -110,7 +110,7 @@ nfpms: section: utils contents: - src: ./LICENSE - dst: /usr/share/doc/nfpm/copyright + dst: /usr/share/doc/k9s/copyright file_info: mode: 0644 From 8713fba02aed890ef8ef2283b98a7df546c87b8a Mon Sep 17 00:00:00 2001 From: yjqg6666 <1879641+yjqg6666@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:28:51 +0800 Subject: [PATCH 164/169] [#2773] fix freebsd build failure (#2775) fix build error: vendor/github.com/knqyf263/go-rpmdb/pkg/ndb/ndb.go:99:8: undefined: syscallFlock vendor/github.com/knqyf263/go-rpmdb/pkg/ndb/ndb.go:99:37: undefined: syscallLOCK_SH vendor/github.com/knqyf263/go-rpmdb/pkg/ndb/ndb.go:135:6: undefined: syscallFlock vendor/github.com/knqyf263/go-rpmdb/pkg/ndb/ndb.go:135:38: undefined: syscallLOCK_UN Related to issue https://github.com/knqyf263/go-rpmdb/pull/53. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 37d48c50dd..043dfc496a 100644 --- a/go.mod +++ b/go.mod @@ -184,7 +184,7 @@ require ( github.com/klauspost/pgzip v1.2.5 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect - github.com/knqyf263/go-rpmdb v0.1.0 // indirect + github.com/knqyf263/go-rpmdb v0.1.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect diff --git a/go.sum b/go.sum index 49cb4ce384..6967b4539b 100644 --- a/go.sum +++ b/go.sum @@ -792,8 +792,8 @@ github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GX github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= -github.com/knqyf263/go-rpmdb v0.1.0 h1:pOgjtOGtW0B+ibY905hP3ETrYFmLZsHiReKsplcs+to= -github.com/knqyf263/go-rpmdb v0.1.0/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= +github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584= +github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= From 350439b98553f23672f7ce0b650637d0afdd4104 Mon Sep 17 00:00:00 2001 From: Jayson Wang Date: Sun, 7 Jul 2024 22:32:59 +0800 Subject: [PATCH 165/169] proper handle OwnerReference for manually created job (#2772) --- internal/dao/cronjob.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index b02b95759e..3153349571 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -81,6 +81,7 @@ func (c *CronJob) Run(path string) error { APIVersion: c.gvr.GV().String(), Kind: "CronJob", BlockOwnerDeletion: &true, + Controller: &true, Name: cj.Name, UID: cj.UID, }, From 379ddafdb72fa2ce3999c3e3fa1318aea9624db0 Mon Sep 17 00:00:00 2001 From: Caleb Meyer Date: Tue, 30 Jul 2024 14:30:12 -0500 Subject: [PATCH 166/169] Add comment about Escape keybinding (#2817) This was the first question I had, and took me using the `?` hotkey to answer (wasn't in the docs anywhere that I saw) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ff67c5ca4..41252a3afa 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,8 @@ K9s uses aliases to navigate most K8s resources. |---------------------------------------------------------------------------------|-------------------------------|------------------------------------------------------------------------| | Show active keyboard mnemonics and help | `?` | | | Show all available resource alias | `ctrl-a` | | -| To bail out of K9s | `:quit`, `:q`, `ctrl-c` | | +| To bail out of K9s | `:quit`, `:q`, `ctrl-c` | | +| To go up/back to the previous view | `esc` | If you have crumbs on, this will go to the previous one | | View a Kubernetes resource using singular/plural or short-name | `:`pod⏎ | accepts singular, plural, short-name or alias ie pod or pods | | View a Kubernetes resource in a given namespace | `:`pod ns-x⏎ | | | View filtered pods (New v0.30.0!) | `:`pod /fred⏎ | View all pods filtered by fred | From 71338d8a067d5c306ae015a4de7899844837f383 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:58:57 -0600 Subject: [PATCH 167/169] Bump golangci/golangci-lint-action from 6.0.1 to 6.1.0 (#2813) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.0.1 to 6.1.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.0.1...v6.1.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 76e5a481bd..17570b8e46 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: cache-dependency-path: go.sum - name: Lint - uses: golangci/golangci-lint-action@v6.0.1 + uses: golangci/golangci-lint-action@v6.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check \ No newline at end of file From 2de726a2fbd4a6ad36b713417177a57b7b776a14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:59:18 -0600 Subject: [PATCH 168/169] Bump github.com/docker/docker (#2816) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.1+incompatible to 26.1.4+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v26.0.1...v26.1.4) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 043dfc496a..ac462d4651 100644 --- a/go.mod +++ b/go.mod @@ -102,7 +102,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v25.0.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v26.0.1+incompatible // indirect + github.com/docker/docker v26.1.4+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect diff --git a/go.sum b/go.sum index 6967b4539b..6694de0e64 100644 --- a/go.sum +++ b/go.sum @@ -404,8 +404,8 @@ github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbT github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.0.1+incompatible h1:t39Hm6lpXuXtgkF0dm1t9a5HkbUfdGy6XbWexmGr+hA= -github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= +github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= From 8e1a29fe64829a8c43a7a5c3389473ea108ca619 Mon Sep 17 00:00:00 2001 From: Daniel Gomes-Sebastiao Date: Thu, 8 Aug 2024 05:59:40 +1200 Subject: [PATCH 169/169] fix: align build image Go version with go.mod (#2812) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c76d4ba3b0..2c372a0e8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # The base image for building the k9s binary -FROM golang:1.21.5-alpine3.17 AS build +FROM golang:1.22-alpine3.20 AS build WORKDIR /k9s COPY go.mod go.sum main.go Makefile ./