added subcommands

mine
derailed 2019-10-01 23:34:52 -06:00
parent 5baa447e46
commit 92a5d2a636
23 changed files with 325 additions and 124 deletions

7
go.mod
View File

@ -27,10 +27,8 @@ replace (
)
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
github.com/atotto/clipboard v0.1.2
github.com/derailed/tview v0.2.1
github.com/derailed/tview v0.2.4
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
@ -44,7 +42,7 @@ require (
github.com/imdario/mergo v0.3.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.4
github.com/petergtz/pegomock v2.5.0+incompatible
github.com/petergtz/pegomock v2.6.0+incompatible
github.com/rakyll/hey v0.1.2
github.com/rs/zerolog v1.14.3
github.com/sahilm/fuzzy v0.1.0
@ -52,7 +50,6 @@ require (
github.com/stretchr/testify v1.3.0
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.2.0+incompatible

27
go.sum
View File

@ -39,8 +39,6 @@ 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/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
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=
@ -89,8 +87,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/derailed/tview v0.2.1 h1:3UjeNni+Q94WmmVnc8tLSTZab5NuRVrwolghCiXCtSs=
github.com/derailed/tview v0.2.1/go.mod h1:aDhJBLLf7pXbkaNmVroSvsjiP8ry6sfBVWNHTz6klZw=
github.com/derailed/tview v0.2.2 h1:APBixkqfHz3fL+XJ/bjxZivvd8HUtrXVcTUb/qu6wR0=
github.com/derailed/tview v0.2.2/go.mod h1:rFq1AIpLyyo8ilN1vG//2YnXmHg6szt4zIdg1ftJx9E=
github.com/derailed/tview v0.2.3 h1:bvoF230amMmzrwDYTHnToSLjemeCp6shmBJ+lnJRiAA=
github.com/derailed/tview v0.2.3/go.mod h1:rFq1AIpLyyo8ilN1vG//2YnXmHg6szt4zIdg1ftJx9E=
github.com/derailed/tview v0.2.4 h1:txiIdBLeSH1JV+9uP7hfuZAQBt1qJ+iDasmDCtVsvk0=
github.com/derailed/tview v0.2.4/go.mod h1:rFq1AIpLyyo8ilN1vG//2YnXmHg6szt4zIdg1ftJx9E=
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=
@ -112,7 +114,6 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb
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/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
@ -124,6 +125,7 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
github.com/gdamore/tcell v1.1.2 h1:Afe8cU6SECC06UmvaJ55Jr3Eh0tz/ywLjqWYqjGZp3s=
github.com/gdamore/tcell v1.1.2/go.mod h1:h3kq4HO9l2On+V9ed8w8ewqQEmGCSSHOgQ+2h8uzurE=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -175,7 +177,6 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+
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/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -211,7 +212,6 @@ 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/gophercloud/gophercloud v0.0.0-20190427020117-60507118a582/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
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=
@ -251,7 +251,6 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
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/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=
@ -273,6 +272,7 @@ github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -331,8 +331,8 @@ github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/petergtz/pegomock v2.5.0+incompatible h1:NgwX1/qc+tsl7I45OkDxYZ1mIonYWbOESnpZcd20sR0=
github.com/petergtz/pegomock v2.5.0+incompatible/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o=
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/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=
@ -373,7 +373,6 @@ github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
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/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=
@ -416,7 +415,6 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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=
@ -446,7 +444,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
@ -485,7 +482,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
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/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-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=
@ -517,8 +513,8 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
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/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
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=
@ -569,7 +565,6 @@ k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1/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/kube-aggregator v0.0.0-20190918161219-8c8f079fddc3/go.mod h1:NJisPUqwlg1A99RhO1BTnNtwC4pKUyXJ2f3Xc4PxKQg=

View File

@ -26,20 +26,6 @@ const NA = "n/a"
var supportedMetricsAPIVersions = []string{"v1beta1"}
type (
// GKV tracks api resource version info.
GKV struct {
Group, Kind, Version string
}
// APIGroup represents a K8s resource descriptor.
APIGroup struct {
GKV
Resource string
Plural, Singular string
Aliases []string
}
// Collection of empty interfaces.
Collection []interface{}
@ -72,7 +58,7 @@ type (
CurrentNamespaceName() (string, error)
CheckNSAccess(ns string) error
CheckListNSAccess() error
CanIAccess(ns, resURL string, verbs []string) (bool, error)
CanIAccess(ns, rvg string, verbs []string) (bool, error)
}
k8sClient struct {
@ -121,12 +107,12 @@ func (a *APIClient) CheckNSAccess(n string) error {
return err
}
func makeSAR(ns, resURL string) *authorizationv1.SelfSubjectAccessReview {
gvr, _ := schema.ParseResourceArg(strings.ToLower(resURL))
func makeSAR(ns, rvg string) *authorizationv1.SelfSubjectAccessReview {
gvr, _ := schema.ParseResourceArg(strings.ToLower(rvg))
if gvr == nil {
panic(fmt.Errorf("Unable to get GVR from url %s", resURL))
panic(fmt.Errorf("Unable to get GVR from url %s", rvg))
}
log.Debug().Msgf("GVR for %s -- %#v", resURL, *gvr)
log.Debug().Msgf("GVR for %s -- %#v", rvg, *gvr)
return &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
@ -139,8 +125,8 @@ func makeSAR(ns, resURL string) *authorizationv1.SelfSubjectAccessReview {
}
// CanIAccess checks if user has access to a certain resource.
func (a *APIClient) CanIAccess(ns, resURL string, verbs []string) (bool, error) {
sar := makeSAR(ns, resURL)
func (a *APIClient) CanIAccess(ns, rvg string, verbs []string) (bool, error) {
sar := makeSAR(ns, rvg)
dial := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews()
for _, v := range verbs {
sar.Spec.ResourceAttributes.Verb = v
@ -210,7 +196,7 @@ func (a *APIClient) IsNamespaced(res string) bool {
func (a *APIClient) SupportsResource(group string) bool {
list, err := a.DialOrDie().Discovery().ServerPreferredResources()
if err != nil {
log.Debug().Err(err).Msg("Unable to dial api server")
log.Error().Err(err).Msg("Unable to dial api server")
return false
}
for _, l := range list {
@ -294,7 +280,7 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) {
}
var err error
if a.mxsClient, err = versioned.NewForConfig(a.RestConfigOrDie()); err != nil {
a.log.Debug().Err(err)
a.log.Error().Err(err)
}
return a.mxsClient, err

View File

@ -33,6 +33,15 @@ func (g GVR) AsGR() schema.GroupVersion {
}
}
// AsGVR returns a schema gvr instance.
func (g GVR) AsGVR() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: g.ToG(),
Version: g.ToV(),
Resource: g.ToR(),
}
}
// String returns a GVR as a string.
func (g GVR) String() string {
return string(g)
@ -41,6 +50,9 @@ func (g GVR) String() string {
// ToV returns the resource version.
func (g GVR) ToV() string {
tokens := strings.Split(string(g), "/")
if len(tokens) < 2 {
return ""
}
return tokens[len(tokens)-2]
}

146
internal/k8s/gvr_test.go Normal file
View File

@ -0,0 +1,146 @@
package k8s_test
import (
"testing"
"github.com/derailed/k9s/internal/k8s"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestAsGR(t *testing.T) {
uu := map[string]struct {
gvr string
e schema.GroupVersion
}{
"full": {"apps/v1/deployments", schema.GroupVersion{"apps", "v1"}},
"core": {"v1/pods", schema.GroupVersion{"", "v1"}},
"bork": {"users", schema.GroupVersion{"", ""}},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).AsGR())
})
}
}
func TestNewGVR(t *testing.T) {
uu := map[string]struct {
g, v, r string
e string
}{
"full": {"apps", "v1", "deployments", "apps/v1/deployments"},
"core": {"", "v1", "pods", "v1/pods"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.NewGVR(u.g, u.v, u.r).String())
})
}
}
func TestToGVR(t *testing.T) {
uu := map[string]struct {
gv, r, e string
}{
"full": {"apps/v1", "deployments", "apps/v1/deployments"},
"core": {"v1", "pods", "v1/pods"},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.ToGVR(u.gv, u.r).String())
})
}
}
func TestResName(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments.v1.apps"},
"core": {"v1/pods", "pods.v1."},
"k9s": {"users", "users.."},
"empty": {"", ".."},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ResName())
})
}
}
func TestToR(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "deployments"},
"core": {"v1/pods", "pods"},
"k9s": {"users", "users"},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToR())
})
}
}
func TestToG(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "apps"},
"core": {"v1/pods", ""},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToG())
})
}
}
func TestToV(t *testing.T) {
uu := map[string]struct {
gvr string
e string
}{
"full": {"apps/v1/deployments", "v1"},
"core": {"v1beta1/pods", "v1beta1"},
"k9s": {"users", ""},
"empty": {"", ""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToV())
})
}
}
func TestToStringer(t *testing.T) {
uu := map[string]struct {
gvr string
}{
"full": {"apps/v1/deployments"},
"core": {"v1beta1/pods"},
"k9s": {"users"},
"empty": {""},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, k8s.GVR(u.gvr).String())
})
}
}

View File

@ -20,7 +20,7 @@ func (i *Ingress) Get(ns, n string) (interface{}, error) {
return i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).Get(n, metav1.GetOptions{})
}
// List all Ingresss in a given namespace.
// List all Ingresses in a given namespace.
func (i *Ingress) List(ns string) (Collection, error) {
opts := metav1.ListOptions{
LabelSelector: i.labelSelector,

View File

@ -6,7 +6,6 @@ import (
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/dynamic"
"k8s.io/client-go/rest"
@ -31,12 +30,7 @@ func (r *Resource) GetInfo() GVR {
}
func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
g := schema.GroupVersionResource{
Group: r.gvr.ToG(),
Version: r.gvr.ToV(),
Resource: r.gvr.ToR(),
}
return r.DynDialOrDie().Resource(g)
return r.DynDialOrDie().Resource(r.gvr.AsGVR())
}
// Get a Resource.

View File

@ -133,7 +133,7 @@ func (b *Base) Describe(gvr, pa string) (string, error) {
mapper := k8s.RestMapper{Connection: b.Connection}
mapping, err := mapper.ResourceFor(k8s.GVR(gvr).ResName())
if err != nil {
log.Debug().Err(err).Msgf("Unable to find mapper for %s %s", gvr, pa)
log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, pa)
return "", err
}
ns, n := Namespaced(pa)

View File

@ -203,22 +203,58 @@ func (a *appView) refreshIndicator() {
a.indicator().SetPermanent(info)
}
func (a *appView) startInformer(ns string) {
func (a *appView) switchNS(ns string) bool {
if ns == a.Config.ActiveNamespace() {
log.Debug().Msgf("Namespace did not change %s", ns)
return true
}
a.Config.SetActiveNamespace(ns)
return a.startInformer(ns)
}
func (a *appView) switchCtx(ctx string, load bool) error {
l := resource.NewContext(a.Conn())
if err := l.Switch(ctx); err != nil {
return err
}
a.stopForwarders()
ns, err := a.Conn().Config().CurrentNamespaceName()
if err != nil {
log.Info().Err(err).Msg("No namespace specified using all namespaces")
}
a.startInformer(ns)
a.Config.Reset()
a.Config.Save()
a.Flash().Infof("Switching context to %s", ctx)
if load {
a.gotoResource("po", true)
}
return nil
}
func (a *appView) startInformer(ns string) bool {
if a.stopCh != nil {
close(a.stopCh)
a.stopCh = nil
}
var err error
a.stopCh = make(chan struct{})
a.informer, err = watch.NewInformer(a.Conn(), ns)
if err != nil {
log.Panic().Err(err).Msgf("%v", err)
log.Error().Err(err).Msgf("%v", err)
a.Flash().Err(err)
return false
}
a.stopCh = make(chan struct{})
a.informer.Run(a.stopCh)
if a.Config.K9s.GetHeadless() {
a.refreshIndicator()
}
return true
}
// BailOut exists the application.
@ -340,9 +376,9 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
if _, ok := a.Frame().GetPrimitive("main").(*helpView); ok {
return evt
}
h := newHelpView(a, a.ActiveView(), a.GetHints())
a.inject(h)
return nil
}
@ -350,7 +386,6 @@ func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
if _, ok := a.Frame().GetPrimitive("main").(*aliasView); ok {
return evt
}
a.inject(newAliasView(a, a.ActiveView()))
return nil

View File

@ -3,6 +3,7 @@ package views
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/derailed/k9s/internal/k8s"
@ -55,18 +56,22 @@ func (c *command) defaultCmd() {
var policyMatcher = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`)
func (c *command) isCustCmd(cmd string) bool {
switch {
case cmd == "q", cmd == "quit":
func (c *command) isK9sCmd(cmd string) bool {
cmds := strings.Split(cmd, " ")
switch cmds[0] {
case "q", "quit":
c.app.BailOut()
return true
case cmd == "?", cmd == "help":
case "?", "help":
c.app.helpCmd(nil)
return true
case cmd == "alias":
case "alias":
c.app.aliasCmd(nil)
return true
case policyMatcher.MatchString(cmd):
default:
if !policyMatcher.MatchString(cmd) {
return false
}
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
if len(tokens) == 1 && len(tokens[0]) == 3 {
c.app.inject(newPolicyView(c.app, tokens[0][1], tokens[0][2]))
@ -76,36 +81,72 @@ func (c *command) isCustCmd(cmd string) bool {
return false
}
// Exec the command by showing associated display.
func (c *command) run(cmd string) bool {
defer func(t time.Time) {
log.Debug().Msgf("RUN CMD Elapsed %v", time.Since(t))
}(time.Now())
if c.isCustCmd(cmd) {
return true
}
vv := make(viewers, 200)
// load scrape api for resources and populate aliases.
func (c *command) load() viewers {
vv := make(viewers, 100)
resourceViews(c.app.Conn(), vv)
allCRDs(c.app.Conn(), vv)
return vv
}
func (c *command) viewMetaFor(cmd string) (string, *viewer) {
vv := c.load()
gvr, ok := aliases.Get(cmd)
if !ok {
log.Error().Err(fmt.Errorf("Huh? `%s` command not found", cmd)).Msg("Command Failed")
c.app.Flash().Warnf("Huh? `%s` command not found", cmd)
return false
return "", nil
}
v, ok := vv[gvr]
if !ok {
log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", cmd)).Msg("Viewer Failed")
c.app.Flash().Warnf("Huh? `%s` viewer not found", gvr)
return false
return "", nil
}
return c.execCmd(gvr, v)
return gvr, &v
}
func (c *command) execCmd(gvr string, v viewer) bool {
log.Debug().Msgf("ExecCmd gvr %s", gvr)
// Exec the command by showing associated display.
func (c *command) run(cmd string) bool {
log.Debug().Msgf("Running command %v", cmd)
defer func(t time.Time) {
log.Debug().Msgf("RUN CMD Elapsed %v", time.Since(t))
}(time.Now())
if c.isK9sCmd(cmd) {
return true
}
cmds := strings.Split(cmd, " ")
gvr, v := c.viewMetaFor(cmds[0])
if v == nil {
return false
}
switch cmds[0] {
case "ctx", "context", "contexts":
if len(cmds) == 2 {
c.app.switchCtx(cmds[1], true)
return true
}
view := c.viewerFor(gvr, v)
return c.exec(gvr, "", view)
default:
ns := c.app.Config.ActiveNamespace()
if len(cmds) == 2 {
ns = cmds[1]
}
if !c.app.switchNS(ns) {
return false
}
return c.exec(gvr, ns, c.viewerFor(gvr, v))
}
return false
}
func (c *command) viewerFor(gvr string, v *viewer) resourceViewer {
var r resource.List
if v.listFn != nil {
r = v.listFn(c.app.Conn(), resource.DefaultNamespace)
@ -127,10 +168,10 @@ func (c *command) execCmd(gvr string, v viewer) bool {
view.setDecorateFn(v.decorateFn)
}
return c.exec(gvr, view)
return view
}
func (c *command) exec(gvr string, v ui.Igniter) bool {
func (c *command) exec(gvr string, ns string, v ui.Igniter) bool {
if v == nil {
log.Error().Err(fmt.Errorf("No igniter given for %s", gvr))
return false

View File

@ -5,7 +5,6 @@ import (
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/rs/zerolog/log"
)
type contextView struct {
@ -50,18 +49,19 @@ func (v *contextView) useContext(name string) error {
return err
}
v.app.stopForwarders()
ns, err := v.app.Conn().Config().CurrentNamespaceName()
if err != nil {
log.Info().Err(err).Msg("No namespace specified using all namespaces")
}
v.app.startInformer(ns)
v.app.Config.Reset()
v.app.Config.Save()
v.app.Flash().Infof("Switching context to %s", ctx)
v.app.switchCtx(name, false)
// v.app.stopForwarders()
// ns, err := v.app.Conn().Config().CurrentNamespaceName()
// if err != nil {
// log.Info().Err(err).Msg("No namespace specified using all namespaces")
// }
// v.app.startInformer(ns)
// v.app.Config.Reset()
// v.app.Config.Save()
// v.app.Flash().Infof("Switching context to %s", ctx)
v.refresh()
if tv, ok := v.GetPrimitive("ctx").(*tableView); ok {
tv.Select(0, 0)
tv.Select(1, 0)
}
return nil

View File

@ -10,7 +10,7 @@ import (
func TestContextView(t *testing.T) {
l := resource.NewContextList(nil, "fred")
v := newContextView("blee", NewApp(config.NewConfig(ks{})), l).(*contextView)
v := newContextView("blee", "", NewApp(config.NewConfig(ks{})), l).(*contextView)
assert.Equal(t, 10, len(v.hints()))
}

View File

@ -10,7 +10,7 @@ import (
func TestDeployView(t *testing.T) {
l := resource.NewDeploymentList(nil, "fred")
v := newDeployView("blee", NewApp(config.NewConfig(ks{})), l).(*deployView)
v := newDeployView("blee", "", NewApp(config.NewConfig(ks{})), l).(*deployView)
assert.Equal(t, 10, len(v.hints()))
}

View File

@ -10,7 +10,7 @@ import (
func TestDaemonSetView(t *testing.T) {
l := resource.NewDaemonSetList(nil, "fred")
v := newDaemonSetView("blee", NewApp(config.NewConfig(ks{})), l).(*daemonSetView)
v := newDaemonSetView("blee", "", NewApp(config.NewConfig(ks{})), l).(*daemonSetView)
assert.Equal(t, 10, len(v.hints()))
}

View File

@ -50,13 +50,13 @@ func showPods(app *appView, ns, labelSel, fieldSel string, a ui.ActionHandler) {
list.SetLabelSelector(labelSel)
list.SetFieldSelector(fieldSel)
pv := newPodView("Pods", "v1/pods", app, list)
pv := newPodView("Pod", "v1/pods", app, list)
pv.setColorerFn(podColorer)
// pv.setExtraActionsFn(func(aa ui.KeyActions) {
pv.masterPage().SetActions(ui.KeyActions{
tcell.KeyEsc: ui.NewKeyAction("Back", a, true),
})
// Reset active namespace to all.
// Reset active namespace to ns.
app.Config.SetActiveNamespace(ns)
app.inject(pv)
}

View File

@ -56,6 +56,7 @@ func (v *namespaceView) useNamespace(ns string) {
v.app.Flash().Infof("Namespace %s is now active!", ns)
}
v.app.Config.Save()
v.app.startInformer(ns)
}
func (*namespaceView) cleanser(s string) string {

View File

@ -76,7 +76,7 @@ func (v *podView) listContainers(app *appView, _, res, sel string) {
pod := po.(*v1.Pod)
list := resource.NewContainerList(app.Conn(), pod)
title := skinTitle(fmt.Sprintf(containerFmt, "Containers", sel), app.Styles.Frame())
title := skinTitle(fmt.Sprintf(containerFmt, "Container", sel), app.Styles.Frame())
// Stop my updater
if v.cancelFn != nil {

View File

@ -90,7 +90,8 @@ func (v *policyView) getTitle() string {
func (v *policyView) refresh() {
data, err := v.reconcile()
if err != nil {
log.Error().Err(err).Msgf("Unable to reconcile for %s:%s", v.subjectKind, v.subjectName)
log.Error().Err(err).Msgf("Refresh for %s:%s", v.subjectKind, v.subjectName)
v.app.Flash().Err(err)
}
v.Update(data)
}
@ -129,7 +130,7 @@ func (v *policyView) reconcile() (resource.TableData, error) {
evts, errs := v.clusterPolicies()
if len(errs) > 0 {
for _, err := range errs {
log.Debug().Err(err).Msg("Unable to find cluster policies")
log.Error().Err(err).Msg("Unable to find cluster policies")
}
return table, errs[0]
}
@ -137,7 +138,7 @@ func (v *policyView) reconcile() (resource.TableData, error) {
nevts, errs := v.namespacedPolicies()
if len(errs) > 0 {
for _, err := range errs {
log.Debug().Err(err).Msg("Unable to find cluster policies")
log.Error().Err(err).Msg("Unable to find cluster policies")
}
return table, errs[0]
}

View File

@ -136,7 +136,8 @@ func (v *rbacView) getTitle() string {
func (v *rbacView) refresh() {
data, err := v.reconcile(v.ActiveNS(), v.roleName, v.roleType)
if err != nil {
log.Error().Err(err).Msgf("Unable to reconcile for %s:%d", v.roleName, v.roleType)
log.Error().Err(err).Msgf("Refresh for %s:%d", v.roleName, v.roleType)
v.app.Flash().Err(err)
}
v.Update(data)
}

View File

@ -334,8 +334,9 @@ func (v *resourceView) refresh() {
if v.list.Namespaced() {
v.list.SetNamespace(v.currentNS)
}
log.Debug().Msgf("Reconcile with NS %q", v.currentNS)
if err := v.list.Reconcile(v.app.informer, v.path); err != nil {
v.app.Flash().Errf("Reconciliation for %s failed - %s", v.list.GetName(), err)
v.app.Flash().Err(err)
}
data := v.list.Data()
if v.decorateFn != nil {
@ -345,6 +346,11 @@ func (v *resourceView) refresh() {
}
func (v *resourceView) namespaceActions(aa ui.KeyActions) {
ns, err := v.app.Conn().Config().CurrentNamespaceName()
log.Debug().Msgf("NAMESPACE %q -- %v", ns, err)
if err == nil && ns != resource.AllNamespace {
return
}
if !v.list.Access(resource.NamespaceAccess) {
return
}

View File

@ -115,7 +115,8 @@ func (v *subjectView) SetSubject(s string) {
func (v *subjectView) refresh() {
data, err := v.reconcile()
if err != nil {
log.Error().Err(err).Msgf("Unable to reconcile for %s", v.subjectKind)
log.Error().Err(err).Msgf("Refresh for %s", v.subjectKind)
v.app.Flash().Err(err)
}
v.Update(data)
}

View File

@ -209,20 +209,10 @@ func benchTimedOut(app *appView) {
})
}
func (v *svcView) showSvcPods(ns string, sel map[string]string, b ui.ActionHandler) {
func (v *svcView) showSvcPods(ns string, sel map[string]string, a ui.ActionHandler) {
var s []string
for k, v := range sel {
s = append(s, fmt.Sprintf("%s=%s", k, v))
}
list := resource.NewPodList(v.app.Conn(), ns)
list.SetLabelSelector(strings.Join(s, ","))
pv := newPodView("Pods", "v1/pods", v.app, list)
pv.setColorerFn(podColorer)
pv.setExtraActionsFn(func(aa ui.KeyActions) {
aa[tcell.KeyEsc] = ui.NewKeyAction("Back", b, true)
})
// set active namespace to service ns.
v.app.Config.SetActiveNamespace(ns)
v.app.inject(pv)
showPods(v.app, ns, strings.Join(s, ","), "", a)
}

View File

@ -64,17 +64,12 @@ type Informer struct {
// NewInformer creates a new cluster resource informer
func NewInformer(client k8s.Connection, ns string) (*Informer, error) {
i := Informer{client: client, informers: map[string]StoreInformer{}}
if client.CheckListNSAccess() == nil {
i.init(allNamespaces)
return &i, nil
}
if err := client.CheckNSAccess(ns); err != nil {
log.Error().Err(err).Msg("Checking NS Access")
return nil, err
}
i.init(ns)
return &i, nil
}