From 4f05dc9cbf4def10c59171259b1076cd44dd26c9 Mon Sep 17 00:00:00 2001 From: derailed Date: Sat, 4 Jan 2020 14:05:41 -0700 Subject: [PATCH] checkpoint --- Makefile | 3 +- README.md | 3 - cmd/root.go | 3 +- go.mod | 14 +- go.sum | 199 ++++++++++++++++++ internal/client/client.go | 24 --- internal/client/helpers.go | 35 +++ internal/client/metrics.go | 44 +--- internal/client/types.go | 75 +++++++ internal/config/ns.go | 6 +- internal/dao/alias.go | 60 +++++- internal/{model => dao}/alias_test.go | 22 +- internal/dao/benchmark.go | 47 ++++- internal/dao/benchmark_test.go | 24 +++ internal/dao/chart.go | 123 +++++++++++ internal/dao/container.go | 100 ++++++++- internal/{model => dao}/container_test.go | 25 +-- internal/dao/context.go | 67 +----- internal/{model => dao}/crd.go | 11 +- internal/dao/cronjob.go | 9 +- internal/dao/cruiser.go | 43 ++++ internal/dao/cruiser_test.go | 40 ++++ internal/dao/describe.go | 7 +- internal/dao/dp.go | 19 +- internal/dao/ds.go | 17 +- internal/dao/generic.go | 88 +++++++- internal/dao/helpers.go | 27 ++- internal/{model => dao}/hpa.go | 15 +- internal/dao/job.go | 13 +- internal/{model => dao}/node.go | 41 ++-- internal/dao/non_resource.go | 30 +++ internal/dao/pod.go | 112 +++++++++- .../portforward.go => dao/port_forward.go} | 29 ++- internal/dao/portforward.go | 25 --- internal/{model => dao}/rbac.go | 60 ++++-- .../{model/policy.go => dao/rbac_policy.go} | 63 ++---- .../{model/subject.go => dao/rbac_subject.go} | 55 +++-- internal/dao/registry.go | 34 ++- internal/dao/resource.go | 51 +++++ internal/dao/screen_dump.go | 37 +++- internal/dao/sts.go | 19 +- internal/dao/svc.go | 12 +- .../default_fred_1577308050814961000.txt | 0 internal/dao/test_assets/crb.json | 26 +++ internal/{model => dao}/test_assets/n1.json | 0 internal/{model => dao}/test_assets/p1.json | 0 internal/dao/types.go | 33 ++- internal/model/alias.go | 43 ---- internal/model/benchmark.go | 37 ---- internal/model/benchmark_test.go | 49 ----- internal/model/container.go | 114 ---------- internal/model/context.go | 28 --- internal/model/generic.go | 150 ------------- internal/model/helpers.go | 31 --- internal/model/job.go | 69 ------ internal/model/node_test.go | 70 ------ internal/model/pod.go | 96 --------- internal/model/pod_test.go | 52 ----- internal/model/registry.go | 52 +++-- internal/model/resource.go | 49 ----- internal/model/screen_dump.go | 36 ---- internal/model/table.go | 188 +++++++++++------ internal/model/types.go | 34 ++- internal/render/alias_test.go | 9 +- internal/render/chart.go | 82 ++++++++ internal/render/cm.go | 7 +- internal/render/container.go | 3 +- internal/render/crd.go | 3 +- internal/render/cronjob.go | 5 +- internal/render/dp.go | 7 +- internal/render/ds.go | 7 +- internal/render/ep.go | 5 +- internal/render/ev.go | 7 +- internal/render/generic.go | 39 ++-- internal/render/generic_test.go | 66 +++--- internal/render/helpers.go | 7 +- internal/render/helpers_test.go | 2 +- internal/render/hpa.go | 9 +- internal/render/ing.go | 5 +- internal/render/job.go | 5 +- internal/render/np.go | 5 +- internal/render/ns_test.go | 2 +- internal/render/pdb.go | 7 +- internal/render/pod.go | 5 +- internal/render/policy.go | 4 +- internal/render/pv_test.go | 2 +- internal/render/pvc.go | 7 +- internal/render/ro.go | 5 +- internal/render/rob.go | 5 +- internal/render/rs.go | 7 +- internal/render/sa.go | 5 +- internal/render/sc.go | 3 +- internal/render/secret.go | 5 +- internal/render/sts.go | 7 +- internal/render/subject.go | 37 +++- internal/render/svc.go | 5 +- internal/render/table_data.go | 3 - internal/render/types.go | 9 - internal/ui/select_table.go | 2 +- internal/ui/table.go | 52 ++--- internal/ui/table_test.go | 9 + internal/ui/types.go | 17 +- internal/view/alias_test.go | 14 +- internal/view/app.go | 4 +- internal/view/benchmark.go | 2 +- internal/view/browser.go | 47 ++--- internal/view/charts.go | 53 +++++ internal/view/command.go | 8 +- internal/view/container.go | 2 +- internal/view/context.go | 4 +- internal/view/cronjob.go | 4 +- internal/view/dp.go | 2 +- internal/view/ds.go | 2 +- internal/view/helpers.go | 27 +-- internal/view/job.go | 3 +- internal/view/node.go | 7 +- internal/view/ns.go | 10 +- internal/view/pod.go | 4 +- internal/view/policy.go | 2 +- internal/view/rbac.go | 6 +- internal/view/registrar.go | 34 +-- internal/view/rs.go | 2 +- internal/view/screen_dump.go | 3 +- internal/view/sts.go | 2 +- internal/view/svc.go | 2 +- internal/view/table_helper.go | 7 +- internal/view/table_int_test.go | 12 +- internal/view/types.go | 2 +- internal/watch/factory.go | 15 +- 129 files changed, 2027 insertions(+), 1612 deletions(-) create mode 100644 internal/client/types.go rename internal/{model => dao}/alias_test.go (77%) create mode 100644 internal/dao/benchmark_test.go create mode 100644 internal/dao/chart.go rename internal/{model => dao}/container_test.go (74%) rename internal/{model => dao}/crd.go (69%) create mode 100644 internal/dao/cruiser.go create mode 100644 internal/dao/cruiser_test.go rename internal/{model => dao}/hpa.go (69%) rename internal/{model => dao}/node.go (69%) create mode 100644 internal/dao/non_resource.go rename internal/{model/portforward.go => dao/port_forward.go} (64%) delete mode 100644 internal/dao/portforward.go rename internal/{model => dao}/rbac.go (66%) rename internal/{model/policy.go => dao/rbac_policy.go} (72%) rename internal/{model/subject.go => dao/rbac_subject.go} (53%) create mode 100644 internal/dao/resource.go rename internal/{model => dao}/test_assets/bench/default_fred_1577308050814961000.txt (100%) create mode 100644 internal/dao/test_assets/crb.json rename internal/{model => dao}/test_assets/n1.json (100%) rename internal/{model => dao}/test_assets/p1.json (100%) delete mode 100644 internal/model/alias.go delete mode 100644 internal/model/benchmark.go delete mode 100644 internal/model/benchmark_test.go delete mode 100644 internal/model/container.go delete mode 100644 internal/model/context.go delete mode 100644 internal/model/generic.go delete mode 100644 internal/model/job.go delete mode 100644 internal/model/node_test.go delete mode 100644 internal/model/pod.go delete mode 100644 internal/model/pod_test.go delete mode 100644 internal/model/resource.go delete mode 100644 internal/model/screen_dump.go create mode 100644 internal/render/chart.go create mode 100644 internal/view/charts.go diff --git a/Makefile b/Makefile index 96f19a7f..253ca909 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ IMAGE := ${IMG_NAME}:${VERSION} default: help test: ## Run all tests - @go test ./... + @go clean --testcache && go test ./... + cover: ## Run test coverage suite @go test ./... --coverprofile=cov.out diff --git a/README.md b/README.md index 13c4dddb..9189565d 100644 --- a/README.md +++ b/README.md @@ -572,9 +572,6 @@ to make this project a reality! * [Fernand Galiana](https://github.com/derailed) * fernand@imhotep.io * [@kitesurfer](https://twitter.com/kitesurfer?lang=en) -* [Gustavo Silva Paiva](https://github.com/paivagustavo) - * guustavo.paiva@gmail.com - * [@paivagustavodev](https://twitter.com/paivagustavodev) --- diff --git a/cmd/root.go b/cmd/root.go index bfda7541..9ccc09b6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,6 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/view" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -113,7 +112,7 @@ func loadConfiguration() *config.Config { k9sCfg.K9s.OverrideCommand(*k9sFlags.Command) } - if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(render.AllNamespaces) != nil { + if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(client.AllNamespaces) != nil { log.Error().Msg("Setting active namespace") } diff --git a/go.mod b/go.mod index 77e1a0b2..898850ec 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/derailed/k9s go 1.13 replace ( + github.com/docker/docker => github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 @@ -27,19 +28,15 @@ replace ( ) require ( + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/atotto/clipboard v0.1.2 github.com/derailed/tview v0.3.3 - github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/gdamore/tcell v1.3.0 github.com/ghodss/yaml v1.0.0 - github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect - github.com/google/btree v1.0.0 // indirect - github.com/googleapis/gnostic v0.2.0 // indirect github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect - github.com/imdario/mergo v0.3.7 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-runewidth v0.0.5 github.com/petergtz/pegomock v2.6.0+incompatible @@ -47,16 +44,15 @@ require ( github.com/rs/zerolog v1.17.2 github.com/sahilm/fuzzy v0.1.0 github.com/spf13/cobra v0.0.5 - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 golang.org/x/text v0.3.2 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.2.4 + helm.sh/helm/v3 v3.0.2 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 k8s.io/cli-runtime v0.0.0 k8s.io/client-go v0.0.0 - k8s.io/klog v0.4.0 + k8s.io/klog v1.0.0 k8s.io/kubectl v0.0.0 k8s.io/kubernetes v1.16.3 k8s.io/metrics v0.0.0 diff --git a/go.sum b/go.sum index 3ac1d5c9..ede18e0e 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= @@ -28,10 +29,23 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= +github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= +github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= +github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -40,6 +54,11 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +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/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -53,13 +72,23 @@ github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1 github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 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/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= @@ -70,6 +99,10 @@ github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2 github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190823190603-4a2f61c4f2b4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= @@ -89,25 +122,46 @@ github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1D github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deislabs/oras v0.7.0 h1:RnDoFd3tQYODMiUqxgQ8JxlrlWL0/VMKIKRD01MmNYk= +github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM= +github.com/deislabs/oras v0.8.0 h1:WZqPI25DlEmth2VE/pIcnEh6msL2yHrzS5lV5gwaCsQ= github.com/derailed/tview v0.3.3 h1:tipPwxcDhx0zRBZuc8VKIrNgWL40FL5JeF/30XVieUE= github.com/derailed/tview v0.3.3/go.mod h1:yApPszFU62FoaGkf7swy2nIdV/h7Nid3dhMSVy6+OFI= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d h1:qdD+BtyCE1XXpDyhvn0yZVcZOLILdj9Cw4pKu0kQbPQ= +github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf h1:+Hdbkr8QbGSQ4dY50mmgZEGtzjhv0we2Ws2XCz3c0Q8= +github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.1 h1:Dq4iIfcM7cNtddhLVWe9h4QDjsi4OER3Z8voPu/I52g= +github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +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-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA= +github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs= @@ -117,9 +171,14 @@ github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE= +github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= +github.com/evanphx/json-patch v4.5.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/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= @@ -127,6 +186,7 @@ github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.1.2/go.mod h1:h3kq4HO9l2On+V9ed8w8ewqQEmGCSSHOgQ+2h8uzurE= @@ -141,6 +201,10 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +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 v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -156,11 +220,15 @@ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -174,6 +242,8 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0 h1:0Dn9qy1G9+UJfRU7TR8bmdGxb4uifB7HNrJjOnV0yPk= @@ -183,17 +253,30 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2 h1:ky5l57HjyVRrsJfd2+Ro5Z9PjGuKbsmftwyMtk8H7js= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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 v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -201,6 +284,8 @@ github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= @@ -212,6 +297,8 @@ github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -225,13 +312,20 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gosuri/uitable v0.0.1 h1:M9sMNgSZPyAu1FJZJLpJ16ofL8q5ko2EDUkICsynvlY= +github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -243,6 +337,8 @@ github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/heketi/heketi v9.0.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI= @@ -250,9 +346,13 @@ github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7U github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= @@ -264,11 +364,15 @@ github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62F github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -295,6 +399,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -306,14 +413,20 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= +github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -327,6 +440,7 @@ github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gn github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7PEeBTyK1rj1gABdCO2mbri6GKO1cMDs= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= +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/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= @@ -334,12 +448,19 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2Pupw60ON8TYEIGGTAI77yZsWYkiOeHFZWkwlCk= github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= @@ -351,6 +472,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petergtz/pegomock v2.6.0+incompatible h1:gD9YvI42LylIA/il2Cy8lMfg+CncNFMqexYepyEWGaQ= github.com/petergtz/pegomock v2.6.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o= +github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -359,14 +481,30 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 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 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= github.com/rakyll/hey v0.1.2 h1:XlGaKcBdmXJaPImiTnE+TGLDUWQ2toYuHCwdrylLjmg= github.com/rakyll/hey v0.1.2/go.mod h1:S5M+++KwbmxA7w68S92B5NdWiCB+cIhITaMUkq9W608= @@ -383,19 +521,24 @@ github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo= github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -404,6 +547,8 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -414,6 +559,8 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -422,10 +569,21 @@ github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKn github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xenolf/lego v0.0.0-20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= +github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg= @@ -435,6 +593,7 @@ go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8 go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -444,10 +603,14 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= +golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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= @@ -462,6 +625,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181005035420-146acd28ed58/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-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20181201002055-351d144fa1fc/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-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -471,10 +636,14 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= +golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -483,11 +652,14 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ 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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/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-20181004145325-8469e314837c/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -501,6 +673,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE= +golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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= @@ -509,7 +684,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -535,16 +714,24 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5/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-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= 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-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= +google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 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.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -559,6 +746,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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= @@ -572,6 +760,10 @@ gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= +helm.sh/helm v2.16.1+incompatible h1:np11uYeEtlYcFIFRya8Xs5ZweV1z6MvaWQqJAW+1SZQ= +helm.sh/helm/v3 v3.0.2 h1:BggvLisIMrAc+Is5oAHVrlVxgwOOrMN8nddfQbm5gKo= +helm.sh/helm/v3 v3.0.2/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= @@ -603,10 +795,14 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-aggregator v0.0.0-20190918161219-8c8f079fddc3/go.mod h1:NJisPUqwlg1A99RhO1BTnNtwC4pKUyXJ2f3Xc4PxKQg= k8s.io/kube-controller-manager v0.0.0-20190918162944-7a93a0ddadd8/go.mod h1:+HrHoqJm0UqnlrBEKXGzs2701YN4+ozi76oG7iYvJ8s= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d h1:Xpe6sK+RY4ZgCTyZ3y273UmFmURhjtoJiwOMbQsXitY= +k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-proxy v0.0.0-20190918162534-de037b596c1e/go.mod h1:/48p8Y6dkWJrll4tsceAoGKudGpRmtQu/u1zlG14NnI= k8s.io/kube-scheduler v0.0.0-20190918162820-3b5c1246eb18/go.mod h1:k2dnGirIGylr51dpqxn2Zv6Yt47A+6NiynBIYfAU67I= k8s.io/kubectl v0.0.0-20190918164019-21692a0861df h1:EwjdCG4HveZxJkI650+g4UoIuSvH7vODn55VmBjxIAo= @@ -621,11 +817,14 @@ k8s.io/repo-infra v0.0.0-20181204233714-00fe14e3d1a3/go.mod h1:+G1xBfZDfVFsm1Tj/ k8s.io/sample-apiserver v0.0.0-20190918161442-d4c9c65c82af/go.mod h1:HP/BmiRyZTMIZ5RI2p4tCz/b2kre7URuKLQ7/KHqWAs= k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA= +k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= diff --git a/internal/client/client.go b/internal/client/client.go index 2d71f9a2..d8885478 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -19,9 +19,6 @@ import ( versioned "k8s.io/metrics/pkg/client/clientset/versioned" ) -// NA Not available -const NA = "n/a" - var supportedMetricsAPIVersions = []string{"v1beta1"} // Authorizer checks what a user can or cannot do to a resource. @@ -30,27 +27,6 @@ type Authorizer interface { CanI(ns, gvr string, verbs []string) (bool, error) } -// Connection represents a Kubenetes apiserver connection. -// BOZO!! Refactor! -type Connection interface { - Authorizer - - Config() *Config - DialOrDie() kubernetes.Interface - SwitchContextOrDie(ctx string) - CachedDiscovery() (*disk.CachedDiscoveryClient, error) - RestConfigOrDie() *restclient.Config - MXDial() (*versioned.Clientset, error) - DynDialOrDie() dynamic.Interface - HasMetrics() bool - IsNamespaced(n string) bool - SupportsResource(group string) bool - ValidNamespaces() ([]v1.Namespace, error) - SupportsRes(grp string, versions []string) (string, bool, error) - ServerVersion() (*version.Info, error) - CurrentNamespaceName() (string, error) -} - // APIClient represents a Kubernetes api client. type APIClient struct { client kubernetes.Interface diff --git a/internal/client/helpers.go b/internal/client/helpers.go index c78c6aec..89ff92cf 100644 --- a/internal/client/helpers.go +++ b/internal/client/helpers.go @@ -11,6 +11,41 @@ import ( var toFileName = regexp.MustCompile(`[^(\w/\.)]`) +// ClusterWide returns true if ns designates cluster scope, false otherwise. +func IsClusterWide(ns string) bool { + return ns == NamespaceAll || ns == AllNamespaces || ns == ClusterScope +} + +// IsAllNamespace returns true if ns == all. +func IsAllNamespace(ns string) bool { + return ns == NamespaceAll +} + +// IsAllNamespaces returns true if all namespaces, false otherwise. +func IsAllNamespaces(ns string) bool { + return ns == NamespaceAll || ns == AllNamespaces +} + +// IsNamespaced returns true if a specific ns is given. +func IsNamespaced(ns string) bool { + return !IsClusterScoped(ns) +} + +// IsClusterScoped returns true if resource is not namespaced. +func IsClusterScoped(ns string) bool { + return ns == ClusterScope +} + +// NormalizeNS normalizes a namespace name to a k8s ns known designation. +func NormalizeNS(ns string) string { + switch ns { + case NamespaceAll, ClusterScope: + return "" + default: + return ns + } +} + // Namespaced converts a resource path to namespace and resource name. func Namespaced(p string) (string, string) { ns, n := path.Split(p) diff --git a/internal/client/metrics.go b/internal/client/metrics.go index e300bee3..bb2116cc 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -8,46 +8,10 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) -const ( - NamespaceAll = "all" - AllNamespaces = "" -) - -type ( - // MetricsServer serves cluster metrics for nodes and pods. - MetricsServer struct { - Connection - } - - currentMetrics struct { - CurrentCPU int64 - CurrentMEM float64 - } - - // PodMetrics represent an aggregation of all pod containers metrics. - PodMetrics currentMetrics - - // NodeMetrics describes raw node metrics. - NodeMetrics struct { - currentMetrics - AvailCPU int64 - AvailMEM float64 - TotalCPU int64 - TotalMEM float64 - } - - // ClusterMetrics summarizes total node metrics as percentages. - ClusterMetrics struct { - PercCPU float64 - PercMEM float64 - } - - // NodesMetrics tracks usage metrics per nodes. - NodesMetrics map[string]NodeMetrics - - // PodsMetrics tracks usage metrics per pods. - PodsMetrics map[string]PodMetrics -) +// MetricsServer serves cluster metrics for nodes and pods. +type MetricsServer struct { + Connection +} // NewMetricsServer return a metric server instance. func NewMetricsServer(c Connection) *MetricsServer { diff --git a/internal/client/types.go b/internal/client/types.go new file mode 100644 index 00000000..c0f73fbe --- /dev/null +++ b/internal/client/types.go @@ -0,0 +1,75 @@ +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" + "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" +) + +const ( + // NA Not available + NA = "n/a" + + // NamespaceAll designates the fictional all namespace. + NamespaceAll = "all" + + // AllNamespaces designates all namespaces. + AllNamespaces = "" + + // ClusterScope designates a resource is not namespaced. + ClusterScope = "-" +) + +// Connection represents a Kubenetes apiserver connection. +// BOZO!! Refactor! +type Connection interface { + Authorizer + + Config() *Config + DialOrDie() kubernetes.Interface + SwitchContextOrDie(ctx string) + CachedDiscovery() (*disk.CachedDiscoveryClient, error) + RestConfigOrDie() *restclient.Config + MXDial() (*versioned.Clientset, error) + DynDialOrDie() dynamic.Interface + HasMetrics() bool + IsNamespaced(n string) bool + SupportsResource(group string) bool + ValidNamespaces() ([]v1.Namespace, error) + SupportsRes(grp string, versions []string) (string, bool, error) + ServerVersion() (*version.Info, error) + CurrentNamespaceName() (string, error) +} + +type currentMetrics struct { + CurrentCPU int64 + CurrentMEM float64 +} + +// PodMetrics represent an aggregation of all pod containers metrics. +type PodMetrics currentMetrics + +// NodeMetrics describes raw node metrics. +type NodeMetrics struct { + currentMetrics + AvailCPU int64 + AvailMEM float64 + TotalCPU int64 + TotalMEM float64 +} + +// ClusterMetrics summarizes total node metrics as percentages. +type ClusterMetrics struct { + PercCPU float64 + PercMEM float64 +} + +// NodesMetrics tracks usage metrics per nodes. +type NodesMetrics map[string]NodeMetrics + +// PodsMetrics tracks usage metrics per pods. +type PodsMetrics map[string]PodMetrics diff --git a/internal/config/ns.go b/internal/config/ns.go index 10ee1b0f..c6544433 100644 --- a/internal/config/ns.go +++ b/internal/config/ns.go @@ -33,13 +33,13 @@ func (n *Namespace) Validate(c client.Connection, ks KubeSettings) { return } nn := ks.NamespaceNames(nns) - if !n.isAllNamespace() && !InList(nn, n.Active) { + if !n.isAllNamespaces() && !InList(nn, 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) { - log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespace()) + log.Debug().Msgf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespaces()) n.rmFavNS(ns) } } @@ -55,7 +55,7 @@ func (n *Namespace) SetActive(ns string, ks KubeSettings) error { return nil } -func (n *Namespace) isAllNamespace() bool { +func (n *Namespace) isAllNamespaces() bool { return n.Active == allNS || n.Active == "" } diff --git a/internal/dao/alias.go b/internal/dao/alias.go index a8a67cd9..b3619feb 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -1,23 +1,32 @@ package dao import ( + "context" + "errors" + "sort" "strings" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/render" + "k8s.io/apimachinery/pkg/runtime" ) +var _ Accessor = (*Alias)(nil) + // Alias tracks standard and custom command aliases. type Alias struct { + NonResource config.Aliases - factory Factory } // NewAlias returns a new set of aliases. func NewAlias(f Factory) *Alias { - return &Alias{ - Aliases: config.NewAliases(), - factory: f, - } + a := Alias{Aliases: config.NewAliases()} + a.Init(f, client.NewGVR("aliases")) + + return &a } // Clear remove all aliases. @@ -27,9 +36,48 @@ func (a *Alias) Clear() { } } +// List returns a collection of screen dumps. +// BOZO!! Already have aliases here. Refact!! +func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) { + a, ok := ctx.Value(internal.KeyAliases).(*Alias) + if !ok { + return nil, errors.New("no aliases found in context") + } + + m := make(config.ShortNames, len(a.Alias)) + for alias, gvr := range a.Alias { + if _, ok := m[gvr]; ok { + m[gvr] = append(m[gvr], alias) + } else { + m[gvr] = []string{alias} + } + } + + 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}) + } + + return oo, nil +} + +// GVR 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 + } + return client.GVR{}, false +} + +func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) { + panic("NYI!") +} + // Ensure makes sure alias are loaded. func (a *Alias) Ensure() (config.Alias, error) { - if err := LoadResources(a.factory); err != nil { + if err := LoadResources(a.Factory); err != nil { return config.Alias{}, err } return a.Alias, a.load() diff --git a/internal/model/alias_test.go b/internal/dao/alias_test.go similarity index 77% rename from internal/model/alias_test.go rename to internal/dao/alias_test.go index 1c76f45b..b77638de 100644 --- a/internal/model/alias_test.go +++ b/internal/dao/alias_test.go @@ -1,4 +1,4 @@ -package model_test +package dao_test import ( "context" @@ -8,7 +8,6 @@ import ( "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/watch" "github.com/stretchr/testify/assert" @@ -18,30 +17,17 @@ import ( ) func TestAliasList(t *testing.T) { - a := model.Alias{} - a.Init(render.ClusterScope, "aliases", makeFactory()) + a := dao.Alias{} + a.Init(makeFactory(), client.NewGVR("aliases")) ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases()) - oo, err := a.List(ctx) + oo, err := a.List(ctx, "-") assert.Nil(t, err) assert.Equal(t, 2, len(oo)) assert.Equal(t, 2, len(oo[0].(render.AliasRes).Aliases)) } -func TestAliasHydrate(t *testing.T) { - a := model.Alias{} - a.Init(render.ClusterScope, "aliases", makeFactory()) - - ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases()) - oo, err := a.List(ctx) - assert.Nil(t, err) - - rr := make(render.Rows, len(oo)) - assert.Nil(t, a.Hydrate(oo, rr, render.Alias{})) - assert.Equal(t, 2, len(rr)) -} - // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go index 0babc1d5..12efddf8 100644 --- a/internal/dao/benchmark.go +++ b/internal/dao/benchmark.go @@ -1,18 +1,53 @@ package dao import ( + "context" + "errors" + "io/ioutil" "os" + "path/filepath" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/render" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*Benchmark)(nil) + _ Nuker = (*Benchmark)(nil) ) // Benchmark represents a benchmark resource. type Benchmark struct { - Generic + NonResource } -var _ Accessor = (*Benchmark)(nil) -var _ Nuker = (*Benchmark)(nil) - -// Delete a Benchmark. -func (d *Benchmark) Delete(path string, cascade, force bool) error { +// Delete nukes a resource. +func (b *Benchmark) Delete(path string, cascade, force bool) error { return os.Remove(path) } + +// Get returns a resource. +func (b *Benchmark) Get(context.Context, string) (runtime.Object, error) { + panic("NYI") +} + +// List returns a collection of resources. +func (b *Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error) { + dir, ok := ctx.Value(internal.KeyDir).(string) + if !ok { + return nil, errors.New("no benchmark dir found in context") + } + + ff, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, len(ff)) + for i, f := range ff { + oo[i] = render.BenchInfo{File: f, Path: filepath.Join(dir, f.Name())} + } + + return oo, nil +} diff --git a/internal/dao/benchmark_test.go b/internal/dao/benchmark_test.go new file mode 100644 index 00000000..767abef1 --- /dev/null +++ b/internal/dao/benchmark_test.go @@ -0,0 +1,24 @@ +package dao_test + +import ( + "context" + "testing" + + "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/stretchr/testify/assert" +) + +func TestBenchmarkList(t *testing.T) { + a := dao.Benchmark{} + a.Init(makeFactory(), client.NewGVR("benchmarks")) + + ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench") + oo, err := a.List(ctx, "-") + + assert.Nil(t, err) + assert.Equal(t, 1, len(oo)) + assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path) +} diff --git a/internal/dao/chart.go b/internal/dao/chart.go new file mode 100644 index 00000000..f6da343e --- /dev/null +++ b/internal/dao/chart.go @@ -0,0 +1,123 @@ +package dao + +import ( + "context" + "fmt" + "os" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*Chart)(nil) + _ Nuker = (*Chart)(nil) + _ Describer = (*Chart)(nil) +) + +// Chart represents a helm chart. +type Chart struct { + NonResource +} + +// List returns a collection of resources. +func (c *Chart) List(ctx context.Context, ns string) ([]runtime.Object, error) { + cfg, err := c.EnsureHelmConfig(ns) + if err != nil { + return nil, err + } + + rr, err := action.NewList(cfg).Run() + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, 0, len(rr)) + for _, r := range rr { + oo = append(oo, render.ChartRes{Release: r}) + } + + return oo, nil +} + +// Get returns a resource. +func (c *Chart) Get(_ context.Context, path string) (runtime.Object, error) { + ns, n := client.Namespaced(path) + cfg, err := c.EnsureHelmConfig(ns) + if err != nil { + return nil, err + } + resp, err := action.NewGet(cfg).Run(n) + if err != nil { + return nil, err + } + + return render.ChartRes{Release: resp}, nil +} + +// Describe returns the chart notes. +func (c *Chart) Describe(path string) (string, error) { + ns, n := client.Namespaced(path) + cfg, err := c.EnsureHelmConfig(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 (c *Chart) ToYAML(path string) (string, error) { + ns, n := client.Namespaced(path) + cfg, err := c.EnsureHelmConfig(ns) + if err != nil { + return "", err + } + resp, err := action.NewGet(cfg).Run(n) + if err != nil { + return "", err + } + + return resp.Manifest, nil +} + +// Delete uninstall a Chart. +func (c *Chart) Delete(path string, cascade, force bool) error { + log.Debug().Msgf("CHART DELETE %q", path) + ns, n := client.Namespaced(path) + cfg, err := c.EnsureHelmConfig(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 +} + +func (c *Chart) EnsureHelmConfig(ns string) (*action.Configuration, error) { + cfg := new(action.Configuration) + flags := c.Client().Config().Flags() + if err := cfg.Init(flags, ns, os.Getenv("HELM_DRIVER"), helmLogger); err != nil { + return nil, err + } + return cfg, nil +} + +func helmLogger(s string, args ...interface{}) { + log.Debug().Msgf("%s %v", s, args) +} diff --git a/internal/dao/container.go b/internal/dao/container.go index 379910fb..294ae22a 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -3,23 +3,67 @@ package dao import ( "context" "errors" + "fmt" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "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" restclient "k8s.io/client-go/rest" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +) + +var ( + _ Accessor = (*Container)(nil) + _ Loggable = (*Container)(nil) ) // Container represents a pod's container dao. type Container struct { - Generic + NonResource } -var _ Accessor = (*Container)(nil) -var _ Loggable = (*Container)(nil) +// List returns a collection of containers. +func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error) { + path, ok := ctx.Value(internal.KeyPath).(string) + if !ok { + return nil, fmt.Errorf("no context path for %q", c.gvr) + } + ns, _ := render.Namespaced(path) + o, err := c.Factory.Get("v1/pods", path, true, labels.Everything()) + if err != nil { + return nil, err + } + + var po v1.Pod + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) + if err != nil { + return nil, err + } + res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) + mx := client.NewMetricsServer(c.Client()) + var pmx *mv1beta1.PodMetrics + if c.Client() != nil { + var err error + pmx, err = mx.FetchPodMetrics(ns, po.Name) + if err != nil { + log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", ns, po.Name) + } + } + + for _, co := range po.Spec.InitContainers { + res = append(res, makeContainerRes(co, po, pmx, true)) + } + for _, co := range po.Spec.Containers { + res = append(res, makeContainerRes(co, po, pmx, false)) + } + + return res, nil +} // TailLogs tails a given container logs func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { @@ -51,3 +95,53 @@ func (c *Container) Logs(path string, opts *v1.PodLogOptions) (*restclient.Reque ns, n := client.Namespaced(path) return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts), nil } + +// ---------------------------------------------------------------------------- +// Helpers... + +func makeContainerRes(co v1.Container, po v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes { + cmx, err := containerMetrics(co.Name, pmx) + if err != nil { + log.Warn().Err(err).Msgf("Container metrics for %s", co.Name) + } + + return render.ContainerRes{ + Container: co, + Status: getContainerStatus(co.Name, po.Status), + Metrics: cmx, + IsInit: isInit, + Age: po.ObjectMeta.CreationTimestamp, + } +} + +func containerMetrics(n string, mx runtime.Object) (*mv1beta1.ContainerMetrics, error) { + pmx, ok := mx.(*mv1beta1.PodMetrics) + if !ok { + return nil, fmt.Errorf("expecting podmetrics but got `%T", mx) + } + if pmx == nil { + return nil, fmt.Errorf("no metrics for container %s", n) + } + for _, m := range pmx.Containers { + if m.Name == n { + return &m, nil + } + } + return nil, nil +} + +func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { + for _, c := range status.ContainerStatuses { + if c.Name == co { + return &c + } + } + + for _, c := range status.InitContainerStatuses { + if c.Name == co { + return &c + } + } + + return nil +} diff --git a/internal/model/container_test.go b/internal/dao/container_test.go similarity index 74% rename from internal/model/container_test.go rename to internal/dao/container_test.go index 5474a07e..b9b281de 100644 --- a/internal/model/container_test.go +++ b/internal/dao/container_test.go @@ -1,4 +1,4 @@ -package model_test +package dao_test import ( "context" @@ -7,8 +7,6 @@ import ( "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/render" "github.com/derailed/k9s/internal/watch" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" @@ -19,30 +17,15 @@ import ( ) func TestContainerList(t *testing.T) { - c := model.Container{} - c.Init(render.ClusterScope, "containers", makePodFactory()) + c := dao.Container{} + c.Init(makePodFactory(), client.NewGVR("containers")) ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1") - oo, err := c.List(ctx) + oo, err := c.List(ctx, "") assert.Nil(t, err) assert.Equal(t, 1, len(oo)) } -func TestContainerHydrate(t *testing.T) { - c := model.Container{} - c.Init(render.ClusterScope, "containers", makePodFactory()) - - ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1") - oo, err := c.List(ctx) - assert.Nil(t, err) - - rr := make(render.Rows, len(oo)) - assert.Nil(t, c.Hydrate(oo, rr, render.Container{})) - assert.Equal(t, 1, len(rr)) - assert.Equal(t, "fred", rr[0].ID) - assert.Equal(t, render.Fields{"fred", "blee", "false", "Running", "false", "0", "off:off", "n/a", "n/a", "n/a", "n/a", ""}, rr[0].Fields[0:len(rr[0].Fields)-1]) -} - // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/dao/context.go b/internal/dao/context.go index 361ee6e6..1741dbd1 100644 --- a/internal/dao/context.go +++ b/internal/dao/context.go @@ -1,39 +1,40 @@ package dao import ( - "fmt" + "context" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/clientcmd" ) +var ( + _ Accessor = (*Context)(nil) + _ Switchable = (*Context)(nil) +) + // Context represents a kubenetes context. type Context struct { - Generic + NonResource } -var _ Accessor = (*Context)(nil) -var _ Switchable = (*Context)(nil) - func (c *Context) config() *client.Config { return c.Factory.Client().Config() } // Get a Context. -func (c *Context) Get(_, n string) (runtime.Object, error) { - ctx, err := c.config().GetContext(n) +func (c *Context) Get(ctx context.Context, path string) (runtime.Object, error) { + co, err := c.config().GetContext(path) if err != nil { return nil, err } - return &render.NamedContext{Name: n, Context: ctx}, nil + return &render.NamedContext{Name: path, Context: co}, nil } // List all Contexts on the current cluster. -func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) { +func (c *Context) List(_ context.Context, _ string) ([]runtime.Object, error) { ctxs, err := c.config().Contexts() if err != nil { return nil, err @@ -46,19 +47,6 @@ func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) { return cc, nil } -// Delete a Context. -func (c *Context) Delete(path string, cascade, force bool) error { - ctx, err := c.config().CurrentContextName() - if err != nil { - return err - } - if ctx == path { - return fmt.Errorf("trying to delete your current context %s", path) - } - - return c.config().DelContext(path) -} - // MustCurrentContextName return the active context name. func (c *Context) MustCurrentContextName() string { cl, err := c.config().CurrentContextName() @@ -87,36 +75,3 @@ func (c *Context) KubeUpdate(n string) error { clientcmd.NewDefaultPathOptions(), config, true, ) } - -// ---------------------------------------------------------------------------- - -// // NamedContext represents a named cluster context. -// type NamedContext struct { -// Name string -// Context *api.Context -// config *client.Config -// } - -// // NewNamedContext returns a new named context. -// func NewNamedContext(c *client.Config, n string, ctx *api.Context) *NamedContext { -// return &NamedContext{Name: n, Context: ctx, config: c} -// } - -// // MustCurrentContextName return the active context name. -// func (c *NamedContext) MustCurrentContextName() string { -// cl, err := c.config.CurrentContextName() -// if err != nil { -// log.Fatal().Err(err).Msg("Fetching current context") -// } -// return cl -// } - -// // GetObjectKind returns a schema object. -// func (c *NamedContext) GetObjectKind() schema.ObjectKind { -// return nil -// } - -// // DeepCopyObject returns a container copy. -// func (c *NamedContext) DeepCopyObject() runtime.Object { -// return c -// } diff --git a/internal/model/crd.go b/internal/dao/crd.go similarity index 69% rename from internal/model/crd.go rename to internal/dao/crd.go index 7493c9b2..a7b29155 100644 --- a/internal/model/crd.go +++ b/internal/dao/crd.go @@ -1,4 +1,4 @@ -package model +package dao import ( "context" @@ -8,13 +8,18 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*CustomResourceDefinition)(nil) + _ Nuker = (*CustomResourceDefinition)(nil) +) + // CustomResourceDefinition represents a CRD resource model. type CustomResourceDefinition struct { Resource } // List returns a collection of nodes. -func (c *CustomResourceDefinition) List(ctx context.Context) ([]runtime.Object, error) { +func (c *CustomResourceDefinition) List(ctx context.Context, _ string) ([]runtime.Object, error) { strLabel, ok := ctx.Value(internal.KeyLabels).(string) lsel := labels.Everything() if sel, e := labels.ConvertSelectorToLabelsMap(strLabel); ok && e == nil { @@ -22,7 +27,7 @@ func (c *CustomResourceDefinition) List(ctx context.Context) ([]runtime.Object, } const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions" - oo, err := c.factory.List(gvr, "-", true, lsel) + oo, err := c.Factory.List(gvr, "-", true, lsel) if err != nil { return nil, err } diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index d6636fa3..a4b1c370 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -9,14 +9,16 @@ import ( const maxJobNameSize = 42 +var ( + _ Accessor = (*CronJob)(nil) + _ Runnable = (*CronJob)(nil) +) + // CronJob represents a cronjob K8s resource. type CronJob struct { Generic } -var _ Accessor = (*CronJob)(nil) -var _ Runnable = (*CronJob)(nil) - // Run a CronJob. func (c *CronJob) Run(path string) error { ns, n := client.Namespaced(path) @@ -25,6 +27,7 @@ func (c *CronJob) Run(path string) error { return err } + // BOZO!! Factory resource?? cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{}) if err != nil { return err diff --git a/internal/dao/cruiser.go b/internal/dao/cruiser.go new file mode 100644 index 00000000..4709827e --- /dev/null +++ b/internal/dao/cruiser.go @@ -0,0 +1,43 @@ +package dao + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func mustMap(o runtime.Object, field string) map[string]interface{} { + u, ok := o.(*unstructured.Unstructured) + if !ok { + panic("no unstructured") + } + m, ok := u.Object[field].(map[string]interface{}) + if !ok { + panic(fmt.Sprintf("map extract failed for %q", field)) + } + + return m +} + +func mustSlice(o runtime.Object, field string) []interface{} { + u, ok := o.(*unstructured.Unstructured) + if !ok { + return []interface{}{} + } + s, ok := u.Object[field].([]interface{}) + if !ok { + return []interface{}{} + } + + return s +} + +func mustField(o map[string]interface{}, field string) interface{} { + f, ok := o[field] + if !ok { + panic(fmt.Sprintf("no field for %q", field)) + } + + return f +} diff --git a/internal/dao/cruiser_test.go b/internal/dao/cruiser_test.go new file mode 100644 index 00000000..33981345 --- /dev/null +++ b/internal/dao/cruiser_test.go @@ -0,0 +1,40 @@ +package dao + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestCruiserMeta(t *testing.T) { + o := loadJSON(t, "crb") + + m := mustMap(o, "metadata") + assert.Equal(t, "blee", mustField(m, "name")) +} + +func TestCruiserSlice(t *testing.T) { + o := loadJSON(t, "crb") + + s := mustSlice(o, "subjects") + assert.Equal(t, 1, len(s)) + assert.Equal(t, "fernand", mustField(s[0].(map[string]interface{}), "name")) + assert.Equal(t, "User", mustField(s[0].(map[string]interface{}), "kind")) +} + +// Helpers... + +func loadJSON(t assert.TestingT, n string) *unstructured.Unstructured { + raw, err := ioutil.ReadFile(fmt.Sprintf("test_assets/%s.json", n)) + assert.Nil(t, err) + + var o unstructured.Unstructured + err = json.Unmarshal(raw, &o) + assert.Nil(t, err) + + return &o +} diff --git a/internal/dao/describe.go b/internal/dao/describe.go index 6fc54165..79bb4046 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -8,7 +8,8 @@ import ( ) // Describe describes a resource. -func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error) { +func Describe(c client.Connection, gvr client.GVR, path string) (string, error) { + log.Debug().Msgf("DESCRIBE %q::%q", gvr, path) mapper := RestMapper{Connection: c} m, err := mapper.ToRESTMapper() if err != nil { @@ -22,6 +23,10 @@ func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error) return "", err } + ns, n := client.Namespaced(path) + if client.IsClusterScoped(ns) { + ns = client.AllNamespaces + } mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind) if err != nil { log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n) diff --git a/internal/dao/dp.go b/internal/dao/dp.go index e05420ff..96389132 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -15,16 +15,19 @@ import ( "k8s.io/kubectl/pkg/polymorphichelpers" ) +var ( + _ Accessor = (*Deployment)(nil) + _ Nuker = (*Deployment)(nil) + _ Loggable = (*Deployment)(nil) + _ Restartable = (*Deployment)(nil) + _ Scalable = (*Deployment)(nil) +) + // Deployment represents a deployment K8s resource. type Deployment struct { - Generic + Resource } -var _ Accessor = (*Deployment)(nil) -var _ Loggable = (*Deployment)(nil) -var _ Restartable = (*Deployment)(nil) -var _ Scalable = (*Deployment)(nil) - // Scale a Deployment. func (d *Deployment) Scale(path string, replicas int32) error { ns, n := client.Namespaced(path) @@ -45,7 +48,7 @@ func (d *Deployment) Scale(path string, replicas int32) error { // Restart a Deployment rollout. func (d *Deployment) Restart(path string) error { - o, err := d.Get(d.gvr.String(), path, true, labels.Everything()) + o, err := d.Factory.Get(d.gvr.String(), path, true, labels.Everything()) if err != nil { return err } @@ -71,7 +74,7 @@ func (d *Deployment) Restart(path string) error { // TailLogs tail logs for all pods represented by this Deployment. func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything()) + o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 2bf76d67..fb78f9f3 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -20,18 +20,21 @@ import ( "k8s.io/kubectl/pkg/polymorphichelpers" ) +var ( + _ Accessor = (*DaemonSet)(nil) + _ Nuker = (*DaemonSet)(nil) + _ Loggable = (*DaemonSet)(nil) + _ Restartable = (*DaemonSet)(nil) +) + // DaemonSet represents a K8s daemonset. type DaemonSet struct { - Generic + Resource } -var _ Accessor = (*DaemonSet)(nil) -var _ Loggable = (*DaemonSet)(nil) -var _ Restartable = (*DaemonSet)(nil) - // Restart a DaemonSet rollout. func (d *DaemonSet) Restart(path string) error { - o, err := d.Get(d.gvr.String(), path, true, labels.Everything()) + o, err := d.Factory.Get(d.gvr.String(), path, true, labels.Everything()) if err != nil { return err } @@ -56,7 +59,7 @@ func (d *DaemonSet) Restart(path string) error { // TailLogs tail logs for all pods represented by this DaemonSet. func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything()) + o, err := d.Factory.Get(d.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/dao/generic.go b/internal/dao/generic.go index d822bbad..df22b44f 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -1,24 +1,91 @@ package dao import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic" ) +var _ Describer = (*Generic)(nil) + // Generic represents a generic resource. type Generic struct { - Factory - - gvr client.GVR + NonResource } -// Init initializes the resource. -func (g *Generic) Init(f Factory, gvr client.GVR) { - g.Factory, g.gvr = f, gvr +// Describe describes a k8s resource. +func (g *Generic) Describe(path string) (string, error) { + return Describe(g.Client(), g.gvr, path) } -// Delete a Generic. +// ToYAML returns a resource yaml. +func (g *Generic) ToYAML(path string) (string, error) { + o, err := g.Get(context.Background(), path) + if err != nil { + return "", err + } + + raw, err := ToYAML(o) + if err != nil { + return "", fmt.Errorf("unable to marshal resource %s", err) + } + return raw, nil +} + +// List returns a collection of nodes. +func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) { + labelSel, ok := ctx.Value(internal.KeyLabels).(string) + if !ok { + log.Warn().Msgf("No label selector found in context. Listing all resources") + } + + if client.IsAllNamespace(ns) { + ns = client.AllNamespaces + } + + var ( + ll *unstructured.UnstructuredList + err error + ) + if client.IsNamespaced(ns) { + ll, err = g.dynClient().Namespace(ns).List(metav1.ListOptions{LabelSelector: labelSel}) + } else { + ll, err = g.dynClient().List(metav1.ListOptions{LabelSelector: labelSel}) + } + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, len(ll.Items)) + for i := range ll.Items { + oo[i] = &ll.Items[i] + } + + return oo, nil +} + +// List returns a collection of node resources. +func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) { + var opts metav1.GetOptions + + ns, n := client.Namespaced(path) + req := g.dynClient() + if client.IsClusterScoped(ns) { + return req.Get(n, opts) + } + + return req.Namespace(ns).Get(n, opts) +} + +// Delete deletes a resource. func (g *Generic) Delete(path string, cascade, force bool) error { ns, n := client.Namespaced(path) auth, err := g.Client().CanI(ns, g.gvr.String(), []string{"delete"}) @@ -31,10 +98,11 @@ func (g *Generic) Delete(path string, cascade, force bool) error { p = metav1.DeletePropagationBackground } opts := metav1.DeleteOptions{PropagationPolicy: &p} - if ns != "-" { - return g.dynClient().Namespace(ns).Delete(n, &opts) + if client.IsClusterScoped(ns) { + return g.dynClient().Delete(n, &opts) } - return g.dynClient().Delete(n, &opts) + + return g.dynClient().Namespace(ns).Delete(n, &opts) } func (g *Generic) dynClient() dynamic.NamespaceableResourceInterface { diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index 534a1de3..e893e3b2 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -1,12 +1,15 @@ package dao import ( + "bytes" + "errors" "math" "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/rs/zerolog/log" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" ) func toPerc(v1, v2 float64) float64 { @@ -21,12 +24,20 @@ func Truncate(str string, width int) string { return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis)) } -// FetchNodes returns a collection of nodes. -func FetchNodes(f Factory) (*v1.NodeList, error) { - auth, err := f.Client().CanI("", "v1/nodes", []string{"list"}) - if !auth || err != nil { - return nil, err +// ToYAML converts a resource to its YAML representation. +func ToYAML(o runtime.Object) (string, error) { + if o == nil { + return "", errors.New("no object to yamlize") + } + var ( + buff bytes.Buffer + p printers.YAMLPrinter + ) + err := p.PrintObj(o, &buff) + if err != nil { + log.Error().Msgf("Marshal Error %v", err) + return "", err } - return f.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) + return buff.String(), nil } diff --git a/internal/model/hpa.go b/internal/dao/hpa.go similarity index 69% rename from internal/model/hpa.go rename to internal/dao/hpa.go index 31b8e3ed..b71740b8 100644 --- a/internal/model/hpa.go +++ b/internal/dao/hpa.go @@ -1,4 +1,4 @@ -package model +package dao import ( "context" @@ -10,13 +10,18 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*HorizontalPodAutoscaler)(nil) + _ Nuker = (*HorizontalPodAutoscaler)(nil) +) + // HorizontalPodAutoscaler represents a HPA resource model. type HorizontalPodAutoscaler struct { Resource } // List returns a collection of nodes. -func (h *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, error) { +func (h *HorizontalPodAutoscaler) List(ctx context.Context, ns string) ([]runtime.Object, error) { strLabel, ok := ctx.Value(internal.KeyLabels).(string) lsel := labels.Everything() if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { @@ -30,7 +35,7 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, e } for _, gvr := range gvrs { - oo, err := h.list(gvr, lsel) + oo, err := h.list(gvr, ns, lsel) if err == nil && len(oo) > 0 { return oo, nil } @@ -40,8 +45,8 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, e return []runtime.Object{}, nil } -func (h *HorizontalPodAutoscaler) list(gvr string, sel labels.Selector) ([]runtime.Object, error) { - oo, err := h.factory.List(gvr, h.namespace, true, sel) +func (h *HorizontalPodAutoscaler) list(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { + oo, err := h.Factory.List(gvr, ns, true, sel) if err != nil { return nil, err } diff --git a/internal/dao/job.go b/internal/dao/job.go index 6be9cc98..ff96a6d4 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -11,17 +11,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*Job)(nil) + _ Nuker = (*Job)(nil) + _ Loggable = (*Job)(nil) +) + // Job represents a K8s job resource. type Job struct { - Generic + Resource } -var _ Accessor = (*Job)(nil) -var _ Loggable = (*Job)(nil) - // TailLogs tail logs for all pods represented by this Job. func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := j.Get(j.gvr.String(), opts.Path, true, labels.Everything()) + o, err := j.Factory.Get(j.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/model/node.go b/internal/dao/node.go similarity index 69% rename from internal/model/node.go rename to internal/dao/node.go index 567b87f6..d7dc9a5a 100644 --- a/internal/model/node.go +++ b/internal/dao/node.go @@ -1,18 +1,22 @@ -package model +package dao import ( "context" - "fmt" "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "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" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) +var ( + _ Accessor = (*Node)(nil) +) + type NodeMetricsFunc func() (*mv1beta1.NodeMetricsList, error) // Node represents a node model. @@ -21,13 +25,13 @@ type Node struct { } // List returns a collection of node resources. -func (n *Node) List(ctx context.Context) ([]runtime.Object, error) { +func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) { nmx, ok := ctx.Value(internal.KeyMetrics).(*mv1beta1.NodeMetricsList) if !ok { log.Warn().Msgf("No node metrics available in context") } - nn, err := dao.FetchNodes(n.factory) + nn, err := FetchNodes(n.Factory) if err != nil { return nil, err } @@ -47,27 +51,18 @@ func (n *Node) List(ctx context.Context) ([]runtime.Object, error) { return oo, nil } -// Hydrate returns nodes as rows. -func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { - for i, o := range oo { - nmx, ok := o.(*render.NodeWithMetrics) - if !ok { - return fmt.Errorf("expecting *NodeWithMetrics but got %T", o) - } - - var row render.Row - if err := re.Render(nmx, render.ClusterScope, &row); err != nil { - return err - } - rr[i] = row - } - - return nil -} - // ---------------------------------------------------------------------------- // Helpers... +func FetchNodes(f Factory) (*v1.NodeList, error) { + auth, err := f.Client().CanI("", "v1/nodes", []string{"list"}) + if !auth || err != nil { + return nil, err + } + + return f.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) +} + func nodeMetricsFor(fqn string, mmx *mv1beta1.NodeMetricsList) *mv1beta1.NodeMetrics { for _, mx := range mmx.Items { if MetaFQN(mx.ObjectMeta) == fqn { diff --git a/internal/dao/non_resource.go b/internal/dao/non_resource.go new file mode 100644 index 00000000..59e5d547 --- /dev/null +++ b/internal/dao/non_resource.go @@ -0,0 +1,30 @@ +package dao + +import ( + "context" + "fmt" + + "github.com/derailed/k9s/internal/client" + "k8s.io/apimachinery/pkg/runtime" +) + +// NonResource represents a non k8s resource. +type NonResource struct { + Factory + + gvr client.GVR +} + +// Init initializes the resource. +func (g *NonResource) Init(f Factory, gvr client.GVR) { + g.Factory, g.gvr = f, gvr +} + +func (g *NonResource) GVR() string { + return g.gvr.String() +} + +// Get returns the given resource. +func (c *NonResource) Get(context.Context, string) (runtime.Object, error) { + return nil, fmt.Errorf("NYI!") +} diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 12b35eed..85b34071 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -12,24 +12,75 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/color" + "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/watch" "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/labels" "k8s.io/apimachinery/pkg/runtime" restclient "k8s.io/client-go/rest" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) const defaultTimeout = 1 * time.Second +var ( + _ Accessor = (*Pod)(nil) + _ Nuker = (*Pod)(nil) + _ Loggable = (*Pod)(nil) +) + // Pod represents a pod resource. type Pod struct { - Generic + Resource } -var _ Accessor = (*Pod)(nil) -var _ Loggable = (*Pod)(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) + if err != nil { + return oo, err + } + + pmx, ok := ctx.Value(internal.KeyMetrics).(*mv1beta1.PodMetricsList) + if !ok { + log.Warn().Msgf("expecting context PodMetricsList") + } + + sel, ok := ctx.Value(internal.KeyFields).(string) + if !ok { + return oo, nil + } + fsel, err := labels.ConvertSelectorToLabelsMap(sel) + if err != nil { + return nil, err + } + nodeName := fsel["spec.nodeName"] + + var res []runtime.Object + for _, o := range oo { + u, ok := o.(*unstructured.Unstructured) + if !ok { + return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o) + } + if nodeName == "" { + res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)}) + continue + } + + spec, ok := u.Object["spec"].(map[string]interface{}) + if !ok { + return res, fmt.Errorf("expecting interface map but got `%T", o) + } + if spec["nodeName"] == nodeName { + res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)}) + } + } + + return res, nil +} // Logs fetch container logs for a given pod and container. func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) { @@ -45,7 +96,7 @@ func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, er // Containers returns all container names on pod func (p *Pod) Containers(path string, includeInit bool) ([]string, error) { - o, err := p.Get(p.gvr.String(), path, true, labels.Everything()) + o, err := p.Factory.Get(p.gvr.String(), path, true, labels.Everything()) if err != nil { return nil, err } @@ -177,6 +228,59 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L // ---------------------------------------------------------------------------- // Helpers... +func podMetricsFor(o runtime.Object, mmx *mv1beta1.PodMetricsList) *mv1beta1.PodMetrics { + fqn := extractFQN(o) + for _, mx := range mmx.Items { + if MetaFQN(mx.ObjectMeta) == fqn { + return &mx + } + } + return nil +} + +// MetaFQN returns a fully qualified resource name. +func MetaFQN(m metav1.ObjectMeta) string { + if m.Namespace == "" { + return m.Name + } + + 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 "na" + } + m, ok := u.Object["metadata"].(map[string]interface{}) + if !ok { + log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"])) + return "na" + } + + n, ok := m["name"].(string) + if !ok { + log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"])) + return "na" + } + + ns, ok := m["namespace"].(string) + if !ok { + return FQN("", n) + } + + return FQN(ns, n) +} + func loggableContainers(s v1.PodStatus) []string { var rcos []string for _, c := range s.ContainerStatuses { diff --git a/internal/model/portforward.go b/internal/dao/port_forward.go similarity index 64% rename from internal/model/portforward.go rename to internal/dao/port_forward.go index ceeab7ed..efd8f125 100644 --- a/internal/model/portforward.go +++ b/internal/dao/port_forward.go @@ -1,4 +1,4 @@ -package model +package dao import ( "context" @@ -12,21 +12,38 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// PortForward represents a portforward model. +var ( + _ Accessor = (*PortForward)(nil) + _ Nuker = (*PortForward)(nil) +) + +// PortForward represents a port forward dao. type PortForward struct { - Resource + NonResource +} + +// Delete a portforward. +func (p *PortForward) Delete(path string, cascade, force bool) error { + ns, _ := client.Namespaced(path) + auth, err := p.Client().CanI(ns, "v1/pods:portforward", []string{"delete"}) + if !auth || err != nil { + return err + } + p.Factory.DeleteForwarder(path) + + return nil } // List returns a collection of screen dumps. -func (c *PortForward) List(ctx context.Context) ([]runtime.Object, error) { +func (c *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, error) { config, ok := ctx.Value(internal.KeyBenchCfg).(*config.Bench) if !ok { return nil, fmt.Errorf("no benchconfig found in context") } cc := config.Benchmarks.Containers - oo := make([]runtime.Object, 0, len(c.factory.Forwarders())) - for _, f := range c.factory.Forwarders() { + oo := make([]runtime.Object, 0, len(c.Factory.Forwarders())) + for _, f := range c.Factory.Forwarders() { cfg := render.BenchCfg{ C: config.Benchmarks.Defaults.C, N: config.Benchmarks.Defaults.N, diff --git a/internal/dao/portforward.go b/internal/dao/portforward.go deleted file mode 100644 index b3cf6963..00000000 --- a/internal/dao/portforward.go +++ /dev/null @@ -1,25 +0,0 @@ -package dao - -import ( - "github.com/derailed/k9s/internal/client" -) - -// PortForward represents a port forward dao. -type PortForward struct { - Generic -} - -var _ Accessor = (*PortForward)(nil) -var _ Nuker = (*PortForward)(nil) - -// Delete a portforward. -func (p *PortForward) Delete(path string, cascade, force bool) error { - ns, _ := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods:portforward", []string{"delete"}) - if !auth || err != nil { - return err - } - p.Factory.DeleteForwarder(path) - - return nil -} diff --git a/internal/model/rbac.go b/internal/dao/rbac.go similarity index 66% rename from internal/model/rbac.go rename to internal/dao/rbac.go index b29f673c..c0daf77f 100644 --- a/internal/model/rbac.go +++ b/internal/dao/rbac.go @@ -1,4 +1,4 @@ -package model +package dao import ( "context" @@ -20,24 +20,28 @@ const ( rGVR = "rbac.authorization.k8s.io/v1/roles" ) +var ( + _ Accessor = (*Rbac)(nil) + _ Nuker = (*Rbac)(nil) +) + // Rbac represents a model for listing rbac resources. type Rbac struct { Resource } // List lists out rbac resources. -func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) { +func (r *Rbac) List(ctx context.Context, ns string) ([]runtime.Object, error) { gvr, ok := ctx.Value(internal.KeyGVR).(string) if !ok { return nil, fmt.Errorf("expecting a context gvr") } - r.gvr = gvr path, ok := ctx.Value(internal.KeyPath).(string) if !ok || path == "" { - return r.Resource.List(ctx) + return r.Resource.List(ctx, ns) } - res := client.NewGVR(r.gvr) + res := client.NewGVR(gvr) switch res.ToR() { case "clusterrolebindings": return r.loadClusterRoleBinding(path) @@ -53,7 +57,7 @@ func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) { } func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(crbGVR, path, true, labels.Everything()) + o, err := r.Factory.Get(crbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -64,7 +68,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { return nil, err } - crbo, err := r.factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything()) + crbo, err := r.Factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -74,11 +78,11 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { return nil, err } - return asRuntimeObjects(parseRules(render.ClusterScope, "-", cr.Rules)), nil + return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil } func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(rbGVR, path, true, labels.Everything()) + o, err := r.Factory.Get(rbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -89,7 +93,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { } if rb.RoleRef.Kind == "ClusterRole" { - o, e := r.factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything()) + o, e := r.Factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything()) if e != nil { return nil, e } @@ -98,10 +102,10 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { if err != nil { return nil, err } - return asRuntimeObjects(parseRules(render.ClusterScope, "-", cr.Rules)), nil + return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil } - ro, err := r.factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything()) + ro, err := r.Factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -111,11 +115,11 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { return nil, err } - return asRuntimeObjects(parseRules(render.ClusterScope, "-", role.Rules)), nil + return asRuntimeObjects(parseRules(client.ClusterScope, "-", role.Rules)), nil } func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(crGVR, path, true, labels.Everything()) + o, err := r.Factory.Get(crGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -126,11 +130,11 @@ func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { return nil, err } - return asRuntimeObjects(parseRules(render.ClusterScope, "-", cr.Rules)), nil + return asRuntimeObjects(parseRules(client.ClusterScope, "-", cr.Rules)), nil } func (r *Rbac) loadRole(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(rGVR, path, true, labels.Everything()) + o, err := r.Factory.Get(rGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -141,7 +145,7 @@ func (r *Rbac) loadRole(path string) ([]runtime.Object, error) { return nil, err } - return asRuntimeObjects(parseRules(render.ClusterScope, "-", ro.Rules)), nil + return asRuntimeObjects(parseRules(client.ClusterScope, "-", ro.Rules)), nil } func asRuntimeObjects(rr render.Policies) []runtime.Object { @@ -152,3 +156,25 @@ func asRuntimeObjects(rr render.Policies) []runtime.Object { return oo } + +func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies { + pp := make(render.Policies, 0, len(rules)) + for _, rule := range rules { + for _, grp := range rule.APIGroups { + for _, res := range rule.Resources { + for _, na := range rule.ResourceNames { + pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs)) + } + pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(grp, res), grp, rule.Verbs)) + } + } + for _, nres := range rule.NonResourceURLs { + if nres[0] != '/' { + nres = "/" + nres + } + pp = pp.Upsert(render.NewPolicyRes(ns, binding, nres, "n/a", rule.Verbs)) + } + } + + return pp +} diff --git a/internal/model/policy.go b/internal/dao/rbac_policy.go similarity index 72% rename from internal/model/policy.go rename to internal/dao/rbac_policy.go index e0b450d3..e26763af 100644 --- a/internal/model/policy.go +++ b/internal/dao/rbac_policy.go @@ -1,11 +1,11 @@ -package model +package dao import ( "context" "fmt" "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" rbacv1 "k8s.io/api/rbac/v1" @@ -14,17 +14,18 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*Policy)(nil) + _ Nuker = (*Policy)(nil) +) + // Policy represent rbac policy. type Policy struct { Resource } // List returns available policies. -func (p *Policy) List(ctx context.Context) ([]runtime.Object, error) { - gvr, ok := ctx.Value(internal.KeyGVR).(string) - if !ok { - return nil, fmt.Errorf("expecting a context gvr") - } +func (p *Policy) List(ctx context.Context, ns string) ([]runtime.Object, error) { kind, ok := ctx.Value(internal.KeySubjectKind).(string) if !ok { return nil, fmt.Errorf("expecting a context subject kind") @@ -34,7 +35,6 @@ func (p *Policy) List(ctx context.Context) ([]runtime.Object, error) { return nil, fmt.Errorf("expecting a context subject name") } - p.gvr = gvr crps, err := p.loadClusterRoleBinding(kind, name) if err != nil { return nil, err @@ -56,7 +56,7 @@ func (p *Policy) List(ctx context.Context) ([]runtime.Object, error) { } func (p *Policy) loadClusterRoleBinding(kind, name string) (render.Policies, error) { - crbs, err := fetchClusterRoleBindings(p.factory) + crbs, err := fetchClusterRoleBindings(p.Factory) if err != nil { return nil, err } @@ -118,8 +118,8 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) { return rows, nil } -func fetchClusterRoleBindings(f dao.Factory) ([]rbacv1.ClusterRoleBinding, error) { - oo, err := f.List(crbGVR, render.ClusterScope, true, labels.Everything()) +func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) { + oo, err := f.List(crbGVR, client.ClusterScope, false, labels.Everything()) if err != nil { return nil, err } @@ -136,8 +136,8 @@ func fetchClusterRoleBindings(f dao.Factory) ([]rbacv1.ClusterRoleBinding, error return crbs, nil } -func fetchRoleBindings(f dao.Factory) ([]rbacv1.RoleBinding, error) { - oo, err := f.List(rbGVR, render.ClusterScope, true, labels.Everything()) +func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, error) { + oo, err := f.List(rbGVR, client.ClusterScope, false, labels.Everything()) if err != nil { return nil, err } @@ -155,7 +155,7 @@ func fetchRoleBindings(f dao.Factory) ([]rbacv1.RoleBinding, error) { } func (p *Policy) fetchRoleBindingSubjects(kind, name string) ([]string, error) { - rbs, err := fetchRoleBindings(p.factory) + rbs, err := fetchRoleBindings(p.Factory) if err != nil { return nil, err } @@ -174,7 +174,7 @@ func (p *Policy) fetchRoleBindingSubjects(kind, name string) ([]string, error) { func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { const gvr = "rbac.authorization.k8s.io/v1/clusterroles" - oo, err := p.factory.List(gvr, render.ClusterScope, true, labels.Everything()) + oo, err := p.Factory.List(gvr, client.ClusterScope, false, labels.Everything()) if err != nil { return nil, err } @@ -194,7 +194,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.factory.List(gvr, render.AllNamespaces, true, labels.Everything()) + oo, err := p.Factory.List(gvr, client.AllNamespaces, false, labels.Everything()) if err != nil { return nil, err } @@ -210,34 +210,3 @@ func (p *Policy) fetchRoles() ([]rbacv1.Role, error) { return rr, nil } - -func in(nn []string, match string) bool { - for _, n := range nn { - if n == match { - return true - } - } - return false -} - -func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies { - pp := make(render.Policies, 0, len(rules)) - for _, rule := range rules { - for _, grp := range rule.APIGroups { - for _, res := range rule.Resources { - for _, na := range rule.ResourceNames { - pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(res, na), grp, rule.Verbs)) - } - pp = pp.Upsert(render.NewPolicyRes(ns, binding, FQN(grp, res), grp, rule.Verbs)) - } - } - for _, nres := range rule.NonResourceURLs { - if nres[0] != '/' { - nres = "/" + nres - } - pp = pp.Upsert(render.NewPolicyRes(ns, binding, nres, "n/a", rule.Verbs)) - } - } - - return pp -} diff --git a/internal/model/subject.go b/internal/dao/rbac_subject.go similarity index 53% rename from internal/model/subject.go rename to internal/dao/rbac_subject.go index eb4b779c..73f13922 100644 --- a/internal/model/subject.go +++ b/internal/dao/rbac_subject.go @@ -1,4 +1,4 @@ -package model +package dao import ( "context" @@ -9,13 +9,18 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*Subject)(nil) + _ Nuker = (*Subject)(nil) +) + // Subject represents a subject model. type Subject struct { Resource } // List returns a collection of subjects. -func (s *Subject) List(ctx context.Context) ([]runtime.Object, error) { +func (s *Subject) List(ctx context.Context, ns string) ([]runtime.Object, error) { kind, ok := ctx.Value(internal.KeySubjectKind).(string) if !ok { return nil, errors.New("expecting a SubjectKind") @@ -31,22 +36,30 @@ func (s *Subject) List(ctx context.Context) ([]runtime.Object, error) { return nil, err } - return append(crbs, rbs...), nil + for _, rb := range rbs { + crbs = crbs.Upsert(rb) + } + + oo := make([]runtime.Object, len(crbs)) + for i, o := range crbs { + oo[i] = o + } + return oo, nil } -func (s *Subject) listClusterRoleBindings(kind string) ([]runtime.Object, error) { - crbs, err := fetchClusterRoleBindings(s.factory) +func (s *Subject) listClusterRoleBindings(kind string) (render.Subjects, error) { + crbs, err := fetchClusterRoleBindings(s.Factory) if err != nil { return nil, err } - oo := make([]runtime.Object, 0, len(crbs)) + oo := make(render.Subjects, 0, len(crbs)) for _, crb := range crbs { for _, su := range crb.Subjects { - if su.Kind != kind || inSubjectRes(oo, su.Name) { + if su.Kind != kind { continue } - oo = append(oo, render.SubjectRef{ + oo = oo.Upsert(render.SubjectRes{ Name: su.Name, Kind: "ClusterRoleBinding", FirstLocation: crb.Name, @@ -57,19 +70,19 @@ func (s *Subject) listClusterRoleBindings(kind string) ([]runtime.Object, error) return oo, nil } -func (s *Subject) listRoleBindings(kind string) ([]runtime.Object, error) { - rbs, err := fetchRoleBindings(s.factory) +func (s *Subject) listRoleBindings(kind string) (render.Subjects, error) { + rbs, err := fetchRoleBindings(s.Factory) if err != nil { return nil, err } - oo := make([]runtime.Object, 0, len(rbs)) + oo := make(render.Subjects, 0, len(rbs)) for _, rb := range rbs { for _, su := range rb.Subjects { - if su.Kind != kind || inSubjectRes(oo, su.Name) { + if su.Kind != kind { continue } - oo = append(oo, render.SubjectRef{ + oo = oo.Upsert(render.SubjectRes{ Name: su.Name, Kind: "RoleBinding", FirstLocation: rb.Name, @@ -79,19 +92,3 @@ func (s *Subject) listRoleBindings(kind string) ([]runtime.Object, error) { return oo, nil } - -// ---------------------------------------------------------------------------- -// Helpers... - -func inSubjectRes(oo []runtime.Object, match string) bool { - for _, o := range oo { - res, ok := o.(render.SubjectRef) - if !ok { - continue - } - if res.Name == match { - return true - } - } - return false -} diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 390d50e6..aae5b837 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -12,12 +12,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// ResourceMetas represents a collection of resource metadata. -type ResourceMetas map[client.GVR]metav1.APIResource - -// Accessors represents a collection of dao accessors. -type Accessors map[client.GVR]Accessor - var resMetas = ResourceMetas{} // AccessorFor returns a client accessor for a resource if registered. @@ -38,6 +32,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { client.NewGVR("apps/v1/statefulsets"): &StatefulSet{}, client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, client.NewGVR("batch/v1/jobs"): &Job{}, + client.NewGVR("charts"): &Chart{}, } r, ok := m[gvr] @@ -75,6 +70,17 @@ func MetaFor(gvr client.GVR) (metav1.APIResource, error) { return m, nil } +// IsK8sMeta checks for non resource meta. +func IsK8sMeta(m metav1.APIResource) bool { + for _, c := range m.Categories { + if c == "k9s" || c == "helm" { + return false + } + } + + return true +} + // IsK9sMeta checks for non resource meta. func IsK9sMeta(m metav1.APIResource) bool { for _, c := range m.Categories { @@ -100,6 +106,12 @@ func LoadResources(f Factory) error { // BOZO!! Need contermeasure for direct commands! func loadNonResource(m ResourceMetas) { + loadK9s(m) + loadRBAC(m) + loadHelm(m) +} + +func loadK9s(m ResourceMetas) { m[client.NewGVR("aliases")] = metav1.APIResource{ Name: "aliases", Kind: "Aliases", @@ -138,8 +150,16 @@ func loadNonResource(m ResourceMetas) { Kind: "Containers", Categories: []string{"k9s"}, } +} - loadRBAC(m) +func loadHelm(m ResourceMetas) { + m[client.NewGVR("charts")] = metav1.APIResource{ + Name: "charts", + Kind: "Charts", + Namespaced: true, + Verbs: []string{"delete"}, + Categories: []string{"helm"}, + } } func loadRBAC(m ResourceMetas) { diff --git a/internal/dao/resource.go b/internal/dao/resource.go new file mode 100644 index 00000000..0736c9f2 --- /dev/null +++ b/internal/dao/resource.go @@ -0,0 +1,51 @@ +package dao + +import ( + "context" + "fmt" + + "github.com/derailed/k9s/internal" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*Resource)(nil) + _ Describer = (*Resource)(nil) + _ Nuker = (*Resource)(nil) +) + +// Resource represents an informer based resource. +type Resource struct { + Generic +} + +// ToYAML returns a resource yaml. +func (r *Resource) ToYAML(path string) (string, error) { + o, err := r.Get(context.Background(), path) + if err != nil { + return "", err + } + + raw, err := ToYAML(o) + if err != nil { + return "", fmt.Errorf("unable to marshal resource %s", err) + } + return raw, nil +} + +// Get returns a resource instance if found, else an error. +func (r *Resource) Get(ctx context.Context, path string) (runtime.Object, error) { + return r.Factory.Get(r.gvr.String(), path, true, labels.Everything()) +} + +// List returns a collection of nodes. +func (r *Resource) List(ctx context.Context, ns string) ([]runtime.Object, error) { + strLabel, ok := ctx.Value(internal.KeyLabels).(string) + lsel := labels.Everything() + if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { + lsel = sel.AsSelector() + } + + return r.Factory.List(r.gvr.String(), ns, true, lsel) +} diff --git a/internal/dao/screen_dump.go b/internal/dao/screen_dump.go index 9a7e85a1..51bdf11d 100644 --- a/internal/dao/screen_dump.go +++ b/internal/dao/screen_dump.go @@ -1,18 +1,47 @@ package dao import ( + "context" + "errors" + "io/ioutil" "os" + + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/render" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + _ Accessor = (*ScreenDump)(nil) + _ Nuker = (*ScreenDump)(nil) ) // ScreenDump represents a scraped resources. type ScreenDump struct { - Generic + NonResource } -var _ Accessor = (*ScreenDump)(nil) -var _ Nuker = (*ScreenDump)(nil) - // Delete a ScreenDump. func (d *ScreenDump) Delete(path string, cascade, force bool) error { return os.Remove(path) } + +// List returns a collection of screen dumps. +func (c *ScreenDump) List(ctx context.Context, _ string) ([]runtime.Object, error) { + dir, ok := ctx.Value(internal.KeyDir).(string) + if !ok { + return nil, errors.New("no screendump dir found in context") + } + + ff, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + oo := make([]runtime.Object, len(ff)) + for i, f := range ff { + oo[i] = render.FileRes{File: f, Dir: dir} + } + + return oo, nil +} diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 882542d4..88552a8b 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -15,16 +15,19 @@ import ( "k8s.io/kubectl/pkg/polymorphichelpers" ) +var ( + _ Accessor = (*StatefulSet)(nil) + _ Nuker = (*StatefulSet)(nil) + _ Loggable = (*StatefulSet)(nil) + _ Restartable = (*StatefulSet)(nil) + _ Scalable = (*StatefulSet)(nil) +) + // StatefulSet represents a K8s sts. type StatefulSet struct { - Generic + Resource } -var _ Accessor = (*StatefulSet)(nil) -var _ Loggable = (*StatefulSet)(nil) -var _ Restartable = (*StatefulSet)(nil) -var _ Scalable = (*StatefulSet)(nil) - // Scale a StatefulSet. func (s *StatefulSet) Scale(path string, replicas int32) error { ns, n := client.Namespaced(path) @@ -44,7 +47,7 @@ func (s *StatefulSet) Scale(path string, replicas int32) error { // Restart a StatefulSet rollout. func (s *StatefulSet) Restart(path string) error { - o, err := s.Get(s.gvr.String(), path, true, labels.Everything()) + o, err := s.Factory.Get(s.gvr.String(), path, true, labels.Everything()) if err != nil { return err } @@ -70,7 +73,7 @@ func (s *StatefulSet) Restart(path string) error { // TailLogs tail logs for all pods represented by this StatefulSet. func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything()) + o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/dao/svc.go b/internal/dao/svc.go index d39cd68d..25b82e0f 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -11,17 +11,19 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +var ( + _ Accessor = (*Service)(nil) + _ Loggable = (*Service)(nil) +) + // Service represents a k8s service. type Service struct { - Generic + Resource } -var _ Accessor = (*Service)(nil) -var _ Loggable = (*Service)(nil) - // TailLogs tail logs for all pods represented by this Service. func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything()) + o, err := s.Factory.Get(s.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/model/test_assets/bench/default_fred_1577308050814961000.txt b/internal/dao/test_assets/bench/default_fred_1577308050814961000.txt similarity index 100% rename from internal/model/test_assets/bench/default_fred_1577308050814961000.txt rename to internal/dao/test_assets/bench/default_fred_1577308050814961000.txt diff --git a/internal/dao/test_assets/crb.json b/internal/dao/test_assets/crb.json new file mode 100644 index 00000000..297a1a8b --- /dev/null +++ b/internal/dao/test_assets/crb.json @@ -0,0 +1,26 @@ +{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"annotations\":{},\"name\":\"blee\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"blee\"},\"subjects\":[{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"User\",\"name\":\"fernand\"}]}\n" + }, + "creationTimestamp": "2019-06-04T16:48:35Z", + "name": "blee", + "resourceVersion": "26689100", + "selfLink": "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/blee", + "uid": "97e5f84d-86e8-11e9-a8e8-42010a80015b" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "blee" + }, + "subjects": [ + { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "User", + "name": "fernand" + } + ] +} \ No newline at end of file diff --git a/internal/model/test_assets/n1.json b/internal/dao/test_assets/n1.json similarity index 100% rename from internal/model/test_assets/n1.json rename to internal/dao/test_assets/n1.json diff --git a/internal/model/test_assets/p1.json b/internal/dao/test_assets/p1.json similarity index 100% rename from internal/model/test_assets/p1.json rename to internal/dao/test_assets/p1.json diff --git a/internal/dao/types.go b/internal/dao/types.go index fb41099d..d8c78f45 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -6,12 +6,19 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/watch" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/informers" restclient "k8s.io/client-go/rest" ) +// ResourceMetas represents a collection of resource metadata. +type ResourceMetas map[client.GVR]metav1.APIResource + +// Accessors represents a collection of dao accessors. +type Accessors map[client.GVR]Accessor + // Factory represents a resource factory. type Factory interface { // Client retrieves an api client. @@ -39,12 +46,27 @@ type Factory interface { Forwarders() watch.Forwarders } +// Getter represents a resource getter. +type Getter interface { + // Get return a given resource. + Get(ctx context.Context, path string) (runtime.Object, error) +} + +// Lister represents a resource lister. +type Lister interface { + // List returns a resource collection. + List(ctx context.Context, ns string) ([]runtime.Object, error) +} + // Accessor represents an accessible k8s resource. type Accessor interface { - Nuker + Lister + Getter // Init the resource with a factory object. Init(Factory, client.GVR) + + GVR() string } // Loggable represents resources with logs. @@ -53,6 +75,15 @@ type Loggable interface { TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error } +// Describer describes a resource. +type Describer interface { + // Describe describes a resource. + Describe(path string) (string, error) + + // ToYAML dumps a resource to YAML. + ToYAML(path string) (string, error) +} + // Scalable represents resources that can scale. type Scalable interface { // Scale scales a resource up or down. diff --git a/internal/model/alias.go b/internal/model/alias.go deleted file mode 100644 index a417621b..00000000 --- a/internal/model/alias.go +++ /dev/null @@ -1,43 +0,0 @@ -package model - -import ( - "context" - "errors" - "sort" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/config" - "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" - "k8s.io/apimachinery/pkg/runtime" -) - -// Alias represents a collection of aliases. -type Alias struct { - Resource -} - -// List returns a collection of screen dumps. -func (b *Alias) List(ctx context.Context) ([]runtime.Object, error) { - a, ok := ctx.Value(internal.KeyAliases).(*dao.Alias) - if !ok { - return nil, errors.New("no aliases found in context") - } - - m := make(config.ShortNames, len(a.Alias)) - for alias, gvr := range a.Alias { - if _, ok := m[gvr]; ok { - m[gvr] = append(m[gvr], alias) - } else { - m[gvr] = []string{alias} - } - } - - 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}) - } - - return oo, nil -} diff --git a/internal/model/benchmark.go b/internal/model/benchmark.go deleted file mode 100644 index 9b923254..00000000 --- a/internal/model/benchmark.go +++ /dev/null @@ -1,37 +0,0 @@ -package model - -import ( - "context" - "errors" - "io/ioutil" - "path/filepath" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/render" - "k8s.io/apimachinery/pkg/runtime" -) - -// Benchmark represents a collection of benchmarks. -type Benchmark struct { - Resource -} - -// List returns a collection of screen dumps. -func (b *Benchmark) List(ctx context.Context) ([]runtime.Object, error) { - dir, ok := ctx.Value(internal.KeyDir).(string) - if !ok { - return nil, errors.New("no benchmark dir found in context") - } - - ff, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - oo := make([]runtime.Object, len(ff)) - for i, f := range ff { - oo[i] = render.BenchInfo{File: f, Path: filepath.Join(dir, f.Name())} - } - - return oo, nil -} diff --git a/internal/model/benchmark_test.go b/internal/model/benchmark_test.go deleted file mode 100644 index 9646d210..00000000 --- a/internal/model/benchmark_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package model_test - -import ( - "context" - "testing" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" -) - -func TestBenchmarkList(t *testing.T) { - a := model.Benchmark{} - a.Init(render.ClusterScope, "benchmarks", makeFactory()) - - ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench") - oo, err := a.List(ctx) - - assert.Nil(t, err) - assert.Equal(t, 1, len(oo)) - assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path) -} - -func TestBenchmarkHydrate(t *testing.T) { - a := model.Benchmark{} - a.Init(render.ClusterScope, "benchmarks", makeFactory()) - - ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench") - oo, err := a.List(ctx) - assert.Nil(t, err) - - rr := make(render.Rows, len(oo)) - assert.Nil(t, a.Hydrate(oo, rr, render.Benchmark{})) - assert.Equal(t, 1, len(rr)) - assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", rr[0].ID) - assert.Equal(t, render.Fields{ - "default", - "fred", - "fail", - "816.6403", - "0.0122", - "0", - "0", - "default_fred_1577308050814961000.txt", - }, - rr[0].Fields[:len(rr[0].Fields)-1], - ) -} diff --git a/internal/model/container.go b/internal/model/container.go deleted file mode 100644 index 5e10f306..00000000 --- a/internal/model/container.go +++ /dev/null @@ -1,114 +0,0 @@ -package model - -import ( - "context" - "fmt" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/render" - "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" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -// Container represents a container model. -type Container struct { - Resource - - pod *v1.Pod -} - -// List returns a collection of containers -func (c *Container) List(ctx context.Context) ([]runtime.Object, error) { - c.pod = nil - path, ok := ctx.Value(internal.KeyPath).(string) - if !ok { - return nil, fmt.Errorf("no context path for %q", c.gvr) - } - ns, _ := render.Namespaced(path) - c.namespace = ns - o, err := c.factory.Get("v1/pods", path, true, labels.Everything()) - if err != nil { - return nil, err - } - - var po v1.Pod - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) - if err != nil { - return nil, err - } - c.pod = &po - res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) - mx := client.NewMetricsServer(c.factory.Client()) - var pmx *mv1beta1.PodMetrics - if c.factory.Client() != nil { - var err error - pmx, err = mx.FetchPodMetrics(c.namespace, c.pod.Name) - if err != nil { - log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", c.namespace, c.pod.Name) - } - } - - for _, co := range po.Spec.InitContainers { - res = append(res, makeContainerRes(co, po, pmx, true)) - } - for _, co := range po.Spec.Containers { - res = append(res, makeContainerRes(co, po, pmx, false)) - } - - return res, nil -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func makeContainerRes(co v1.Container, po v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes { - cmx, err := containerMetrics(co.Name, pmx) - if err != nil { - log.Warn().Err(err).Msgf("Container metrics for %s", co.Name) - } - - return render.ContainerRes{ - Container: co, - Status: getContainerStatus(co.Name, po.Status), - Metrics: cmx, - IsInit: isInit, - Age: po.ObjectMeta.CreationTimestamp, - } -} - -func containerMetrics(n string, mx runtime.Object) (*mv1beta1.ContainerMetrics, error) { - pmx, ok := mx.(*mv1beta1.PodMetrics) - if !ok { - return nil, fmt.Errorf("expecting podmetrics but got `%T", mx) - } - if pmx == nil { - return nil, fmt.Errorf("no metrics for container %s", n) - } - for _, m := range pmx.Containers { - if m.Name == n { - return &m, nil - } - } - return nil, nil -} - -func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { - for _, c := range status.ContainerStatuses { - if c.Name == co { - return &c - } - } - - for _, c := range status.InitContainerStatuses { - if c.Name == co { - return &c - } - } - - return nil -} diff --git a/internal/model/context.go b/internal/model/context.go deleted file mode 100644 index c92a4357..00000000 --- a/internal/model/context.go +++ /dev/null @@ -1,28 +0,0 @@ -package model - -import ( - "context" - - "github.com/derailed/k9s/internal/render" - "k8s.io/apimachinery/pkg/runtime" -) - -// Context represents a kube context model. -type Context struct { - Resource -} - -// List returns a collection of node resources. -func (c *Context) List(_ context.Context) ([]runtime.Object, error) { - cfg := c.factory.Client().Config() - ctxs, err := cfg.Contexts() - if err != nil { - return nil, err - } - cc := make([]runtime.Object, 0, len(ctxs)) - for name, ctx := range ctxs { - cc = append(cc, render.NewNamedContext(cfg, name, ctx)) - } - - return cc, nil -} diff --git a/internal/model/generic.go b/internal/model/generic.go deleted file mode 100644 index 972d7bfb..00000000 --- a/internal/model/generic.go +++ /dev/null @@ -1,150 +0,0 @@ -package model - -import ( - "context" - "fmt" - - "github.com/derailed/k9s/internal/client" - "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" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/client-go/rest" -) - -const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" - -// Generic represents a generic model. -type Generic struct { - Resource - - table *metav1beta1.Table -} - -// List returns a collection of node resources. -func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) { - var opts metav1.GetOptions - - ns, n := client.Namespaced(path) - gvr := client.NewGVR(g.gvr) - req := g.factory.Client().DynDialOrDie().Resource(gvr.AsGVR()) - if ns == "" { - return req.Get(n, opts) - } - - return req.Namespace(ns).Get(n, opts) -} - -// List returns a collection of node resources. -func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) { - // Ensures the factory is tracking this resource - _, err := g.factory.CanForResource(g.namespace, g.gvr, []string{"list"}) - if err != nil { - return nil, err - } - - gvr := client.NewGVR(g.gvr) - fcodec, codec := g.codec(gvr.AsGV()) - - c, err := g.client(fcodec, gvr) - if err != nil { - return nil, err - } - - ns := g.namespace - if g.namespace == render.ClusterScope { - ns = render.AllNamespaces - } - - log.Debug().Msgf("GENERIC LIST %q:%q", g.namespace, g.gvr) - o, err := c.Get(). - SetHeader("Accept", fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)). - Resource(gvr.ToR()). - VersionedParams(&metav1beta1.TableOptions{}, codec). - Namespace(ns). - Do(). - Get() - if err != nil { - return nil, err - } - table, ok := o.(*metav1beta1.Table) - if !ok { - return nil, fmt.Errorf("expecting table but got %T", o) - } - g.table = table - res := make([]runtime.Object, len(g.table.Rows)) - for i := range g.table.Rows { - res[i] = RowRes{&g.table.Rows[i]} - } - - return res, err -} - -// Hydrate returns nodes as rows. -func (g *Generic) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { - gr, ok := re.(*render.Generic) - if !ok { - return fmt.Errorf("expecting generic renderer for %s but got %T", g.gvr, re) - } - gr.SetTable(g.table) - for i, o := range oo { - res, ok := o.(RowRes) - if !ok { - return fmt.Errorf("expecting RowRes but got %#v", o) - } - if err := gr.Render(res.TableRow, g.namespace, &rr[i]); err != nil { - return err - } - } - - return nil -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func (g *Generic) client(codec serializer.CodecFactory, gvr client.GVR) (*rest.RESTClient, error) { - crConfig := g.factory.Client().RestConfigOrDie() - gv := gvr.AsGV() - crConfig.GroupVersion = &gv - crConfig.APIPath = "/apis" - if len(gvr.ToG()) == 0 { - crConfig.APIPath = "/api" - } - crConfig.NegotiatedSerializer = codec.WithoutConversion() - - crRestClient, err := rest.RESTClientFor(crConfig) - if err != nil { - return nil, err - } - return crRestClient, nil -} - -func (r *Resource) codec(gv schema.GroupVersion) (serializer.CodecFactory, runtime.ParameterCodec) { - scheme := runtime.NewScheme() - metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - - return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) -} - -// ---------------------------------------------------------------------------- - -// RowRes represents a table row. -type RowRes struct { - *metav1beta1.TableRow -} - -// GetObjectKind returns a schema object. -func (r RowRes) GetObjectKind() schema.ObjectKind { - return nil -} - -// DeepCopyObject returns a container copy. -func (r RowRes) DeepCopyObject() runtime.Object { - return r -} diff --git a/internal/model/helpers.go b/internal/model/helpers.go index 401ae815..3f4269ac 100644 --- a/internal/model/helpers.go +++ b/internal/model/helpers.go @@ -1,42 +1,11 @@ package model import ( - "fmt" - "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" ) -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 "na" - } - m, ok := u.Object["metadata"].(map[string]interface{}) - if !ok { - log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"])) - return "na" - } - - n, ok := m["name"].(string) - if !ok { - log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"])) - return "na" - } - - ns, ok := m["namespace"].(string) - if !ok { - return FQN("", n) - } - - return FQN(ns, n) -} - // MetaFQN returns a fully qualified resource name. func MetaFQN(m metav1.ObjectMeta) string { if m.Namespace == "" { diff --git a/internal/model/job.go b/internal/model/job.go deleted file mode 100644 index bbde5a77..00000000 --- a/internal/model/job.go +++ /dev/null @@ -1,69 +0,0 @@ -package model - -import ( - "context" - "errors" - "strings" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" - batchv1 "k8s.io/api/batch/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" -) - -// Job represents a collections of jobs. -type Job struct { - Resource -} - -// List returns a collection of screen dumps. -func (c *Job) List(ctx context.Context) ([]runtime.Object, error) { - uid, ok := ctx.Value(internal.KeyUID).(string) - if !ok { - log.Debug().Msgf("NO UID in context") - } - path, ok := ctx.Value(internal.KeyPath).(string) - if !ok { - return nil, errors.New("no cronjob path found in context") - } - - log.Debug().Msgf("Listing jobs %q %q--%q", c.gvr, uid, path) - oo, err := c.Resource.List(ctx) - if err != nil { - return nil, err - } - if uid == "" { - return oo, nil - } - - _, cronName := client.Namespaced(path) - jj := make([]runtime.Object, 0, len(oo)) - for _, j := range oo { - var job batchv1.Job - err = runtime.DefaultUnstructuredConverter.FromUnstructured(j.(*unstructured.Unstructured).Object, &job) - if err != nil { - return nil, err - } - log.Debug().Msgf("Looking at job %q -- %q", job.Name, cronName) - if !isNamedAfter(cronName, job.Name) { - continue - } - log.Debug().Msgf("GOT Job %s -- %#v -- %q -- %q", job.Name, job.Labels, uid, path) - jj = append(jj, j) - } - - return jj, nil -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func isNamedAfter(p, n string) bool { - tokens := strings.Split(n, "-") - if len(tokens) == 0 || tokens[0] != p { - return false - } - return true -} diff --git a/internal/model/node_test.go b/internal/model/node_test.go deleted file mode 100644 index d20b1a25..00000000 --- a/internal/model/node_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package model_test - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "testing" - - "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestNodeHydrate(t *testing.T) { - f := makeFactory() - var no model.Node - no.Init("", "v1/nodes", f) - - o := render.NodeWithMetrics{Raw: load(t, "n1")} - rr := make(render.Rows, 1) - assert.Nil(t, no.Hydrate([]runtime.Object{&o}, rr, render.Node{})) - assert.Equal(t, 1, len(rr)) - assert.Equal(t, "minikube", rr[0].ID) - assert.Equal(t, render.Fields{ - "minikube", - "Ready", - "master", - "v1.17.0", - "4.19.81", - "192.168.64.6", - "", - "n/a", - "n/a", - "n/a", - "n/a", - "n/a", - "n/a", - }, rr[0].Fields[:len(rr[0].Fields)-1]) -} - -func BenchmarkNodeHydrate(b *testing.B) { - f := makeFactory() - var no model.Node - no.Init("", "v1/nodes", f) - o := load(b, "n1") - rr := make(render.Rows, 1) - - oo := []runtime.Object{&render.NodeWithMetrics{Raw: o}} - re := render.Node{} - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - no.Hydrate(oo, rr, re) - } -} - -// Helpers... - -func load(t assert.TestingT, n string) *unstructured.Unstructured { - raw, err := ioutil.ReadFile(fmt.Sprintf("test_assets/%s.json", n)) - assert.Nil(t, err) - - var o unstructured.Unstructured - err = json.Unmarshal(raw, &o) - assert.Nil(t, err) - - return &o -} diff --git a/internal/model/pod.go b/internal/model/pod.go deleted file mode 100644 index 052c63b8..00000000 --- a/internal/model/pod.go +++ /dev/null @@ -1,96 +0,0 @@ -package model - -import ( - "context" - "fmt" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" -) - -// Pod represents a pod model. -type Pod struct { - Resource -} - -// List returns a collection of nodes. -func (p *Pod) List(ctx context.Context) ([]runtime.Object, error) { - oo, err := p.Resource.List(ctx) - if err != nil { - return oo, err - } - - pmx, ok := ctx.Value(internal.KeyMetrics).(*mv1beta1.PodMetricsList) - if !ok { - log.Warn().Msgf("expecting context PodMetricsList") - } - - sel, ok := ctx.Value(internal.KeyFields).(string) - if !ok { - return oo, nil - } - fsel, err := labels.ConvertSelectorToLabelsMap(sel) - if err != nil { - return nil, err - } - nodeName := fsel["spec.nodeName"] - - var res []runtime.Object - for _, o := range oo { - u, ok := o.(*unstructured.Unstructured) - if !ok { - return res, fmt.Errorf("expecting *unstructured.Unstructured but got `%T", o) - } - if nodeName == "" { - res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)}) - continue - } - - spec, ok := u.Object["spec"].(map[string]interface{}) - if !ok { - return res, fmt.Errorf("expecting interface map but got `%T", o) - } - if spec["nodeName"] == nodeName { - res = append(res, &render.PodWithMetrics{Raw: u, MX: podMetricsFor(o, pmx)}) - } - } - - return res, nil -} - -// Hydrate returns pod resources as rows. -func (p *Pod) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { - var index int - for _, o := range oo { - po, ok := o.(*render.PodWithMetrics) - if !ok { - return fmt.Errorf("expecting *PodWithMetric but got %T", po) - } - var row render.Row - if err := re.Render(po, p.namespace, &row); err != nil { - return err - } - rr[index] = row - index++ - } - - return nil -} - -// ---------------------------------------------------------------------------- -// Helpers... - -func podMetricsFor(o runtime.Object, mmx *mv1beta1.PodMetricsList) *mv1beta1.PodMetrics { - fqn := extractFQN(o) - for _, mx := range mmx.Items { - if MetaFQN(mx.ObjectMeta) == fqn { - return &mx - } - } - return nil -} diff --git a/internal/model/pod_test.go b/internal/model/pod_test.go deleted file mode 100644 index 6f080d9d..00000000 --- a/internal/model/pod_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package model_test - -import ( - "testing" - - "github.com/derailed/k9s/internal/model" - "github.com/derailed/k9s/internal/render" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestPodHydrate(t *testing.T) { - f := makeFactory() - var po model.Pod - po.Init("", "v1/pods", f) - - o := render.PodWithMetrics{Raw: load(t, "p1")} - rr := make(render.Rows, 1) - assert.Nil(t, po.Hydrate([]runtime.Object{&o}, rr, render.Pod{})) - assert.Equal(t, 1, len(rr)) - assert.Equal(t, "default/nginx-7fb78fb6d8-2w75j", rr[0].ID) - assert.Equal(t, render.Fields{ - "default", - "nginx-7fb78fb6d8-2w75j", - "1/1", - "Running", - "0", - "n/a", - "n/a", - "n/a", - "n/a", - "10.44.0.229", - "gke-k9s-default-pool-0fa2fb89-lbtf", - "GA", - }, rr[0].Fields[:len(rr[0].Fields)-1]) -} - -func BenchmarkPodHydrate(b *testing.B) { - f := makeFactory() - var po model.Pod - po.Init("", "v1/pods", f) - o := load(b, "p1") - rr := make(render.Rows, 1) - - oo := []runtime.Object{&render.PodWithMetrics{Raw: o}} - re := render.Pod{} - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - po.Hydrate(oo, rr, re) - } -} diff --git a/internal/model/registry.go b/internal/model/registry.go index 958a911b..90d82a27 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -1,6 +1,7 @@ package model import ( + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" ) @@ -8,44 +9,48 @@ import ( // BOZO!! Break up deps and merge into single registrar var Registry = map[string]ResourceMeta{ // Custom... + "charts": { + DAO: &dao.Chart{}, + Renderer: &render.Chart{}, + }, "containers": { - Model: &Container{}, + DAO: &dao.Container{}, Renderer: &render.Container{}, }, "contexts": { - Model: &Context{}, + DAO: &dao.Context{}, Renderer: &render.Context{}, }, "screendumps": { - Model: &ScreenDump{}, + DAO: &dao.ScreenDump{}, Renderer: &render.ScreenDump{}, }, "rbac": { - Model: &Rbac{}, + DAO: &dao.Rbac{}, Renderer: &render.Rbac{}, }, "policy": { - Model: &Policy{}, + DAO: &dao.Policy{}, Renderer: &render.Policy{}, }, "users": { - Model: &Subject{}, + DAO: &dao.Subject{}, Renderer: &render.Subject{}, }, "groups": { - Model: &Subject{}, + DAO: &dao.Subject{}, Renderer: &render.Subject{}, }, "portforwards": { - Model: &PortForward{}, + DAO: &dao.PortForward{}, Renderer: &render.PortForward{}, }, "benchmarks": { - Model: &Benchmark{}, + DAO: &dao.Benchmark{}, Renderer: &render.Benchmark{}, }, "aliases": { - Model: &Alias{}, + DAO: &dao.Alias{}, Renderer: &render.Alias{}, }, @@ -57,14 +62,14 @@ var Registry = map[string]ResourceMeta{ Renderer: &render.Event{}, }, "v1/pods": { - Model: &Pod{}, + DAO: &dao.Pod{}, Renderer: &render.Pod{}, }, "v1/namespaces": { Renderer: &render.Namespace{}, }, "v1/nodes": { - Model: &Node{}, + DAO: &dao.Node{}, Renderer: &render.Node{}, }, "v1/services": { @@ -73,6 +78,12 @@ var Registry = map[string]ResourceMeta{ "v1/serviceaccounts": { Renderer: &render.ServiceAccount{}, }, + "v1/persistentvolumes": { + Renderer: &render.PersistentVolume{}, + }, + "v1/persistentvolumeclaims": { + Renderer: &render.PersistentVolumeClaim{}, + }, // Apps... "apps/v1/deployments": { @@ -98,37 +109,40 @@ var Registry = map[string]ResourceMeta{ "extensions/v1beta1/networkpolicies": { Renderer: &render.NetworkPolicy{}, }, + "networking.k8s.io/v1/networkpolicies": { + Renderer: &render.NetworkPolicy{}, + }, // Batch... "batch/v1beta1/cronjobs": { Renderer: &render.CronJob{}, }, "batch/v1/jobs": { - Model: &Job{}, + DAO: &dao.Job{}, Renderer: &render.Job{}, }, // Autoscaling... "autoscaling/v1/horizontalpodautoscalers": { - Model: &HorizontalPodAutoscaler{}, + DAO: &dao.HorizontalPodAutoscaler{}, Renderer: &render.HorizontalPodAutoscaler{}, }, "autoscaling/v2beta1/horizontalpodautoscalers": { - Model: &HorizontalPodAutoscaler{}, + DAO: &dao.HorizontalPodAutoscaler{}, Renderer: &render.HorizontalPodAutoscaler{}, }, "autoscaling/v2beta2/horizontalpodautoscalers": { - Model: &HorizontalPodAutoscaler{}, + DAO: &dao.HorizontalPodAutoscaler{}, Renderer: &render.HorizontalPodAutoscaler{}, }, // CRDs... "apiextensions.k8s.io/v1/customresourcedefinitions": { - Model: &CustomResourceDefinition{}, + DAO: &dao.CustomResourceDefinition{}, Renderer: &render.CustomResourceDefinition{}, }, "apiextensions.k8s.io/v1beta1/customresourcedefinitions": { - Model: &CustomResourceDefinition{}, + DAO: &dao.CustomResourceDefinition{}, Renderer: &render.CustomResourceDefinition{}, }, @@ -144,7 +158,7 @@ var Registry = map[string]ResourceMeta{ // RBAC... "rbac.authorization.k8s.io/v1/clusterroles": { - Model: &Rbac{}, + DAO: &dao.Rbac{}, Renderer: &render.ClusterRole{}, }, "rbac.authorization.k8s.io/v1/clusterrolebindings": { diff --git a/internal/model/resource.go b/internal/model/resource.go deleted file mode 100644 index b32af0dd..00000000 --- a/internal/model/resource.go +++ /dev/null @@ -1,49 +0,0 @@ -package model - -import ( - "context" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" -) - -// Resource represents a generic resource model. -type Resource struct { - namespace, gvr string - factory dao.Factory -} - -// Init initializes the model. -func (r *Resource) Init(ns, gvr string, f dao.Factory) { - r.namespace, r.gvr, r.factory = ns, gvr, f -} - -// Get returns a resource instance if found, else an error. -func (r *Resource) Get(ctx context.Context, path string) (runtime.Object, error) { - return r.factory.Get(r.gvr, path, true, labels.Everything()) -} - -// List returns a collection of nodes. -func (r *Resource) List(ctx context.Context) ([]runtime.Object, error) { - strLabel, ok := ctx.Value(internal.KeyLabels).(string) - lsel := labels.Everything() - if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { - lsel = sel.AsSelector() - } - - return r.factory.List(r.gvr, r.namespace, true, lsel) -} - -// Hydrate renders all rows. -func (r *Resource) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { - for i, o := range oo { - if err := re.Render(o, r.namespace, &rr[i]); err != nil { - return err - } - } - - return nil -} diff --git a/internal/model/screen_dump.go b/internal/model/screen_dump.go deleted file mode 100644 index 2d445ce1..00000000 --- a/internal/model/screen_dump.go +++ /dev/null @@ -1,36 +0,0 @@ -package model - -import ( - "context" - "errors" - "io/ioutil" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/render" - "k8s.io/apimachinery/pkg/runtime" -) - -// ScreenDump represents a collections of screendumps. -type ScreenDump struct { - Resource -} - -// List returns a collection of screen dumps. -func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) { - dir, ok := ctx.Value(internal.KeyDir).(string) - if !ok { - return nil, errors.New("no screendump dir found in context") - } - - ff, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - oo := make([]runtime.Object, len(ff)) - for i, f := range ff { - oo[i] = render.FileRes{File: f, Dir: dir} - } - - return oo, nil -} diff --git a/internal/model/table.go b/internal/model/table.go index ccf6f7eb..b067dfd0 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -7,6 +7,7 @@ import ( "time" "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/rs/zerolog/log" @@ -41,6 +42,26 @@ func NewTable(gvr string) *Table { } } +// AddListener adds a new model listener. +func (t *Table) AddListener(l TableListener) { + t.listeners = append(t.listeners, l) +} + +// RemoveListener delete a listener from the list. +func (t *Table) RemoveListener(l TableListener) { + victim := -1 + for i, lis := range t.listeners { + if lis == l { + victim = i + break + } + } + + if victim >= 0 { + t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...) + } +} + // Watch initiates model updates. func (t *Table) Watch(ctx context.Context) { t.Refresh(ctx) @@ -49,14 +70,57 @@ func (t *Table) Watch(ctx context.Context) { // Get returns a resource instance if found, else an error. func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) { - meta := t.resourceMeta() - factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory) - if !ok { - return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory)) + meta, err := t.getMeta(ctx) + if err != nil { + return nil, err } - meta.Model.Init(t.namespace, t.gvr, factory) - return meta.Model.Get(ctx, path) + return meta.DAO.Get(ctx, path) +} + +func (t *Table) Delete(ctx context.Context, path string, cascade, force bool) error { + log.Debug().Msgf("TABLE DELETE %q", path) + meta, err := t.getMeta(ctx) + if err != nil { + return err + } + + nuker, ok := meta.DAO.(dao.Nuker) + if !ok { + return fmt.Errorf("no nuker for %q", meta.DAO.GVR()) + } + + return nuker.Delete(path, cascade, force) +} + +// Describe describes a given resource. +func (t *Table) Describe(ctx context.Context, path string) (string, error) { + meta, err := t.getMeta(ctx) + if err != nil { + return "", err + } + + desc, ok := meta.DAO.(dao.Describer) + if !ok { + return "", fmt.Errorf("no describer for %q", meta.DAO.GVR()) + } + + return desc.Describe(path) +} + +// ToYAML returns a resource yaml. +func (t *Table) ToYAML(ctx context.Context, path string) (string, error) { + meta, err := t.getMeta(ctx) + if err != nil { + return "", err + } + + desc, ok := meta.DAO.(dao.Describer) + if !ok { + return "", fmt.Errorf("no describer for %q", meta.DAO.GVR()) + } + + return desc.ToYAML(path) } // Refresh update the model now. @@ -82,7 +146,7 @@ func (t *Table) SetRefreshRate(d time.Duration) { // ClusterWide checks if resource is scope for all namespaces. func (t *Table) ClusterWide() bool { - return t.namespace == render.AllNamespaces + return client.IsClusterWide(t.namespace) } // InNamespace checks if current namespace matches desired namespace. @@ -113,7 +177,6 @@ func (t *Table) updater(ctx context.Context) { } func (t *Table) refresh(ctx context.Context) { - log.Debug().Msgf("RECONCILING") if !atomic.CompareAndSwapInt32(&t.inUpdate, 0, 1) { log.Debug().Msgf("Dropping update...") return @@ -128,62 +191,14 @@ func (t *Table) refresh(ctx context.Context) { t.fireTableChanged(*t.data) } -// AddListener adds a new model listener. -func (t *Table) AddListener(l TableListener) { - t.listeners = append(t.listeners, l) -} - -// RemoveListener delete a listener from the list. -func (t *Table) RemoveListener(l TableListener) { - victim := -1 - for i, lis := range t.listeners { - if lis == l { - victim = i - break - } - } - - if victim >= 0 { - t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...) - } -} - -func (t *Table) fireTableChanged(data render.TableData) { - for _, l := range t.listeners { - l.TableDataChanged(data) - } -} - -func (t *Table) fireTableLoadFailed(err error) { - for _, l := range t.listeners { - l.TableLoadFailed(err) - } -} - -func (t *Table) list(ctx context.Context, l Lister) ([]runtime.Object, error) { +func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, error) { factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory) if !ok { return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory)) } - l.Init(t.namespace, t.gvr, factory) + a.Init(factory, client.NewGVR(t.gvr)) - return l.List(ctx) -} - -func (t *Table) resourceMeta() ResourceMeta { - meta, ok := Registry[t.gvr] - if !ok { - log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr) - meta = ResourceMeta{ - Model: &Generic{}, - Renderer: &render.Generic{}, - } - } - if meta.Model == nil { - meta.Model = &Resource{} - } - - return meta + return a.List(ctx, t.namespace) } func (t *Table) reconcile(ctx context.Context) error { @@ -192,14 +207,14 @@ func (t *Table) reconcile(ctx context.Context) error { }(time.Now()) meta := t.resourceMeta() - oo, err := t.list(ctx, meta.Model) + oo, err := t.list(ctx, meta.DAO) if err != nil { return err } log.Debug().Msgf("LIST returned %d rows", len(oo)) rows := make(render.Rows, len(oo)) - if err := meta.Model.Hydrate(oo, rows, meta.Renderer); err != nil { + if err := hydrate(t.namespace, oo, rows, meta.Renderer); err != nil { return err } @@ -216,3 +231,54 @@ func (t *Table) reconcile(ctx context.Context) error { return nil } + +func (t *Table) getMeta(ctx context.Context) (ResourceMeta, error) { + meta := t.resourceMeta() + factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory) + if !ok { + return ResourceMeta{}, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory)) + } + meta.DAO.Init(factory, client.NewGVR(t.gvr)) + + return meta, nil +} + +func (t *Table) resourceMeta() ResourceMeta { + meta, ok := Registry[t.gvr] + if !ok { + log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr) + meta = ResourceMeta{ + DAO: &dao.Generic{}, + Renderer: &render.Generic{}, + } + } + if meta.DAO == nil { + meta.DAO = &dao.Resource{} + } + + return meta +} + +func (t *Table) fireTableChanged(data render.TableData) { + for _, l := range t.listeners { + l.TableDataChanged(data) + } +} + +func (t *Table) fireTableLoadFailed(err error) { + for _, l := range t.listeners { + 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 +} diff --git a/internal/model/types.go b/internal/model/types.go index e7c0dae8..e78af135 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -3,6 +3,7 @@ package model import ( "context" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/derailed/tview" @@ -54,23 +55,40 @@ type Renderer interface { ColorerFunc() render.ColorerFunc } -// Lister represents a resource lister. -type Lister interface { - // Init initializes a resource. - Init(ns, gvr string, f dao.Factory) - +type Cruder interface { // List returns a collection of resources. - List(context.Context) ([]runtime.Object, error) + List(ctx context.Context, ns string) ([]runtime.Object, error) // Get returns a resource instance. Get(ctx context.Context, path string) (runtime.Object, error) + // Delete removes a resource. + // Delete(ctx context.Context, path string) error +} + +// Lister represents a resource lister. +type Lister interface { + Cruder + // Describer + + // Init initializes a resource. + Init(ns, gvr string, f dao.Factory) + // Hydrate converts resource rows into tabular data. - Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error + // Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error +} + +// Describer represents a resource describer. +type Describer interface { + // ToYAML return resource yaml. + ToYAML(ctx context.Context, path string) (string, error) + + // Describe returns a resource description. + Describe(client client.Connection, gvr, path string) (string, error) } // ResourceMeta represents model info about a resource. type ResourceMeta struct { - Model Lister + DAO dao.Accessor Renderer Renderer } diff --git a/internal/render/alias_test.go b/internal/render/alias_test.go index 6caa6763..3b165ae3 100644 --- a/internal/render/alias_test.go +++ b/internal/render/alias_test.go @@ -3,6 +3,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/gdamore/tcell" "github.com/stretchr/testify/assert" @@ -18,15 +19,15 @@ func TestAliasColorer(t *testing.T) { e tcell.Color }{ "addAll": { - ns: render.AllNamespaces, + ns: client.AllNamespaces, re: render.RowEvent{Kind: render.EventAdd, Row: r}, e: tcell.ColorMediumSpringGreen}, "deleteAll": { - ns: render.AllNamespaces, + ns: client.AllNamespaces, re: render.RowEvent{Kind: render.EventDelete, Row: r}, e: tcell.ColorMediumSpringGreen}, "updateAll": { - ns: render.AllNamespaces, + ns: client.AllNamespaces, re: render.RowEvent{Kind: render.EventUpdate, Row: r}, e: tcell.ColorMediumSpringGreen, }, @@ -49,7 +50,7 @@ func TestAliasHeader(t *testing.T) { var a render.Alias assert.Equal(t, h, a.Header("fred")) - assert.Equal(t, h, a.Header(render.AllNamespaces)) + assert.Equal(t, h, a.Header(client.AllNamespaces)) } func TestAliasRender(t *testing.T) { diff --git a/internal/render/chart.go b/internal/render/chart.go new file mode 100644 index 00000000..3e24473f --- /dev/null +++ b/internal/render/chart.go @@ -0,0 +1,82 @@ +package render + +import ( + "fmt" + "strconv" + + "github.com/derailed/k9s/internal/client" + "github.com/gdamore/tcell" + "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{} + +// ColorerFunc colors a resource row. +func (Chart) ColorerFunc() ColorerFunc { + return func(ns string, re RowEvent) tcell.Color { + return tcell.ColorMediumSpringGreen + } +} + +// Header returns a header row. +func (Chart) Header(ns string) HeaderRow { + var h HeaderRow + if client.IsAllNamespaces(ns) { + h = append(h, Header{Name: "NAMESPACE"}) + } + + return append(h, + Header{Name: "NAME"}, + Header{Name: "REVISION"}, + Header{Name: "STATUS"}, + Header{Name: "CHART"}, + Header{Name: "APP VERSION"}, + Header{Name: "AGE", Decorator: AgeDecorator}, + ) +} + +// Render renders a chart to screen. +func (c Chart) Render(o interface{}, ns string, r *Row) error { + h, ok := o.(ChartRes) + if !ok { + return fmt.Errorf("expected ChartRes, but got %T", o) + } + + r.ID = FQN(h.Release.Namespace, h.Release.Name) + r.Fields = make(Fields, 0, len(c.Header(ns))) + if client.IsAllNamespaces(ns) { + r.Fields = append(r.Fields, h.Release.Namespace) + } + r.Fields = append(r.Fields, + 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, + toAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}), + ) + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +// ChartRes represents an alias resource. +type ChartRes struct { + Release *release.Release +} + +// GetObjectKind returns a schema object. +func (ChartRes) GetObjectKind() schema.ObjectKind { + return nil +} + +// DeepCopyObject returns a container copy. +func (h ChartRes) DeepCopyObject() runtime.Object { + return h +} diff --git a/internal/render/cm.go b/internal/render/cm.go index 7f63eeae..1eb51aaa 100644 --- a/internal/render/cm.go +++ b/internal/render/cm.go @@ -5,6 +5,7 @@ import ( "strconv" "time" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,7 +22,7 @@ func (ConfigMap) ColorerFunc() ColorerFunc { // Header returns a header row. func (ConfigMap) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -48,7 +49,7 @@ func (c ConfigMap) Render(o interface{}, ns string, r *Row) error { n, nss := extractMetaField(meta, "name"), extractMetaField(meta, "namespace") r.ID = FQN(nss, n) r.Fields = make(Fields, 0, len(c.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, nss) } @@ -79,7 +80,7 @@ func (c ConfigMap) Render(o interface{}, ns string, r *Row) error { // r.ID = MetaFQN(cm.ObjectMeta) // r.Fields = make(Fields, 0, len(c.Header(ns))) - // if isAllNamespace(ns) { + // if client.IsAllNamespaces(ns) { // r.Fields = append(r.Fields, cm.Namespace) // } // r.Fields = append(r.Fields, diff --git a/internal/render/container.go b/internal/render/container.go index ab7decbb..fb26f311 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" v1 "k8s.io/api/core/v1" @@ -95,7 +96,7 @@ func (c Container) Render(o interface{}, name string, r *Row) error { } r.ID = co.Container.Name - r.Fields = make(Fields, 0, len(c.Header(AllNamespaces))) + r.Fields = make(Fields, 0, len(c.Header(client.AllNamespaces))) r.Fields = append(r.Fields, co.Container.Name, co.Container.Image, diff --git a/internal/render/crd.go b/internal/render/crd.go index 2218b25a..e2a0bfc2 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -41,7 +42,7 @@ func (CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error { log.Error().Err(err).Msgf("Fields timestamp %v", err) } - r.ID = FQN(ClusterScope, extractMetaField(meta, "name")) + r.ID = FQN(client.ClusterScope, extractMetaField(meta, "name")) r.Fields = Fields{ extractMetaField(meta, "name"), toAge(metav1.Time{Time: t}), diff --git a/internal/render/cronjob.go b/internal/render/cronjob.go index c68127a4..f2449d6a 100644 --- a/internal/render/cronjob.go +++ b/internal/render/cronjob.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/derailed/k9s/internal/client" batchv1beta1 "k8s.io/api/batch/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,7 +21,7 @@ func (CronJob) ColorerFunc() ColorerFunc { // Header returns a header row. func (CronJob) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -53,7 +54,7 @@ func (c CronJob) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(cj.ObjectMeta) r.Fields = make(Fields, 0, len(c.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, cj.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/dp.go b/internal/render/dp.go index efb16641..66551fd5 100644 --- a/internal/render/dp.go +++ b/internal/render/dp.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" appsv1 "k8s.io/api/apps/v1" @@ -24,7 +25,7 @@ func (Deployment) ColorerFunc() ColorerFunc { } markCol := 2 - if ns != AllNamespaces { + if !client.IsAllNamespaces(ns) { markCol = 1 } tokens := strings.Split(r.Row.Fields[markCol], "/") @@ -39,7 +40,7 @@ func (Deployment) ColorerFunc() ColorerFunc { // Header returns a header row. func (Deployment) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -67,7 +68,7 @@ func (d Deployment) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(dp.ObjectMeta) r.Fields = make(Fields, 0, len(d.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, dp.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/ds.go b/internal/render/ds.go index cc2e14fd..5a689e25 100644 --- a/internal/render/ds.go +++ b/internal/render/ds.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" appsv1 "k8s.io/api/apps/v1" @@ -24,7 +25,7 @@ func (DaemonSet) ColorerFunc() ColorerFunc { } markCol := 2 - if ns != AllNamespaces { + if !client.IsAllNamespaces(ns) { markCol = 1 } if strings.TrimSpace(r.Row.Fields[markCol]) != strings.TrimSpace(r.Row.Fields[markCol+2]) { @@ -38,7 +39,7 @@ func (DaemonSet) ColorerFunc() ColorerFunc { // Header returns a header row. func (DaemonSet) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -67,7 +68,7 @@ func (d DaemonSet) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(ds.ObjectMeta) r.Fields = make(Fields, 0, len(d.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, ds.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/ep.go b/internal/render/ep.go index 37f496b8..418e08ad 100644 --- a/internal/render/ep.go +++ b/internal/render/ep.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -21,7 +22,7 @@ func (Endpoints) ColorerFunc() ColorerFunc { // Header returns a header row. func (Endpoints) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -46,7 +47,7 @@ func (e Endpoints) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(ep.ObjectMeta) r.Fields = make(Fields, 0, len(e.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, ep.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/ev.go b/internal/render/ev.go index 5a87191d..8f9b8bae 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" v1 "k8s.io/api/core/v1" @@ -21,7 +22,7 @@ func (Event) ColorerFunc() ColorerFunc { c := DefaultColorer(ns, r) markCol := 3 - if ns != AllNamespaces { + if !client.IsAllNamespaces(ns) { markCol = 2 } switch strings.TrimSpace(r.Row.Fields[markCol]) { @@ -38,7 +39,7 @@ func (Event) ColorerFunc() ColorerFunc { // Header returns a header rbw. func (Event) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -66,7 +67,7 @@ func (e Event) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(ev.ObjectMeta) r.Fields = make(Fields, 0, len(e.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, ev.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/generic.go b/internal/render/generic.go index 94fe4118..f26725b6 100644 --- a/internal/render/generic.go +++ b/internal/render/generic.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" ) @@ -35,7 +36,7 @@ func (g *Generic) Header(ns string) HeaderRow { } h := make(HeaderRow, 0, len(g.table.ColumnDefinitions)) - if ns == "" { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } for i, c := range g.table.ColumnDefinitions { @@ -59,22 +60,19 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error { return fmt.Errorf("expecting a TableRow but got %T", o) } - var nns = AllNamespaces - if ns != ClusterScope { - var err error - nns, err = extractNamespace(row.Object.Raw) - if err != nil { - return err - } + _, nns, err := resourceNS(row.Object.Raw) + if err != nil { + return err } + n, ok := row.Cells[0].(string) if !ok { - return fmt.Errorf("expecting row id to be a string but got %#v", row.Cells[0]) + return fmt.Errorf("expecting row 0 to be a string but got %T", row.Cells[0]) } r.ID = FQN(nns, n) r.Fields = make(Fields, 0, len(g.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) && nns != "" { r.Fields = append(r.Fields, nns) } var ageCell interface{} @@ -95,21 +93,26 @@ func (g *Generic) Render(o interface{}, ns string, r *Row) error { // ---------------------------------------------------------------------------- // Helpers... -func extractNamespace(raw []byte) (string, error) { +func resourceNS(raw []byte) (bool, string, error) { var obj map[string]interface{} err := json.Unmarshal(raw, &obj) if err != nil { - return "", err + return false, "", err } meta, ok := obj["metadata"].(map[string]interface{}) if !ok { - return "", errors.New("no metadata found on generic resource") - } - ns, ok := meta["namespace"].(string) - if !ok { - return "", errors.New("invalid namespace found on generic metadata") + return false, "", errors.New("no metadata found on generic resource") } - return ns, nil + ns, ok := meta["namespace"] + if !ok { + return true, "", nil + } + + nns, ok := ns.(string) + if !ok { + return false, "", fmt.Errorf("expecting namespace string type but got %T", ns) + } + return false, nns, nil } diff --git a/internal/render/generic_test.go b/internal/render/generic_test.go index df89a058..d6378086 100644 --- a/internal/render/generic_test.go +++ b/internal/render/generic_test.go @@ -3,6 +3,7 @@ package render_test import ( "testing" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/stretchr/testify/assert" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" @@ -17,8 +18,8 @@ func TestGenericRender(t *testing.T) { eFields render.Fields eHeader render.HeaderRow }{ - "specific_ns": { - ns: "blee", + "withNS": { + ns: "ns1", table: makeNSGeneric(), eID: "ns1/c1", eFields: render.Fields{"c1", "c2", "c3"}, @@ -28,9 +29,9 @@ func TestGenericRender(t *testing.T) { render.Header{Name: "C"}, }, }, - "all_ns": { - ns: "", - table: makeAllNSGeneric(), + "nsAll": { + ns: client.NamespaceAll, + table: makeNSGeneric(), eID: "ns1/c1", eFields: render.Fields{"ns1", "c1", "c2", "c3"}, eHeader: render.HeaderRow{ @@ -40,9 +41,21 @@ func TestGenericRender(t *testing.T) { render.Header{Name: "C"}, }, }, - "cluster": { - ns: "-", - table: makeClusterGeneric(), + "AllNS": { + ns: client.AllNamespaces, + table: makeNSGeneric(), + eID: "ns1/c1", + eFields: render.Fields{"ns1", "c1", "c2", "c3"}, + eHeader: render.HeaderRow{ + render.Header{Name: "NAMESPACE"}, + render.Header{Name: "A"}, + render.Header{Name: "B"}, + render.Header{Name: "C"}, + }, + }, + "clusterWide": { + ns: client.ClusterScope, + table: makeNoNSGeneric(), eID: "c1", eFields: render.Fields{"c1", "c2", "c3"}, eHeader: render.HeaderRow{ @@ -52,7 +65,7 @@ func TestGenericRender(t *testing.T) { }, }, "age": { - ns: "-", + ns: client.ClusterScope, table: makeAgeGeneric(), eID: "c1", eFields: render.Fields{"c1", "c2", "Age"}, @@ -64,16 +77,17 @@ func TestGenericRender(t *testing.T) { }, } - var re render.Generic for k := range uu { + var re render.Generic u := uu[k] t.Run(k, func(t *testing.T) { var r render.Row re.SetTable(u.table) + + assert.Equal(t, u.eHeader, re.Header(u.ns)) assert.Nil(t, re.Render(&u.table.Rows[0], u.ns, &r)) assert.Equal(t, u.eID, r.ID) assert.Equal(t, u.eFields, r.Fields) - assert.Equal(t, u.eHeader, re.Header(u.ns)) }) } } @@ -109,35 +123,7 @@ func makeNSGeneric() *metav1beta1.Table { } } -func makeAllNSGeneric() *metav1beta1.Table { - return &metav1beta1.Table{ - ColumnDefinitions: []metav1beta1.TableColumnDefinition{ - {Name: "a"}, - {Name: "b"}, - {Name: "c"}, - }, - Rows: []metav1beta1.TableRow{ - { - Object: runtime.RawExtension{ - Raw: []byte(`{ - "kind": "fred", - "apiVersion": "v1", - "metadata": { - "namespace": "ns1", - "name": "fred" - }}`), - }, - Cells: []interface{}{ - "c1", - "c2", - "c3", - }, - }, - }, - } -} - -func makeClusterGeneric() *metav1beta1.Table { +func makeNoNSGeneric() *metav1beta1.Table { return &metav1beta1.Table{ ColumnDefinitions: []metav1beta1.TableColumnDefinition{ {Name: "a"}, diff --git a/internal/render/helpers.go b/internal/render/helpers.go index 1a223a76..793b183e 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" "github.com/rs/zerolog/log" @@ -31,10 +32,6 @@ func asSelector(s *metav1.LabelSelector) string { return sel.String() } -func isAllNamespace(ns string) bool { - return ns == AllNamespaces -} - type metric struct { cpu, mem string } @@ -46,7 +43,7 @@ func noMetric() metric { // MetaFQN returns a fully qualified resource name. func MetaFQN(m metav1.ObjectMeta) string { if m.Namespace == "" { - return m.Name + return FQN(client.ClusterScope, m.Name) } return FQN(m.Namespace, m.Name) diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 9b8b6765..b528724e 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -270,7 +270,7 @@ func TestMetaFQN(t *testing.T) { e string }{ "full": {metav1.ObjectMeta{Namespace: "fred", Name: "blee"}, "fred/blee"}, - "nons": {metav1.ObjectMeta{Name: "blee"}, "blee"}, + "nons": {metav1.ObjectMeta{Name: "blee"}, "-/blee"}, } for k := range uu { diff --git a/internal/render/hpa.go b/internal/render/hpa.go index f68a4dde..32815435 100644 --- a/internal/render/hpa.go +++ b/internal/render/hpa.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" @@ -24,7 +25,7 @@ func (HorizontalPodAutoscaler) ColorerFunc() ColorerFunc { // Header returns a header row. func (HorizontalPodAutoscaler) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -69,7 +70,7 @@ func (h HorizontalPodAutoscaler) renderV1(raw *unstructured.Unstructured, ns str r.ID = MetaFQN(hpa.ObjectMeta) r.Fields = make(Fields, 0, len(h.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, hpa.Namespace) } r.Fields = append(r.Fields, @@ -94,7 +95,7 @@ func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, ns s r.ID = MetaFQN(hpa.ObjectMeta) r.Fields = make(Fields, 0, len(h.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, hpa.Namespace) } @@ -120,7 +121,7 @@ func (h HorizontalPodAutoscaler) renderV2b2(raw *unstructured.Unstructured, ns s r.ID = MetaFQN(hpa.ObjectMeta) r.Fields = make(Fields, 0, len(h.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, hpa.Namespace) } diff --git a/internal/render/ing.go b/internal/render/ing.go index 4f4fe747..1f37afc2 100644 --- a/internal/render/ing.go +++ b/internal/render/ing.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" v1 "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,7 +22,7 @@ func (Ingress) ColorerFunc() ColorerFunc { // Header returns a header row. func (Ingress) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -48,7 +49,7 @@ func (i Ingress) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(ing.ObjectMeta) r.Fields = make(Fields, 0, len(i.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, ing.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/job.go b/internal/render/job.go index f9130112..c242ee80 100644 --- a/internal/render/job.go +++ b/internal/render/job.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" @@ -25,7 +26,7 @@ func (Job) ColorerFunc() ColorerFunc { // Header returns a header row. func (Job) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -54,7 +55,7 @@ func (j Job) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(job.ObjectMeta) r.Fields = make(Fields, 0, len(j.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, job.Namespace) } cc, ii := toContainers(job.Spec.Template.Spec) diff --git a/internal/render/np.go b/internal/render/np.go index f3a45a08..9d85343d 100644 --- a/internal/render/np.go +++ b/internal/render/np.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" v1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,7 +22,7 @@ func (NetworkPolicy) ColorerFunc() ColorerFunc { // Header returns a header row. func (NetworkPolicy) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -54,7 +55,7 @@ func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(np.ObjectMeta) r.Fields = make(Fields, 0, len(n.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, np.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go index 89736e22..ed1f6c57 100644 --- a/internal/render/ns_test.go +++ b/internal/render/ns_test.go @@ -39,6 +39,6 @@ func TestNamespaceRender(t *testing.T) { r := render.NewRow(3) c.Render(load(t, "ns"), "-", &r) - assert.Equal(t, "kube-system", r.ID) + assert.Equal(t, "-/kube-system", r.ID) assert.Equal(t, render.Fields{"kube-system", "Active"}, r.Fields[:2]) } diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 167492a7..8252d766 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" v1beta1 "k8s.io/api/policy/v1beta1" @@ -25,7 +26,7 @@ func (PodDisruptionBudget) ColorerFunc() ColorerFunc { } markCol := 5 - if ns != AllNamespaces { + if client.IsNamespaced(ns) { markCol = 4 } if strings.TrimSpace(r.Row.Fields[markCol]) != strings.TrimSpace(r.Row.Fields[markCol+1]) { @@ -40,7 +41,7 @@ func (PodDisruptionBudget) ColorerFunc() ColorerFunc { // Header returns a header row. func (PodDisruptionBudget) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -70,7 +71,7 @@ func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(pdb.ObjectMeta) r.Fields = make(Fields, 0, len(p.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, pdb.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/pod.go b/internal/render/pod.go index 2f3dbfe0..87cc8d9e 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" v1 "k8s.io/api/core/v1" @@ -66,7 +67,7 @@ func (Pod) checkReadyCol(readyCol, statusCol string, c tcell.Color) tcell.Color // Header returns a header row. func (Pod) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -105,7 +106,7 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(po.ObjectMeta) r.Fields = make(Fields, 0, len(p.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, po.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/policy.go b/internal/render/policy.go index feb5f77a..3eb48a1b 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -103,7 +103,7 @@ type Policies []PolicyRes // Upsert adds a new policy. func (pp Policies) Upsert(p PolicyRes) Policies { - idx, ok := pp.findPol(p.Resource) + idx, ok := pp.find(p.Resource) if !ok { return append(pp, p) } @@ -113,7 +113,7 @@ func (pp Policies) Upsert(p PolicyRes) Policies { } // Find locates a row by id. Retturns false is not found. -func (pp Policies) findPol(res string) (int, bool) { +func (pp Policies) find(res string) (int, bool) { for i, p := range pp { if p.Resource == res { return i, true diff --git a/internal/render/pv_test.go b/internal/render/pv_test.go index 995e5b24..a0fa3927 100644 --- a/internal/render/pv_test.go +++ b/internal/render/pv_test.go @@ -12,6 +12,6 @@ func TestPersistentVolumeRender(t *testing.T) { r := render.NewRow(9) c.Render(load(t, "pv"), "-", &r) - assert.Equal(t, "pvc-07aa4e2c-8726-11e9-a8e8-42010a80015b", r.ID) + 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]) } diff --git a/internal/render/pvc.go b/internal/render/pvc.go index 6cb71af6..2d2f767f 100644 --- a/internal/render/pvc.go +++ b/internal/render/pvc.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" "github.com/gdamore/tcell" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -22,7 +23,7 @@ func (PersistentVolumeClaim) ColorerFunc() ColorerFunc { } markCol := 2 - if ns != AllNamespaces { + if client.IsNamespaced(ns) { markCol = 1 } @@ -38,7 +39,7 @@ func (PersistentVolumeClaim) ColorerFunc() ColorerFunc { // Header returns a header rbw. func (PersistentVolumeClaim) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -86,7 +87,7 @@ func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(pvc.ObjectMeta) r.Fields = make(Fields, 0, len(p.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, pvc.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/ro.go b/internal/render/ro.go index 5a67eb53..dee87474 100644 --- a/internal/render/ro.go +++ b/internal/render/ro.go @@ -3,6 +3,7 @@ package render import ( "fmt" + "github.com/derailed/k9s/internal/client" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -19,7 +20,7 @@ func (Role) ColorerFunc() ColorerFunc { // Header returns a header row. func (Role) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -43,7 +44,7 @@ func (r Role) Render(o interface{}, ns string, row *Row) error { row.ID = MetaFQN(ro.ObjectMeta) row.Fields = make(Fields, 0, len(r.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { row.Fields = append(row.Fields, ro.Namespace) } row.Fields = append(row.Fields, diff --git a/internal/render/rob.go b/internal/render/rob.go index e8fb34f6..6e6f9b91 100644 --- a/internal/render/rob.go +++ b/internal/render/rob.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,7 +21,7 @@ func (RoleBinding) ColorerFunc() ColorerFunc { // Header returns a header rbw. func (RoleBinding) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -49,7 +50,7 @@ func (r RoleBinding) Render(o interface{}, ns string, row *Row) error { row.ID = MetaFQN(rb.ObjectMeta) row.Fields = make(Fields, 0, len(r.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { row.Fields = append(row.Fields, rb.Namespace) } row.Fields = append(row.Fields, diff --git a/internal/render/rs.go b/internal/render/rs.go index 880d6b12..d01bdd27 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" "github.com/gdamore/tcell" appsv1 "k8s.io/api/apps/v1" @@ -24,7 +25,7 @@ func (ReplicaSet) ColorerFunc() ColorerFunc { } markCol := 2 - if ns != AllNamespaces { + if client.IsNamespaced(ns) { markCol = 1 } if strings.TrimSpace(r.Row.Fields[markCol]) != strings.TrimSpace(r.Row.Fields[markCol+1]) { @@ -39,7 +40,7 @@ func (ReplicaSet) ColorerFunc() ColorerFunc { // Header returns a header row. func (ReplicaSet) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -66,7 +67,7 @@ func (s ReplicaSet) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(rs.ObjectMeta) r.Fields = make(Fields, 0, len(s.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, rs.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/sa.go b/internal/render/sa.go index 760a77a9..c468f031 100644 --- a/internal/render/sa.go +++ b/internal/render/sa.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/derailed/k9s/internal/client" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -20,7 +21,7 @@ func (ServiceAccount) ColorerFunc() ColorerFunc { // Header returns a header row. func (ServiceAccount) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -45,7 +46,7 @@ func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(sa.ObjectMeta) r.Fields = make(Fields, 0, len(s.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, sa.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/sc.go b/internal/render/sc.go index a626a918..4897e787 100644 --- a/internal/render/sc.go +++ b/internal/render/sc.go @@ -3,6 +3,7 @@ package render import ( "fmt" + "github.com/derailed/k9s/internal/client" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -37,7 +38,7 @@ func (StorageClass) Render(o interface{}, ns string, r *Row) error { return err } - r.ID = FQN(ClusterScope, sc.ObjectMeta.Name) + r.ID = FQN(client.ClusterScope, sc.ObjectMeta.Name) r.Fields = Fields{ sc.Name, string(sc.Provisioner), diff --git a/internal/render/secret.go b/internal/render/secret.go index 0280d0ba..4f1e2116 100644 --- a/internal/render/secret.go +++ b/internal/render/secret.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/derailed/k9s/internal/client" "github.com/derailed/tview" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,7 +22,7 @@ func (Secret) ColorerFunc() ColorerFunc { // Header returns a header row. func (Secret) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -47,7 +48,7 @@ func (s Secret) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(sec.ObjectMeta) r.Fields = make(Fields, 0, len(s.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, sec.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/sts.go b/internal/render/sts.go index b68a1503..62ba8774 100644 --- a/internal/render/sts.go +++ b/internal/render/sts.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/gdamore/tcell" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -23,7 +24,7 @@ func (StatefulSet) ColorerFunc() ColorerFunc { } readyCol := 2 - if ns != AllNamespaces { + if client.IsNamespaced(ns) { readyCol-- } tokens := strings.Split(strings.TrimSpace(r.Row.Fields[readyCol]), "/") @@ -39,7 +40,7 @@ func (StatefulSet) ColorerFunc() ColorerFunc { // Header returns a header row. func (StatefulSet) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -66,7 +67,7 @@ func (s StatefulSet) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(sts.ObjectMeta) r.Fields = make(Fields, 0, len(s.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, sts.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/subject.go b/internal/render/subject.go index f5fa4b35..5c1e0f83 100644 --- a/internal/render/subject.go +++ b/internal/render/subject.go @@ -29,9 +29,9 @@ func (Subject) Header(ns string) HeaderRow { // Render renders a K8s resource to screen. func (s Subject) Render(o interface{}, ns string, r *Row) error { - res, ok := o.(SubjectRef) + res, ok := o.(SubjectRes) if !ok { - return fmt.Errorf("Expected SubjectRef, but got %T", s) + return fmt.Errorf("Expected SubjectRes, but got %T", s) } r.ID = res.Name @@ -48,17 +48,42 @@ func (s Subject) Render(o interface{}, ns string, r *Row) error { // ---------------------------------------------------------------------------- // Helpers... -// SubjectRef represents a subject rule. -type SubjectRef struct { +// SubjectRes represents a subject rule. +type SubjectRes struct { Name, Kind, FirstLocation string } // GetObjectKind returns a schema object. -func (SubjectRef) GetObjectKind() schema.ObjectKind { +func (SubjectRes) GetObjectKind() schema.ObjectKind { return nil } // DeepCopyObject returns a container copy. -func (s SubjectRef) DeepCopyObject() runtime.Object { +func (s SubjectRes) DeepCopyObject() runtime.Object { return s } + +// Subjects represents a collection of RBAC policies. +type Subjects []SubjectRes + +// Upsert adds a new subject. +func (ss Subjects) Upsert(s SubjectRes) Subjects { + idx, ok := ss.find(s.Name) + if !ok { + return append(ss, s) + } + ss[idx] = s + + return ss +} + +// Find locates a row by id. Retturns false is not found. +func (ss Subjects) find(res string) (int, bool) { + for i, s := range ss { + if s.Name == res { + return i, true + } + } + + return 0, false +} diff --git a/internal/render/svc.go b/internal/render/svc.go index e41935a5..9be2edf5 100644 --- a/internal/render/svc.go +++ b/internal/render/svc.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -22,7 +23,7 @@ func (Service) ColorerFunc() ColorerFunc { // Header returns a header row. func (Service) Header(ns string) HeaderRow { var h HeaderRow - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { h = append(h, Header{Name: "NAMESPACE"}) } @@ -51,7 +52,7 @@ func (s Service) Render(o interface{}, ns string, r *Row) error { r.ID = MetaFQN(svc.ObjectMeta) r.Fields = make(Fields, 0, len(s.Header(ns))) - if isAllNamespace(ns) { + if client.IsAllNamespaces(ns) { r.Fields = append(r.Fields, svc.Namespace) } r.Fields = append(r.Fields, diff --git a/internal/render/table_data.go b/internal/render/table_data.go index d148f93e..07175be4 100644 --- a/internal/render/table_data.go +++ b/internal/render/table_data.go @@ -2,8 +2,6 @@ package render import ( "sync" - - "github.com/rs/zerolog/log" ) // TableData tracks a K8s resource for tabular display. @@ -81,7 +79,6 @@ func (t *TableData) Delete(newKeys []string) { } for _, id := range victims { - log.Debug().Msgf("Deleting %s", id) t.RowEvents = t.RowEvents.Delete(id) } } diff --git a/internal/render/types.go b/internal/render/types.go index 499fe639..9790b78b 100644 --- a/internal/render/types.go +++ b/internal/render/types.go @@ -1,15 +1,6 @@ package render const ( - // AllNamespaces represents all namespaces. - AllNamespaces = "" - - // NamespaceAll represent the all namespace. - NamespaceAll = "all" - - // ClusterScope represents cluster wide resources. - ClusterScope = "-" - // NonResource represents a custom resource. NonResource = "*" ) diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index a56b4d2c..57a681a2 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -97,7 +97,7 @@ func (s *SelectTable) updateSelection(broadcast bool) { } func (s *SelectTable) selectionChanged(r, c int) { - if r <= 0 { + if r < 0 { return } s.selectedRow = r diff --git a/internal/ui/table.go b/internal/ui/table.go index a4f1dd8c..ac2eb337 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" @@ -43,9 +44,10 @@ type Table struct { func NewTable(gvr string) *Table { return &Table{ SelectTable: &SelectTable{ - Table: tview.NewTable(), - model: model.NewTable(gvr), - marks: make(map[string]struct{}), + Table: tview.NewTable(), + model: model.NewTable(gvr), + selectedRow: 1, + marks: make(map[string]struct{}), }, actions: make(KeyActions), cmdBuff: NewCmdBuff('/', FilterBuff), @@ -107,7 +109,7 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { t.SearchBuff().Add(evt.Rune()) t.ClearSelection() data := t.GetModel().Peek() - t.doUpdate(t.filtered(data), len(data.RowEvents) > 0) + t.doUpdate(t.filtered(data)) t.UpdateTitle() t.SelectFirstRow() return nil @@ -152,23 +154,18 @@ func (t *Table) Update(data render.TableData) { data.Mutex.RLock() defer data.Mutex.RUnlock() - var firstRow bool - if t.GetRowCount() == 0 { - firstRow = true - } - if t.decorateFn != nil { data = t.decorateFn(data) } if !t.cmdBuff.Empty() { data = t.filtered(data) } - t.doUpdate(data, firstRow) + t.doUpdate(data) t.UpdateTitle() } -func (t *Table) doUpdate(data render.TableData, firstRow bool) { - if data.Namespace == render.AllNamespaces { +func (t *Table) doUpdate(data render.TableData) { + if client.IsAllNamespaces(data.Namespace) { t.actions[KeyShiftP] = NewKeyAction("Sort Namespace", t.SortColCmd(-2, true), false) } else { t.actions.Delete(KeyShiftP) @@ -191,10 +188,6 @@ func (t *Table) doUpdate(data render.TableData, firstRow bool) { for i, r := range data.RowEvents { t.buildRow(data.Namespace, i+1, r, data.Header, pads) } - - if firstRow { - t.SelectFirstRow() - } t.updateSelection(true) } @@ -348,34 +341,32 @@ func (t *Table) UpdateTitle() { // UpdateTitle refreshes the table title. func (t *Table) styleTitle() string { - ns := t.GetModel().GetNamespace() - if ns == render.AllNamespaces { - ns = render.NamespaceAll - } rc := t.GetRowCount() if rc > 0 { rc-- } - base, path := strings.Title(t.BaseTitle), t.Path - if ns == render.AllNamespaces { - ns = render.NamespaceAll + base := strings.Title(t.BaseTitle) + ns := t.GetModel().GetNamespace() + if ns == client.AllNamespaces { + ns = client.NamespaceAll } - info := ns + path := t.Path if path != "" { - info = path - cns, n := render.Namespaced(path) - if cns == render.ClusterScope { - info = n + cns, n := client.Namespaced(path) + if cns == client.ClusterScope { + ns = n + } else { + ns = path } } buff := t.SearchBuff().String() var title string - if info == "" || info == render.ClusterScope { + if ns == client.ClusterScope { title = SkinTitle(fmt.Sprintf(titleFmt, base, rc), t.styles.Frame()) } else { - title = SkinTitle(fmt.Sprintf(nsTitleFmt, base, info, rc), t.styles.Frame()) + title = SkinTitle(fmt.Sprintf(nsTitleFmt, base, ns, rc), t.styles.Frame()) } if buff == "" { return title @@ -384,5 +375,6 @@ func (t *Table) styleTitle() string { if IsLabelSelector(buff) { buff = TrimLabelSelector(buff) } + return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), t.styles.Frame()) } diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 6258c59a..96603df9 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -70,6 +70,15 @@ func (t *testModel) Watch(context.Context) {} func (t *testModel) Get(ctx context.Context, path string) (runtime.Object, error) { return nil, nil } +func (t *testModel) Delete(ctx context.Context, path string, c, f bool) error { + return nil +} +func (t *testModel) Describe(context.Context, string) (string, error) { + return "", nil +} +func (t *testModel) ToYAML(ctx context.Context, path string) (string, error) { + return "", nil +} func (t *testModel) InNamespace(string) bool { return true } func (t *testModel) SetRefreshRate(time.Duration) {} diff --git a/internal/ui/types.go b/internal/ui/types.go index add25e57..c25acfc3 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -36,9 +36,22 @@ type Namespaceable interface { InNamespace(string) bool } +// Lister represents a viewable resource. +type Lister interface { + // Get returns a resource instance. + Get(ctx context.Context, path string) (runtime.Object, error) + + // ToYAML returns a resource yaml representation. + ToYAML(ctx context.Context, path string) (string, error) + + // Describes describes a given resource. + Describe(ctx context.Context, path string) (string, error) +} + // Tabular represents a tabular model. type Tabular interface { Namespaceable + Lister // Empty returns true if model has no data. Empty() bool @@ -55,6 +68,6 @@ type Tabular interface { // AddListener registers a model listener. AddListener(model.TableListener) - // Get returns a resource instance. - Get(ctx context.Context, path string) (runtime.Object, error) + // Delete a resource. + Delete(ctx context.Context, path string, cascade, force bool) error } diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 0b72cc91..a47078f2 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -106,15 +106,25 @@ func (t *testModel) GetNamespace() string { return "blee" } func (t *testModel) SetNamespace(string) {} func (t *testModel) AddListener(model.TableListener) {} func (t *testModel) Watch(context.Context) {} -func (t *testModel) Get(ctx context.Context, path string) (runtime.Object, error) { +func (t *testModel) Get(context.Context, string) (runtime.Object, error) { return nil, nil } +func (t *testModel) Delete(context.Context, string, bool, bool) error { + return nil +} +func (t *testModel) Describe(context.Context, string) (string, error) { + return "", nil +} +func (t *testModel) ToYAML(ctx context.Context, path string) (string, error) { + return "", nil +} + func (t *testModel) InNamespace(string) bool { return true } func (t *testModel) SetRefreshRate(time.Duration) {} func makeTableData() render.TableData { return render.TableData{ - Namespace: render.ClusterScope, + Namespace: client.ClusterScope, Header: render.HeaderRow{ render.Header{Name: "RESOURCE"}, render.Header{Name: "COMMAND"}, diff --git a/internal/view/app.go b/internal/view/app.go index 09bc7042..406fc9c3 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -219,8 +219,8 @@ func (a *App) refreshIndicator() { } func (a *App) switchNS(ns string) bool { - if ns == render.ClusterScope { - ns = render.AllNamespaces + if ns == client.ClusterScope { + ns = client.AllNamespaces } if err := a.Config.SetActiveNamespace(ns); err != nil { log.Error().Err(err).Msg("Config Set NS failed!") diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 839b6376..681eddee 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -39,7 +39,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, ns, res, path string) { +func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) { data, err := readBenchFile(app.Config, b.benchFile()) if err != nil { app.Flash().Errf("Unable to load bench file %s", err) diff --git a/internal/view/browser.go b/internal/view/browser.go index 3556c3f3..a94a1e17 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -50,7 +50,7 @@ func (b *Browser) Init(ctx context.Context) error { if err = b.Table.Init(ctx); err != nil { return err } - if !dao.IsK9sMeta(b.meta) { + if dao.IsK8sMeta(b.meta) { if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR(), []string{"list", "watch"}); e != nil { return e } @@ -158,18 +158,12 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { } ctx := b.defaultContext() - o, err := b.GetModel().Get(ctx, path) + raw, err := b.GetModel().ToYAML(ctx, path) if err != nil { b.App().Flash().Errf("unable to get resource %q -- %s", b.gvr, err) return nil } - raw, err := toYAML(o) - if err != nil { - b.App().Flash().Errf("unable to marshal resource %s", err) - return nil - } - details := NewDetails(b.app, "YAML", path).Update(raw) if err := b.App().inject(details); err != nil { b.App().Flash().Err(err) @@ -224,7 +218,7 @@ func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey { if b.enterFn != nil { f = b.enterFn } - f(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path) + f(b.app, b.GetModel(), b.gvr.String(), path) return nil } @@ -249,7 +243,7 @@ func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { if len(selections) > 1 { msg = fmt.Sprintf("Delete %d marked %s?", len(selections), b.gvr) } - if dao.IsK9sMeta(b.meta) { + if !dao.IsK8sMeta(b.meta) { b.simpleDelete(selections, msg) return nil } @@ -264,7 +258,7 @@ func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey { if path == "" { return evt } - describeResource(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path) + describeResource(b.app, b.GetModel(), b.gvr.String(), path) return nil } @@ -299,7 +293,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { i, _ := strconv.Atoi(string(evt.Rune())) ns := b.namespaces[i] if ns == "" { - ns = render.NamespaceAll + ns = client.NamespaceAll } auth, err := b.App().factory.Client().CanI(ns, b.GVR(), watch.ReadVerbs) @@ -330,22 +324,18 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { func (b *Browser) setNamespace(ns string) { if !b.meta.Namespaced { - b.GetModel().SetNamespace(render.ClusterScope) + b.GetModel().SetNamespace(client.ClusterScope) return } if b.GetModel().InNamespace(ns) { return } - if ns == render.NamespaceAll { - ns = render.AllNamespaces - } - b.GetModel().SetNamespace(ns) + b.GetModel().SetNamespace(client.NormalizeNS(ns)) } func (b *Browser) defaultContext() context.Context { ctx := context.Background() - ctx = context.WithValue(ctx, internal.KeyFactory, b.app.factory) ctx = context.WithValue(ctx, internal.KeyGVR, b.gvr.String()) ctx = context.WithValue(ctx, internal.KeyPath, b.Path) @@ -355,7 +345,7 @@ func (b *Browser) defaultContext() context.Context { ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.SearchBuff().String())) } ctx = context.WithValue(ctx, internal.KeyFields, "") - ctx = context.WithValue(ctx, internal.KeyNamespace, b.App().Config.ActiveNamespace()) + ctx = context.WithValue(ctx, internal.KeyNamespace, client.NormalizeNS(b.App().Config.ActiveNamespace())) return ctx } @@ -365,11 +355,11 @@ func (b *Browser) namespaceActions(aa ui.KeyActions) { return } b.namespaces = make(map[int]string, config.MaxFavoritesNS) - aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(render.NamespaceAll, b.switchNamespaceCmd, true) - b.namespaces[0] = render.NamespaceAll + aa[tcell.Key(ui.NumKeys[0])] = ui.NewKeyAction(client.NamespaceAll, b.switchNamespaceCmd, true) + b.namespaces[0] = client.NamespaceAll index := 1 for _, n := range b.app.Config.FavNamespaces() { - if n == render.NamespaceAll { + if n == client.NamespaceAll { continue } aa[tcell.Key(ui.NumKeys[index])] = ui.NewKeyAction(n, b.switchNamespaceCmd, true) @@ -417,14 +407,19 @@ func (b *Browser) simpleDelete(selections []string, msg string) { b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) } for _, sel := range selections { - if err := b.accessor.(dao.Nuker).Delete(sel, true, true); err != nil { + log.Debug().Msgf("YO!! %#v", b.accessor) + nuker, ok := b.accessor.(dao.Nuker) + if !ok { + b.app.Flash().Errf("Invalid nuker %T", b.accessor) + return + } + if err := nuker.Delete(sel, true, true); err != nil { b.app.Flash().Errf("Delete failed with `%s", err) } else { b.GetTable().DeleteMark(sel) } } b.refresh() - b.SelectRow(1, true) }, func() {}) } @@ -437,14 +432,14 @@ func (b *Browser) resourceDelete(selections []string, msg string) { b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) } for _, sel := range selections { - if err := b.accessor.(dao.Nuker).Delete(sel, cascade, force); err != nil { + if err := b.GetModel().Delete(b.defaultContext(), sel, cascade, force); err != nil { b.app.Flash().Errf("Delete failed with `%s", err) } else { + b.app.Flash().Infof("%s `%s deleted successfully", b.GVR(), sel) b.app.factory.DeleteForwarder(sel) b.GetTable().DeleteMark(sel) } } b.refresh() - b.SelectRow(1, true) }, func() {}) } diff --git a/internal/view/charts.go b/internal/view/charts.go new file mode 100644 index 00000000..29946367 --- /dev/null +++ b/internal/view/charts.go @@ -0,0 +1,53 @@ +package view + +import ( + "context" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/ui" + "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" +) + +// Chart represents a helm chart view. +type Chart struct { + ResourceViewer +} + +// NewChart returns a new alias view. +func NewChart(gvr client.GVR) ResourceViewer { + c := Chart{ + ResourceViewer: NewBrowser(gvr), + } + c.GetTable().SetColorerFn(render.Chart{}.ColorerFunc()) + c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen) + c.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone) + c.SetBindKeysFn(c.bindKeys) + c.SetContextFn(c.chartContext) + + return &c +} + +func (c *Chart) chartContext(ctx context.Context) context.Context { + return ctx +} + +func (c *Chart) bindKeys(aa ui.KeyActions) { + aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) + aa.Add(ui.KeyActions{ + ui.KeyB: ui.NewKeyAction("Blee", c.bleeCmd, true), + ui.KeyShiftN: ui.NewKeyAction("Sort Name", c.GetTable().SortColCmd(0, true), false), + ui.KeyShiftS: ui.NewKeyAction("Sort Status", c.GetTable().SortColCmd(2, true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort Age", c.GetTable().SortColCmd(-1, true), false), + }) +} + +func (c *Chart) bleeCmd(evt *tcell.EventKey) *tcell.EventKey { + path := c.GetTable().GetSelectedItem() + if path == "" { + return nil + } + log.Debug().Msgf("BLEE CMD %q", path) + return nil +} diff --git a/internal/view/command.go b/internal/view/command.go index b9b6345f..296af938 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -116,17 +116,17 @@ func (c *Command) specialCmd(cmd string) bool { } func (c *Command) viewMetaFor(cmd string) (string, *MetaViewer, error) { - gvr, ok := c.alias.Get(cmd) + gvr, ok := c.alias.AsGVR(cmd) if !ok { return "", nil, fmt.Errorf("Huh? `%s` Command not found", cmd) } - v, ok := customViewers[client.NewGVR(gvr)] + v, ok := customViewers[gvr] if !ok { - return gvr, &MetaViewer{viewerFn: NewBrowser}, nil + return gvr.String(), &MetaViewer{viewerFn: NewBrowser}, nil } - return gvr, &v, nil + return gvr.String(), &v, nil } func (c *Command) componentFor(gvr string, v *MetaViewer) ResourceViewer { diff --git a/internal/view/container.go b/internal/view/container.go index d9d7163e..7b24fc81 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -63,7 +63,7 @@ func (c *Container) selectedContainer() string { return tokens[0] } -func (c *Container) viewLogs(app *App, ns, res, path string) { +func (c *Container) viewLogs(app *App, model ui.Tabular, gvr, path string) { status := c.GetTable().GetSelectedCell(3) if status != "Running" && status != "Completed" { app.Flash().Err(errors.New("No logs available")) diff --git a/internal/view/context.go b/internal/view/context.go index d62b36d7..a452397b 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -32,8 +32,8 @@ func (c *Context) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) } -func (c *Context) useCtx(app *App, _, res, path string) { - log.Debug().Msgf("SWITCH CTX %q--%q", res, path) +func (c *Context) useCtx(app *App, model ui.Tabular, gvr, path string) { + log.Debug().Msgf("SWITCH CTX %q--%q", gvr, path) if err := c.useContext(path); err != nil { app.Flash().Err(err) return diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index 56ac377f..52c512b3 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -32,8 +32,8 @@ func NewCronJob(gvr client.GVR) ResourceViewer { return &c } -func (c *CronJob) showJobs(app *App, ns, gvr, path string) { - log.Debug().Msgf("Showing Jobs %q:%q -- %q", ns, gvr, path) +func (c *CronJob) showJobs(app *App, model ui.Tabular, 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()) if err != nil { app.Flash().Err(err) diff --git a/internal/view/dp.go b/internal/view/dp.go index 26d7074b..aa9bb793 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -40,7 +40,7 @@ func (d *Deploy) bindKeys(aa ui.KeyActions) { }) } -func (d *Deploy) showPods(app *App, _, _, path string) { +func (d *Deploy) showPods(app *App, model ui.Tabular, gvr, path string) { o, err := app.factory.Get(d.GVR(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) diff --git a/internal/view/ds.go b/internal/view/ds.go index f3e6b863..c18028f2 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -39,7 +39,7 @@ func (d *DaemonSet) bindKeys(aa ui.KeyActions) { }) } -func (d *DaemonSet) showPods(app *App, _, _, path string) { +func (d *DaemonSet) showPods(app *App, model ui.Tabular, _, path string) { o, err := app.factory.Get(d.GVR(), path, true, labels.Everything()) if err != nil { d.App().Flash().Err(err) diff --git a/internal/view/helpers.go b/internal/view/helpers.go index a1d24e95..e8a14638 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -1,7 +1,6 @@ package view import ( - "bytes" "context" "errors" "fmt" @@ -11,12 +10,10 @@ 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/render" + "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/printers" ) func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv { @@ -60,9 +57,11 @@ func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv { return env } -func describeResource(app *App, _, gvr, path string) { - ns, n := client.Namespaced(path) - yaml, err := dao.Describe(app.Conn(), client.NewGVR(gvr), ns, n) +func describeResource(app *App, model ui.Tabular, gvr, path string) { + ctx := context.Background() + ctx = context.WithValue(ctx, internal.KeyFactory, app.factory) + + yaml, err := model.Describe(ctx, path) if err != nil { app.Flash().Errf("Describe command failed: %s", err) return @@ -74,20 +73,6 @@ func describeResource(app *App, _, gvr, path string) { } } -func toYAML(o runtime.Object) (string, error) { - var ( - buff bytes.Buffer - p printers.YAMLPrinter - ) - err := p.PrintObj(o, &buff) - if err != nil { - log.Error().Msgf("Marshal Error %v", err) - return "", err - } - - return buff.String(), nil -} - func showPodsWithLabels(app *App, path string, sel map[string]string) { var labels []string for k, v := range sel { diff --git a/internal/view/job.go b/internal/view/job.go index 4789db38..9cf43cfe 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -3,6 +3,7 @@ package view import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/ui" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -23,7 +24,7 @@ func NewJob(gvr client.GVR) ResourceViewer { return &j } -func (*Job) showPods(app *App, _, gvr, path string) { +func (*Job) showPods(app *App, model ui.Tabular, gvr, path string) { o, err := app.factory.Get(gvr, path, true, labels.Everything()) if err != nil { app.Flash().Err(err) diff --git a/internal/view/node.go b/internal/view/node.go index 7a66d420..b204250c 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -5,6 +5,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" @@ -49,8 +50,8 @@ func (n *Node) nodeContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyMetrics, nmx) } -func (n *Node) showPods(app *App, ns, res, sel string) { - showPods(app, n.GetTable().GetSelectedItem(), "", "spec.nodeName="+sel) +func (n *Node) showPods(app *App, _ ui.Tabular, _, path string) { + showPods(app, n.GetTable().GetSelectedItem(), "", "spec.nodeName="+path) } func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -67,7 +68,7 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - raw, err := toYAML(o) + raw, err := dao.ToYAML(o) if err != nil { n.App().Flash().Errf("Unable to marshal resource %s", err) return nil diff --git a/internal/view/ns.go b/internal/view/ns.go index 2d163857..dcc95be4 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -38,8 +38,8 @@ func (n *Namespace) bindKeys(aa ui.KeyActions) { }) } -func (n *Namespace) switchNs(app *App, _, res, sel string) { - n.useNamespace(sel) +func (n *Namespace) switchNs(app *App, model ui.Tabular, gvr, path string) { + n.useNamespace(path) if err := app.gotoResource("pods", true); err != nil { app.Flash().Err(err) } @@ -74,13 +74,13 @@ func (n *Namespace) decorate(data render.TableData) render.TableData { } // checks if all ns is in the list if not add it. - if _, ok := data.RowEvents.FindIndex(render.NamespaceAll); !ok { + if _, ok := data.RowEvents.FindIndex(client.NamespaceAll); !ok { data.RowEvents = append(data.RowEvents, render.RowEvent{ Kind: render.EventUnchanged, Row: render.Row{ - ID: render.NamespaceAll, - Fields: render.Fields{render.NamespaceAll, "Active", "0"}, + ID: client.NamespaceAll, + Fields: render.Fields{client.NamespaceAll, "Active", "0"}, }, }, ) diff --git a/internal/view/pod.go b/internal/view/pod.go index 978ad0c7..395fbb00 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -53,8 +53,8 @@ func (p *Pod) bindKeys(aa ui.KeyActions) { }) } -func (p *Pod) showContainers(app *App, ns, gvr, path string) { - log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path) +func (p *Pod) showContainers(app *App, model ui.Tabular, gvr, path string) { + log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, model.GetNamespace(), path) co := NewContainer(client.NewGVR("containers")) co.SetContextFn(p.coContext) if err := app.inject(co); err != nil { diff --git a/internal/view/policy.go b/internal/view/policy.go index 2245fa08..85c9789b 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -32,7 +32,7 @@ func NewPolicy(app *App, subject, name string) *Policy { } p.GetTable().SetColorerFn(render.Policy{}.ColorerFunc()) p.SetBindKeysFn(p.bindKeys) - p.GetTable().SetSortCol(1, len(render.Policy{}.Header(render.AllNamespaces)), false) + p.GetTable().SetSortCol(1, len(render.Policy{}.Header(client.AllNamespaces)), false) p.SetContextFn(p.subjectCtx) p.GetTable().SetEnterFn(blankEnterFn) diff --git a/internal/view/rbac.go b/internal/view/rbac.go index e1068a55..ccc25535 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -22,7 +22,7 @@ func NewRbac(gvr client.GVR) ResourceViewer { } r.GetTable().SetColorerFn(render.Rbac{}.ColorerFunc()) r.SetBindKeysFn(r.bindKeys) - r.GetTable().SetSortCol(1, len(render.Rbac{}.Header(render.ClusterScope)), true) + r.GetTable().SetSortCol(1, len(render.Rbac{}.Header(client.ClusterScope)), true) r.GetTable().SetEnterFn(blankEnterFn) return &r @@ -35,7 +35,7 @@ func (r *Rbac) bindKeys(aa ui.KeyActions) { }) } -func showRules(app *App, _, gvr, path string) { +func showRules(app *App, _ ui.Tabular, gvr, path string) { v := NewRbac(client.NewGVR("rbac")) v.SetContextFn(rbacCtxt(gvr, path)) @@ -51,4 +51,4 @@ func rbacCtxt(gvr, path string) ContextFunc { } } -func blankEnterFn(_ *App, _, _, _ string) {} +func blankEnterFn(_ *App, _ ui.Tabular, _, _ string) {} diff --git a/internal/view/registrar.go b/internal/view/registrar.go index bf57df4b..40d228a9 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -4,21 +4,29 @@ import ( "strings" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/ui" ) func loadCustomViewers() MetaViewers { m := make(MetaViewers, 30) - coreRes(m) - miscRes(m) - appsRes(m) - rbacRes(m) - batchRes(m) - extRes(m) + coreViewers(m) + miscViewers(m) + appsViewers(m) + rbacViewers(m) + batchViewers(m) + extViewers(m) + helmViewers(m) return m } -func coreRes(vv MetaViewers) { +func helmViewers(vv MetaViewers) { + vv[client.NewGVR("charts")] = MetaViewer{ + viewerFn: NewChart, + } +} + +func coreViewers(vv MetaViewers) { vv[client.NewGVR("v1/namespaces")] = MetaViewer{ viewerFn: NewNamespace, } @@ -39,7 +47,7 @@ func coreRes(vv MetaViewers) { } } -func miscRes(vv MetaViewers) { +func miscViewers(vv MetaViewers) { vv[client.NewGVR("contexts")] = MetaViewer{ viewerFn: NewContext, } @@ -60,7 +68,7 @@ func miscRes(vv MetaViewers) { } } -func appsRes(vv MetaViewers) { +func appsViewers(vv MetaViewers) { vv[client.NewGVR("apps/v1/deployments")] = MetaViewer{ viewerFn: NewDeploy, } @@ -78,7 +86,7 @@ func appsRes(vv MetaViewers) { } } -func rbacRes(vv MetaViewers) { +func rbacViewers(vv MetaViewers) { vv[client.NewGVR("rbac")] = MetaViewer{ enterFn: showRules, } @@ -102,7 +110,7 @@ func rbacRes(vv MetaViewers) { } } -func batchRes(vv MetaViewers) { +func batchViewers(vv MetaViewers) { vv[client.NewGVR("batch/v1beta1/cronjobs")] = MetaViewer{ viewerFn: NewCronJob, } @@ -111,7 +119,7 @@ func batchRes(vv MetaViewers) { } } -func extRes(vv MetaViewers) { +func extViewers(vv MetaViewers) { vv[client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")] = MetaViewer{ enterFn: showCRD, } @@ -120,7 +128,7 @@ func extRes(vv MetaViewers) { } } -func showCRD(app *App, ns, gvr, path string) { +func showCRD(app *App, _ ui.Tabular, _, path string) { _, crdGVR := client.Namespaced(path) tokens := strings.Split(crdGVR, ".") if err := app.gotoResource(tokens[0], false); err != nil { diff --git a/internal/view/rs.go b/internal/view/rs.go index ec28519c..eb8d082c 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -48,7 +48,7 @@ func (r *ReplicaSet) bindKeys(aa ui.KeyActions) { }) } -func (r *ReplicaSet) showPods(app *App, _, gvr, path string) { +func (r *ReplicaSet) showPods(app *App, model ui.Tabular, gvr, path string) { o, err := app.factory.Get(r.GVR(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 50c13cec..41b82088 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -9,6 +9,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/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" ) @@ -39,7 +40,7 @@ func (s *ScreenDump) dirContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyDir, dir) } -func (s *ScreenDump) edit(app *App, ns, resource, path string) { +func (s *ScreenDump) edit(app *App, model ui.Tabular, gvr, path string) { log.Debug().Msgf("ScreenDump selection is %q", path) s.Stop() diff --git a/internal/view/sts.go b/internal/view/sts.go index 762d1c2b..01930512 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -37,7 +37,7 @@ func (s *StatefulSet) bindKeys(aa ui.KeyActions) { }) } -func (s *StatefulSet) showPods(app *App, _, gvr, path string) { +func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _, path string) { sts, err := s.sts(path) if err != nil { app.Flash().Err(err) diff --git a/internal/view/svc.go b/internal/view/svc.go index 2454846e..e6b073ed 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -46,7 +46,7 @@ func (s *Service) bindKeys(aa ui.KeyActions) { }) } -func (s *Service) showPods(app *App, ns, gvr, path string) { +func (s *Service) showPods(app *App, _ ui.Tabular, gvr, path string) { o, err := app.factory.Get(gvr, path, true, labels.Everything()) if err != nil { app.Flash().Err(err) diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 86993479..e4e4683c 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -32,7 +33,7 @@ func computeFilename(cluster, ns, title, path string) (string, error) { } var fName string - if ns == render.ClusterScope { + if ns == client.ClusterScope { fName = fmt.Sprintf(ui.NoNSFmat, name, now) } else { fName = fmt.Sprintf(ui.FullFmat, name, ns, now) @@ -43,8 +44,8 @@ func computeFilename(cluster, ns, title, path string) (string, error) { func saveTable(cluster, title, path string, data render.TableData) (string, error) { ns := data.Namespace - if ns == render.ClusterScope { - ns = render.NamespaceAll + if client.IsClusterWide(ns) { + ns = client.NamespaceAll } fPath, err := computeFilename(cluster, ns, title, path) diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index 4991645c..bf394b28 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -98,9 +98,19 @@ func (t *testTableModel) GetNamespace() string { return "blee" } func (t *testTableModel) SetNamespace(string) {} func (t *testTableModel) AddListener(model.TableListener) {} func (t *testTableModel) Watch(context.Context) {} -func (t *testTableModel) Get(ctx context.Context, path string) (runtime.Object, error) { +func (t *testTableModel) Get(context.Context, string) (runtime.Object, error) { return nil, nil } +func (t *testTableModel) Delete(context.Context, string, bool, bool) error { + return nil +} +func (t *testTableModel) Describe(context.Context, string) (string, error) { + return "", nil +} +func (t *testTableModel) ToYAML(ctx context.Context, path string) (string, error) { + return "", nil +} + func (t *testTableModel) InNamespace(string) bool { return true } func (t *testTableModel) SetRefreshRate(time.Duration) {} diff --git a/internal/view/types.go b/internal/view/types.go index 486847a5..2bc75128 100644 --- a/internal/view/types.go +++ b/internal/view/types.go @@ -16,7 +16,7 @@ type ( BoostActionsFunc func(ui.KeyActions) // EnterFunc represents an enter key action. - EnterFunc func(app *App, ns, resource, selection string) + EnterFunc func(app *App, model ui.Tabular, gvr, path string) // ContainerFunc returns the active container name. ContainerFunc func() string diff --git a/internal/watch/factory.go b/internal/watch/factory.go index ba382c44..9f526a01 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -61,7 +61,7 @@ func (f *Factory) Terminate() { } // List returns a resource collection. -func (f *Factory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { +func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) { if ns == clusterScope { ns = allNamespaces } @@ -73,17 +73,18 @@ func (f *Factory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtim if wait { f.waitForCacheSync(ns) } + if client.IsClusterScoped(ns) { + return inf.Lister().List(labels) + } - return inf.Lister().ByNamespace(ns).List(sel) + return inf.Lister().ByNamespace(ns).List(labels) } // Get retrieves a given resource. func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { ns, n := namespaced(path) - if ns == clusterScope { - ns = allNamespaces - } log.Debug().Msgf("GET %q:%q::%q", ns, gvr, n) + Debug(f, "", gvr) inf, err := f.CanForResource(ns, gvr, []string{"get"}) if err != nil { return nil, err @@ -91,6 +92,9 @@ func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime if wait { f.waitForCacheSync(ns) } + if client.IsClusterScoped(ns) { + return inf.Lister().Get(n) + } return inf.Lister().ByNamespace(ns).Get(n) } @@ -131,7 +135,6 @@ func (f *Factory) FactoryFor(ns string) di.DynamicSharedInformerFactory { } // SetActiveNS sets the active namespace. -// BOZO!! Check ns access for resource?? func (f *Factory) SetActiveNS(ns string) { if !f.isClusterWide() { f.ensureFactory(ns)