fix pod xray menu + layout
parent
8299129baf
commit
aa58d1063a
1
go.mod
1
go.mod
|
|
@ -28,6 +28,7 @@ replace (
|
|||
)
|
||||
|
||||
require (
|
||||
fyne.io/fyne v1.2.2 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/derailed/tview v0.3.3
|
||||
|
|
|
|||
26
go.sum
26
go.sum
|
|
@ -3,6 +3,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
fyne.io/fyne v1.2.2 h1:mf7EseASp3CAC5vLWVPLnsoKxvp/ARdu3Seh0HvAQak=
|
||||
fyne.io/fyne v1.2.2/go.mod h1:Ab+3DIB/FVteW0y4DXfmZv4N3JdnCBh2lHkINI02BOU=
|
||||
github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
|
|
@ -29,6 +31,7 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD
|
|||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
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/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
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=
|
||||
|
|
@ -59,6 +62,7 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
|||
github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
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=
|
||||
|
|
@ -212,6 +216,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-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
|
||||
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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=
|
||||
|
|
@ -283,6 +291,8 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09Vjb
|
|||
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/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
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=
|
||||
|
|
@ -366,9 +376,11 @@ 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/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20190124120936-8611f5a5ff3f/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
|
|
@ -460,6 +472,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
|||
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=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
|
|
@ -568,6 +582,10 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
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/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e h1:LJUrNHytcMXWKxnULIHPe5SCb1jDpO9o672VB1x2EuQ=
|
||||
github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e h1:FFotfUvew9Eg02LYRl8YybAnm0HCwjjfY5JlOI1oB00=
|
||||
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
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=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -577,6 +595,7 @@ 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.3.1-0.20190311161405-34c6fa2dc709/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=
|
||||
|
|
@ -630,8 +649,12 @@ golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3
|
|||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk=
|
||||
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/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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=
|
||||
|
|
@ -639,6 +662,8 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -695,6 +720,7 @@ 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-20190626221950-04f50cda93cb/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=
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ func makeCacheKey(ns, gvr string, vv []string) string {
|
|||
return ns + ":" + gvr + "::" + strings.Join(vv, ",")
|
||||
}
|
||||
|
||||
func (a *APIClient) clearCache() {
|
||||
for _, k := range a.cache.Keys() {
|
||||
a.cache.Remove(k)
|
||||
}
|
||||
}
|
||||
|
||||
// CanI checks if user has access to a certain resource.
|
||||
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
|
||||
if IsClusterWide(ns) {
|
||||
|
|
@ -131,6 +137,9 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
|
|||
// BOZO!! No super sure about this approach either??
|
||||
func (a *APIClient) CheckConnectivity() (status bool) {
|
||||
defer func() {
|
||||
if !status {
|
||||
a.clearCache()
|
||||
}
|
||||
if err := recover(); err != nil {
|
||||
status = false
|
||||
}
|
||||
|
|
@ -149,10 +158,10 @@ func (a *APIClient) CheckConnectivity() (status bool) {
|
|||
}
|
||||
}
|
||||
|
||||
if _, err := a.checkClientSet.ServerVersion(); err != nil {
|
||||
log.Error().Err(err).Msgf("K9s can't connect to cluster")
|
||||
} else {
|
||||
if _, err := a.checkClientSet.ServerVersion(); err == nil {
|
||||
status = true
|
||||
} else {
|
||||
log.Error().Err(err).Msgf("K9s can't connect to cluster")
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -258,21 +267,24 @@ func (a *APIClient) MXDial() (*versioned.Clientset, error) {
|
|||
return a.mxsClient, err
|
||||
}
|
||||
|
||||
// SwitchContextOrDie handles kubeconfig context switches.
|
||||
func (a *APIClient) SwitchContextOrDie(ctx string) {
|
||||
// SwitchContext handles kubeconfig context switches.
|
||||
func (a *APIClient) SwitchContext(ctx string) error {
|
||||
currentCtx, err := a.config.CurrentContextName()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Fetching current context")
|
||||
return err
|
||||
}
|
||||
if currentCtx == ctx {
|
||||
return nil
|
||||
}
|
||||
|
||||
if currentCtx != ctx {
|
||||
a.cachedClient = nil
|
||||
a.reset()
|
||||
if err := a.config.SwitchContext(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("Switching context")
|
||||
return err
|
||||
}
|
||||
a.clearCache()
|
||||
a.reset()
|
||||
_ = a.supportsMxServer()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APIClient) reset() {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ func (c *Config) SwitchContext(name string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if _, err := c.GetContext(name); err != nil {
|
||||
return fmt.Errorf("context %s does not exist", name)
|
||||
}
|
||||
|
||||
if currentCtx != name {
|
||||
c.reset()
|
||||
c.flags.Context, c.currentContext = &name, name
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ type Connection interface {
|
|||
|
||||
Config() *Config
|
||||
DialOrDie() kubernetes.Interface
|
||||
SwitchContextOrDie(ctx string)
|
||||
SwitchContext(ctx string) error
|
||||
CachedDiscoveryOrDie() *disk.CachedDiscoveryClient
|
||||
RestConfigOrDie() *restclient.Config
|
||||
MXDial() (*versioned.Clientset, error)
|
||||
|
|
|
|||
|
|
@ -267,12 +267,14 @@ func (mock *MockConnection) SupportsResource(_param0 string) bool {
|
|||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SwitchContextOrDie(_param0 string) {
|
||||
func (mock *MockConnection) SwitchContext(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{})
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mock *MockConnection) ValidNamespaces() ([]v1.Namespace, error) {
|
||||
|
|
|
|||
|
|
@ -389,6 +389,9 @@ func (s *Styles) Update() {
|
|||
|
||||
// AsColor checks color index, if match return color otherwise pink it is.
|
||||
func AsColor(c string) tcell.Color {
|
||||
if c == "default" {
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
if color, ok := tcell.ColorNames[c]; ok {
|
||||
return color
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func makeConn() *conn {
|
|||
|
||||
func (c *conn) Config() *client.Config { return nil }
|
||||
func (c *conn) DialOrDie() kubernetes.Interface { return nil }
|
||||
func (c *conn) SwitchContextOrDie(ctx string) {}
|
||||
func (c *conn) SwitchContext(ctx string) error { return nil }
|
||||
func (c *conn) CachedDiscoveryOrDie() *disk.CachedDiscoveryClient { return nil }
|
||||
func (c *conn) RestConfigOrDie() *restclient.Config { return nil }
|
||||
func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ func (c *Context) MustCurrentContextName() string {
|
|||
|
||||
// Switch to another context.
|
||||
func (c *Context) Switch(ctx string) error {
|
||||
c.Factory.Client().SwitchContextOrDie(ctx)
|
||||
return nil
|
||||
return c.Factory.Client().SwitchContext(ctx)
|
||||
}
|
||||
|
||||
// KubeUpdate modifies kubeconfig default context.
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ type Node struct {
|
|||
|
||||
// List returns a collection of node resources.
|
||||
func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
log.Debug().Msgf("NODE-LIST %q:%q", ns, n.gvr)
|
||||
|
||||
labels, ok := ctx.Value(internal.KeyLabels).(string)
|
||||
if !ok {
|
||||
log.Warn().Msgf("No label selector found in context")
|
||||
|
|
|
|||
|
|
@ -332,12 +332,14 @@ func (mock *MockClusterMeta) SupportsResource(_param0 string) bool {
|
|||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockClusterMeta) SwitchContextOrDie(_param0 string) {
|
||||
func (mock *MockClusterMeta) SwitchContext(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{})
|
||||
pegomock.GetGenericMockFrom(mock).Invoke("SwitchContext", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mock *MockClusterMeta) UserName() (string, error) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package model
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -75,10 +76,15 @@ func (t *Table) RemoveListener(l TableListener) {
|
|||
|
||||
// Watch initiates model updates.
|
||||
func (t *Table) Watch(ctx context.Context) {
|
||||
t.Refresh(ctx)
|
||||
t.refresh(ctx)
|
||||
go t.updater(ctx)
|
||||
}
|
||||
|
||||
// Refresh updates the table content.
|
||||
func (t *Table) Refresh(ctx context.Context) {
|
||||
t.refresh(ctx)
|
||||
}
|
||||
|
||||
// Get returns a resource instance if found, else an error.
|
||||
func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||
meta, err := t.getMeta(ctx)
|
||||
|
|
@ -134,11 +140,6 @@ func (t *Table) ToYAML(ctx context.Context, path string) (string, error) {
|
|||
return desc.ToYAML(path)
|
||||
}
|
||||
|
||||
// Refresh update the model now.
|
||||
func (t *Table) Refresh(ctx context.Context) {
|
||||
t.refresh(ctx)
|
||||
}
|
||||
|
||||
// GetNamespace returns the model namespace.
|
||||
func (t *Table) GetNamespace() string {
|
||||
return t.namespace
|
||||
|
|
@ -185,6 +186,7 @@ func (t *Table) updater(ctx context.Context) {
|
|||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.fireTableLoadFailed(errors.New("operation canceled"))
|
||||
return
|
||||
case <-time.After(rate):
|
||||
rate = t.refreshRate
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package ui
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
|
@ -94,6 +96,7 @@ func (l *Logo) refreshLogo(c string) {
|
|||
|
||||
func logo() *tview.TextView {
|
||||
v := tview.NewTextView()
|
||||
v.SetBackgroundColor(tcell.ColorDefault)
|
||||
v.SetWordWrap(false)
|
||||
v.SetWrap(false)
|
||||
v.SetTextAlign(tview.AlignLeft)
|
||||
|
|
@ -104,6 +107,7 @@ func logo() *tview.TextView {
|
|||
|
||||
func status() *tview.TextView {
|
||||
v := tview.NewTextView()
|
||||
v.SetBackgroundColor(tcell.ColorDefault)
|
||||
v.SetWordWrap(false)
|
||||
v.SetWrap(false)
|
||||
v.SetTextAlign(tview.AlignCenter)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func NewTree() *Tree {
|
|||
|
||||
// Init initializes the view
|
||||
func (t *Tree) Init(ctx context.Context) error {
|
||||
t.bindKeys()
|
||||
t.BindKeys()
|
||||
t.SetBorder(true)
|
||||
t.SetBorderAttributes(tcell.AttrBold)
|
||||
t.SetBorderPadding(0, 0, 1, 1)
|
||||
|
|
@ -86,7 +86,7 @@ func (t *Tree) ExtraHints() map[string]string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *Tree) bindKeys() {
|
||||
func (t *Tree) BindKeys() {
|
||||
t.Actions().Add(KeyActions{
|
||||
KeySpace: NewKeyAction("Expand/Collapse", t.noopCmd, true),
|
||||
KeyX: NewKeyAction("Expand/Collapse All", t.toggleCollapseCmd, true),
|
||||
|
|
|
|||
|
|
@ -118,12 +118,16 @@ func execCmd(r Runner, bin string, bg bool, args ...string) ui.ActionHandler {
|
|||
|
||||
ns, _ := client.Namespaced(path)
|
||||
var (
|
||||
env = r.EnvFn()()
|
||||
aa = make([]string, len(args))
|
||||
err error
|
||||
)
|
||||
|
||||
if r.EnvFn() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, a := range args {
|
||||
aa[i], err = env.envFor(ns, a)
|
||||
aa[i], err = r.EnvFn()().envFor(ns, a)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Plugin Args match failed")
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
|
|
@ -24,7 +24,7 @@ var ExitStatus = ""
|
|||
const (
|
||||
splashDelay = 1 * time.Second
|
||||
clusterRefresh = 5 * time.Second
|
||||
maxConRetry = 5
|
||||
maxConRetry = 10
|
||||
clusterInfoWidth = 50
|
||||
clusterInfoPad = 15
|
||||
)
|
||||
|
|
@ -39,9 +39,8 @@ type App struct {
|
|||
version string
|
||||
showHeader bool
|
||||
cancelFn context.CancelFunc
|
||||
conRetry int
|
||||
conRetry int32
|
||||
clusterModel *model.ClusterInfo
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
// NewApp returns a K9s app instance.
|
||||
|
|
@ -61,9 +60,7 @@ func NewApp(cfg *config.Config) *App {
|
|||
|
||||
// ConOK checks the connection is cool, returns false otherwise.
|
||||
func (a *App) ConOK() bool {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
return a.conRetry == 0
|
||||
return atomic.LoadInt32(&a.conRetry) == 0
|
||||
}
|
||||
|
||||
// Init initializes the application.
|
||||
|
|
@ -198,32 +195,31 @@ func (a *App) clusterUpdater(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (a *App) refreshCluster() {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
c := a.Content.Top()
|
||||
if ok := a.Conn().CheckConnectivity(); ok {
|
||||
if a.conRetry > 0 {
|
||||
if atomic.LoadInt32(&a.conRetry) > 0 {
|
||||
atomic.StoreInt32(&a.conRetry, 0)
|
||||
a.Status(ui.FlashInfo, "K8s connectivity OK")
|
||||
if c != nil {
|
||||
c.Start()
|
||||
}
|
||||
a.Status(ui.FlashInfo, "K8s connectivity OK")
|
||||
}
|
||||
a.conRetry = 0
|
||||
} else {
|
||||
a.conRetry++
|
||||
log.Warn().Msgf("Conn check failed (%d/%d)", a.conRetry, maxConRetry)
|
||||
atomic.AddInt32(&a.conRetry, 1)
|
||||
if c != nil {
|
||||
c.Stop()
|
||||
}
|
||||
a.Status(ui.FlashWarn, fmt.Sprintf("Dial K8s failed (%d)", a.conRetry))
|
||||
|
||||
count := atomic.LoadInt32(&a.conRetry)
|
||||
log.Warn().Msgf("Conn check failed (%d/%d)", count, maxConRetry)
|
||||
a.Status(ui.FlashWarn, fmt.Sprintf("Dial K8s failed (%d)", count))
|
||||
}
|
||||
if a.conRetry >= maxConRetry {
|
||||
ExitStatus = fmt.Sprintf("Lost K8s connection (%d). Bailing out!", a.conRetry)
|
||||
|
||||
count := atomic.LoadInt32(&a.conRetry)
|
||||
if count >= maxConRetry {
|
||||
ExitStatus = fmt.Sprintf("Lost K8s connection (%d). Bailing out!", count)
|
||||
a.BailOut()
|
||||
}
|
||||
if a.conRetry > 0 {
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ func (b *Browser) SetInstance(path string) {
|
|||
func (b *Browser) Start() {
|
||||
b.Stop()
|
||||
|
||||
log.Debug().Msgf("BROWSER started!")
|
||||
b.Table.Start()
|
||||
ctx := b.defaultContext()
|
||||
ctx, b.cancelFn = context.WithCancel(ctx)
|
||||
|
|
@ -111,7 +110,6 @@ func (b *Browser) Stop() {
|
|||
if b.cancelFn == nil {
|
||||
return
|
||||
}
|
||||
log.Debug().Msgf("BROWSER Stopped!")
|
||||
b.Table.Stop()
|
||||
b.cancelFn()
|
||||
b.cancelFn = nil
|
||||
|
|
@ -373,7 +371,6 @@ func (b *Browser) refreshActions() {
|
|||
|
||||
if b.app.ConOK() {
|
||||
b.namespaceActions(aa)
|
||||
|
||||
if !b.app.Config.K9s.GetReadOnly() {
|
||||
if client.Can(b.meta.Verbs, "edit") {
|
||||
aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ func useContext(app *App, name string) error {
|
|||
return errors.New("Expecting a switchable resource")
|
||||
}
|
||||
if err := switcher.Switch(name); err != nil {
|
||||
log.Error().Err(err).Msgf("Context switch failed")
|
||||
return err
|
||||
}
|
||||
if err := app.switchCtx(name, false); err != nil {
|
||||
|
|
|
|||
|
|
@ -16,39 +16,44 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv {
|
||||
ns, n := client.Namespaced(sel)
|
||||
ctx, err := app.Conn().Config().CurrentContextName()
|
||||
func generalEnv(a *App) K9sEnv {
|
||||
ctx, err := a.Conn().Config().CurrentContextName()
|
||||
if err != nil {
|
||||
ctx = render.NAValue
|
||||
}
|
||||
cluster, err := app.Conn().Config().CurrentClusterName()
|
||||
cluster, err := a.Conn().Config().CurrentClusterName()
|
||||
if err != nil {
|
||||
cluster = render.NAValue
|
||||
}
|
||||
user, err := app.Conn().Config().CurrentUserName()
|
||||
user, err := a.Conn().Config().CurrentUserName()
|
||||
if err != nil {
|
||||
user = render.NAValue
|
||||
}
|
||||
groups, err := app.Conn().Config().CurrentGroupNames()
|
||||
groups, err := a.Conn().Config().CurrentGroupNames()
|
||||
if err != nil {
|
||||
groups = []string{render.NAValue}
|
||||
}
|
||||
|
||||
var cfg string
|
||||
kcfg := app.Conn().Config().Flags().KubeConfig
|
||||
kcfg := a.Conn().Config().Flags().KubeConfig
|
||||
if kcfg != nil && *kcfg != "" {
|
||||
cfg = *kcfg
|
||||
}
|
||||
|
||||
env := K9sEnv{
|
||||
"NAMESPACE": ns,
|
||||
"NAME": n,
|
||||
return K9sEnv{
|
||||
"CONTEXT": ctx,
|
||||
"CLUSTER": cluster,
|
||||
"USER": user,
|
||||
"GROUPS": strings.Join(groups, ","),
|
||||
"KUBECONFIG": cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultK9sEnv(a *App, sel string, row render.Row) K9sEnv {
|
||||
ns, n := client.Namespaced(sel)
|
||||
|
||||
env := generalEnv(a)
|
||||
env["NAMESPACE"], env["NAME"] = ns, n
|
||||
|
||||
for i, r := range row.Fields {
|
||||
env["COL"+strconv.Itoa(i)] = r
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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/ui"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
|
|
@ -108,47 +109,86 @@ func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
sel := p.GetTable().GetSelectedItem()
|
||||
if sel == "" {
|
||||
path := p.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
row := p.GetTable().GetSelectedRowIndex()
|
||||
status := ui.TrimCell(p.GetTable().SelectTable, row, p.GetTable().NameColIndex()+2)
|
||||
if status != render.Running {
|
||||
p.App().Flash().Errf("%s is not in a running state", sel)
|
||||
p.App().Flash().Errf("%s is not in a running state", path)
|
||||
return nil
|
||||
}
|
||||
cc, err := fetchContainers(p.App().factory, sel, false)
|
||||
if err != nil {
|
||||
p.App().Flash().Errf("Unable to retrieve containers %s", err)
|
||||
return evt
|
||||
}
|
||||
if len(cc) == 1 {
|
||||
p.shellIn(sel, cc[0])
|
||||
return nil
|
||||
}
|
||||
picker := NewPicker()
|
||||
picker.populate(cc)
|
||||
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
|
||||
p.shellIn(sel, t)
|
||||
})
|
||||
if err := p.App().inject(picker); err != nil {
|
||||
|
||||
if err := containerShellin(p.App(), p, path, ""); err != nil {
|
||||
p.App().Flash().Err(err)
|
||||
}
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
func (p *Pod) shellIn(path, co string) {
|
||||
p.Stop()
|
||||
shellIn(p.App(), path, co)
|
||||
p.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func containerShellin(a *App, comp model.Component, path, co string) error {
|
||||
if co != "" {
|
||||
resumeShellIn(a, comp, path, co)
|
||||
return nil
|
||||
}
|
||||
|
||||
cc, err := fetchContainers(a.factory, path, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cc) == 1 {
|
||||
resumeShellIn(a, comp, path, cc[0])
|
||||
return nil
|
||||
}
|
||||
picker := NewPicker()
|
||||
picker.populate(cc)
|
||||
picker.SetSelectedFunc(func(_ int, co, _ string, _ rune) {
|
||||
resumeShellIn(a, comp, path, co)
|
||||
})
|
||||
if err := a.inject(picker); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resumeShellIn(a *App, c model.Component, path, co string) {
|
||||
c.Stop()
|
||||
defer c.Start()
|
||||
shellIn(a, path, co)
|
||||
}
|
||||
|
||||
func shellIn(a *App, path, co string) {
|
||||
args := computeShellArgs(path, co, a.Config.K9s.CurrentContext, a.Conn().Config().Flags().KubeConfig)
|
||||
|
||||
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
|
||||
if !runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, path, co), args: args}) {
|
||||
a.Flash().Err(errors.New("Shell exec failed"))
|
||||
}
|
||||
}
|
||||
|
||||
func computeShellArgs(path, co, context string, kcfg *string) []string {
|
||||
args := make([]string, 0, 15)
|
||||
args = append(args, "exec", "-it")
|
||||
args = append(args, "--context", context)
|
||||
ns, po := client.Namespaced(path)
|
||||
args = append(args, "-n", ns)
|
||||
args = append(args, po)
|
||||
if kcfg != nil && *kcfg != "" {
|
||||
args = append(args, "--kubeconfig", *kcfg)
|
||||
}
|
||||
if co != "" {
|
||||
args = append(args, "-c", co)
|
||||
}
|
||||
|
||||
return append(args, "--", "sh", "-c", shellCheck)
|
||||
}
|
||||
|
||||
func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string, error) {
|
||||
o, err := f.Get("v1/pods", path, true, labels.Everything())
|
||||
if err != nil {
|
||||
|
|
@ -172,30 +212,3 @@ func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string,
|
|||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func shellIn(a *App, path, co string) {
|
||||
args := computeShellArgs(path, co, a.Config.K9s.CurrentContext, a.Conn().Config().Flags().KubeConfig)
|
||||
log.Debug().Msgf("Shell args %v", args)
|
||||
|
||||
c := color.New(color.BgGreen).Add(color.FgBlack).Add(color.Bold)
|
||||
if !runK(a, shellOpts{clear: true, banner: c.Sprintf(bannerFmt, path, co), args: args}) {
|
||||
a.Flash().Err(errors.New("Shell exec failed"))
|
||||
}
|
||||
}
|
||||
|
||||
func computeShellArgs(path, co, context string, kcfg *string) []string {
|
||||
args := make([]string, 0, 15)
|
||||
args = append(args, "exec", "-it")
|
||||
args = append(args, "--context", context)
|
||||
ns, po := client.Namespaced(path)
|
||||
args = append(args, "-n", ns)
|
||||
args = append(args, po)
|
||||
if kcfg != nil && *kcfg != "" {
|
||||
args = append(args, "--kubeconfig", *kcfg)
|
||||
}
|
||||
if co != "" {
|
||||
args = append(args, "-c", co)
|
||||
}
|
||||
|
||||
return append(args, "--", "sh", "-c", shellCheck)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ func NewXray(gvr client.GVR) ResourceViewer {
|
|||
|
||||
// Init initializes the view
|
||||
func (x *Xray) Init(ctx context.Context) error {
|
||||
x.envFn = x.k9sEnv
|
||||
|
||||
if err := x.Tree.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -77,12 +79,12 @@ func (x *Xray) Init(ctx context.Context) error {
|
|||
x.model.AddListener(x)
|
||||
|
||||
x.SetChangedFunc(func(n *tview.TreeNode) {
|
||||
ref, ok := n.GetReference().(xray.NodeSpec)
|
||||
spec, ok := n.GetReference().(xray.NodeSpec)
|
||||
if !ok {
|
||||
log.Error().Msgf("No ref found on node %s", n.GetText())
|
||||
return
|
||||
}
|
||||
x.SetSelectedItem(ref.Path)
|
||||
x.SetSelectedItem(spec.AsPath())
|
||||
x.refreshActions()
|
||||
})
|
||||
x.refreshActions()
|
||||
|
|
@ -131,16 +133,18 @@ func (x *Xray) refreshActions() {
|
|||
|
||||
x.Actions().Clear()
|
||||
x.bindKeys()
|
||||
x.Tree.BindKeys()
|
||||
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
gvr := spec.GVR()
|
||||
var err error
|
||||
x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(ref.GVR))
|
||||
x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(gvr))
|
||||
if err != nil {
|
||||
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||
log.Warn().Msgf("NO meta for %q -- %s", gvr, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -155,22 +159,27 @@ func (x *Xray) refreshActions() {
|
|||
aa[ui.KeyD] = ui.NewKeyAction("Describe", x.describeCmd, true)
|
||||
}
|
||||
|
||||
if ref.GVR == "containers" {
|
||||
switch gvr {
|
||||
case "containers":
|
||||
x.Actions().Delete(tcell.KeyEnter)
|
||||
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
||||
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true)
|
||||
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
||||
case "v1/pods":
|
||||
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
||||
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true)
|
||||
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
||||
}
|
||||
|
||||
x.Actions().Add(aa)
|
||||
}
|
||||
|
||||
// GetSelectedPath returns the current selection as string.
|
||||
func (x *Xray) GetSelectedPath() string {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return ""
|
||||
}
|
||||
return ref.Path
|
||||
return spec.Path()
|
||||
}
|
||||
|
||||
func (x *Xray) selectedSpec() *xray.NodeSpec {
|
||||
|
|
@ -193,6 +202,34 @@ func (x *Xray) EnvFn() EnvFunc {
|
|||
return x.envFn
|
||||
}
|
||||
|
||||
func (x *Xray) k9sEnv() K9sEnv {
|
||||
env := generalEnv(x.app)
|
||||
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return env
|
||||
}
|
||||
|
||||
env["FILTER"] = x.CmdBuff().String()
|
||||
if env["FILTER"] == "" {
|
||||
ns, n := client.Namespaced(spec.Path())
|
||||
env["NAMESPACE"], env["FILTER"] = ns, n
|
||||
}
|
||||
|
||||
switch spec.GVR() {
|
||||
case "containers":
|
||||
_, co := client.Namespaced(spec.Path())
|
||||
env["CONTAINER"] = co
|
||||
ns, n := client.Namespaced(*spec.ParentPath())
|
||||
env["NAMESPACE"], env["POD"], env["NAME"] = ns, n, co
|
||||
default:
|
||||
ns, n := client.Namespaced(spec.Path())
|
||||
env["NAMESPACE"], env["NAME"] = ns, n
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// Aliases returns all available aliases.
|
||||
func (x *Xray) Aliases() []string {
|
||||
return append(x.meta.ShortNames, x.meta.SingularName, x.meta.Name)
|
||||
|
|
@ -200,100 +237,98 @@ func (x *Xray) Aliases() []string {
|
|||
|
||||
func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ref.Parent != nil {
|
||||
x.showLogs(ref.Parent, ref, prev)
|
||||
} else {
|
||||
log.Error().Msgf("No parent found for container %q", ref.Path)
|
||||
}
|
||||
x.showLogs(spec, prev)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Xray) showLogs(pod, co *xray.NodeSpec, prev bool) {
|
||||
func (x *Xray) showLogs(spec *xray.NodeSpec, prev bool) {
|
||||
// Need to load and wait for pods
|
||||
ns, _ := client.Namespaced(pod.Path)
|
||||
path, co := spec.Path(), ""
|
||||
if spec.GVR() == "containers" {
|
||||
_, coName := client.Namespaced(spec.Path())
|
||||
path, co = *spec.ParentPath(), coName
|
||||
}
|
||||
|
||||
ns, _ := client.Namespaced(path)
|
||||
_, err := x.app.factory.CanForResource(ns, "v1/pods", client.MonitorAccess)
|
||||
if err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := x.app.inject(NewLog(client.NewGVR(co.GVR), pod.Path, co.Path, prev)); err != nil {
|
||||
if err := x.app.inject(NewLog(client.NewGVR("v1/pods"), path, co, prev)); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ref.Status != "" {
|
||||
x.app.Flash().Errf("%s is not in a running state", ref.Path)
|
||||
if spec.Status() != "ok" {
|
||||
x.app.Flash().Errf("%s is not in a running state", spec.Path())
|
||||
return nil
|
||||
}
|
||||
|
||||
if ref.Parent != nil {
|
||||
_, co := client.Namespaced(ref.Path)
|
||||
x.shellIn(ref.Parent.Path, co)
|
||||
} else {
|
||||
log.Error().Msgf("No parent found on container node %q", ref.Path)
|
||||
path, co := spec.Path(), ""
|
||||
if spec.GVR() == "containers" {
|
||||
_, co = client.Namespaced(spec.Path())
|
||||
path = *spec.ParentPath()
|
||||
}
|
||||
|
||||
if err := containerShellin(x.app, x, path, co); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Xray) shellIn(path, co string) {
|
||||
x.Stop()
|
||||
shellIn(x.app, path, co)
|
||||
x.Start()
|
||||
}
|
||||
|
||||
func (x *Xray) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return evt
|
||||
}
|
||||
|
||||
ctx := x.defaultContext()
|
||||
raw, err := x.model.ToYAML(ctx, ref.GVR, ref.Path)
|
||||
raw, err := x.model.ToYAML(ctx, spec.GVR(), spec.Path())
|
||||
if err != nil {
|
||||
x.App().Flash().Errf("unable to get resource %q -- %s", ref.GVR, err)
|
||||
x.App().Flash().Errf("unable to get resource %q -- %s", spec.GVR(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(x.app, "YAML", ref.Path, true).Update(raw)
|
||||
details := NewDetails(x.app, "YAML", spec.Path(), true).Update(raw)
|
||||
if err := x.app.inject(details); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return evt
|
||||
}
|
||||
|
||||
x.Stop()
|
||||
defer x.Start()
|
||||
{
|
||||
gvr := client.NewGVR(ref.GVR)
|
||||
gvr := client.NewGVR(spec.GVR())
|
||||
meta, err := dao.MetaAccess.MetaFor(gvr)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||
log.Warn().Msgf("NO meta for %q -- %s", spec.GVR(), err)
|
||||
return nil
|
||||
}
|
||||
x.resourceDelete(gvr, ref, fmt.Sprintf("Delete %s %s?", meta.SingularName, ref.Path))
|
||||
x.resourceDelete(gvr, spec, fmt.Sprintf("Delete %s %s?", meta.SingularName, spec.Path()))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -301,12 +336,12 @@ func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (x *Xray) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return evt
|
||||
}
|
||||
|
||||
x.describe(ref.GVR, ref.Path)
|
||||
x.describe(spec.GVR(), spec.Path())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -328,18 +363,18 @@ func (x *Xray) describe(gvr, path string) {
|
|||
}
|
||||
|
||||
func (x *Xray) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return evt
|
||||
}
|
||||
|
||||
x.Stop()
|
||||
defer x.Start()
|
||||
{
|
||||
ns, n := client.Namespaced(ref.Path)
|
||||
ns, n := client.Namespaced(spec.Path())
|
||||
args := make([]string, 0, 10)
|
||||
args = append(args, "edit")
|
||||
args = append(args, client.NewGVR(ref.GVR).R())
|
||||
args = append(args, client.NewGVR(spec.GVR()).R())
|
||||
args = append(args, "-n", ns)
|
||||
args = append(args, "--context", x.app.Config.K9s.CurrentContext)
|
||||
if cfg := x.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" {
|
||||
|
|
@ -408,14 +443,15 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
ref := x.selectedSpec()
|
||||
if ref == nil {
|
||||
spec := x.selectedSpec()
|
||||
if spec == nil {
|
||||
return nil
|
||||
}
|
||||
if len(strings.Split(ref.Path, "/")) == 1 {
|
||||
log.Debug().Msgf("SELECTED REF %#v", spec)
|
||||
if len(strings.Split(spec.Path(), "/")) == 1 {
|
||||
return nil
|
||||
}
|
||||
if err := x.app.viewResource(client.NewGVR(ref.GVR).R(), ref.Path, false); err != nil {
|
||||
if err := x.app.viewResource(client.NewGVR(spec.GVR()).R(), spec.Path(), false); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
||||
|
|
@ -464,13 +500,13 @@ func (x *Xray) update(node *xray.TreeNode) {
|
|||
x.hydrate(root, c)
|
||||
}
|
||||
if x.GetSelectedItem() == "" {
|
||||
x.SetSelectedItem(node.ID)
|
||||
x.SetSelectedItem(node.Spec().Path())
|
||||
}
|
||||
|
||||
x.app.QueueUpdateDraw(func() {
|
||||
x.SetRoot(root)
|
||||
root.Walk(func(node, parent *tview.TreeNode) bool {
|
||||
ref, ok := node.GetReference().(xray.NodeSpec)
|
||||
spec, ok := node.GetReference().(xray.NodeSpec)
|
||||
if !ok {
|
||||
log.Error().Msgf("Expecting a NodeSpec but got %T", node.GetReference())
|
||||
return false
|
||||
|
|
@ -482,7 +518,8 @@ func (x *Xray) update(node *xray.TreeNode) {
|
|||
node.SetExpanded(true)
|
||||
}
|
||||
|
||||
if ref.Path == x.GetSelectedItem() {
|
||||
if spec.AsPath() == x.GetSelectedItem() {
|
||||
log.Debug().Msgf("SEL %q--%q", spec.Path(), x.GetSelectedItem())
|
||||
node.SetExpanded(true).SetSelectable(true)
|
||||
x.SetCurrentNode(node)
|
||||
}
|
||||
|
|
@ -611,9 +648,9 @@ func (x *Xray) styleTitle() string {
|
|||
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), x.app.Styles.Frame())
|
||||
}
|
||||
|
||||
func (x *Xray) resourceDelete(gvr client.GVR, ref *xray.NodeSpec, msg string) {
|
||||
func (x *Xray) resourceDelete(gvr client.GVR, spec *xray.NodeSpec, msg string) {
|
||||
dialog.ShowDelete(x.app.Content.Pages, msg, func(cascade, force bool) {
|
||||
x.app.Flash().Infof("Delete resource %s %s", ref.GVR, ref.Path)
|
||||
x.app.Flash().Infof("Delete resource %s %s", spec.GVR(), spec.Path())
|
||||
accessor, err := dao.AccessorFor(x.app.factory, gvr)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("No accessor")
|
||||
|
|
@ -625,11 +662,11 @@ func (x *Xray) resourceDelete(gvr client.GVR, ref *xray.NodeSpec, msg string) {
|
|||
x.app.Flash().Errf("Invalid nuker %T", accessor)
|
||||
return
|
||||
}
|
||||
if err := nuker.Delete(ref.Path, true, true); err != nil {
|
||||
if err := nuker.Delete(spec.Path(), true, true); err != nil {
|
||||
x.app.Flash().Errf("Delete failed with `%s", err)
|
||||
} else {
|
||||
x.app.Flash().Infof("%s `%s deleted successfully", x.GVR(), ref.Path)
|
||||
x.app.factory.DeleteForwarder(ref.Path)
|
||||
x.app.Flash().Infof("%s `%s deleted successfully", x.GVR(), spec.Path())
|
||||
x.app.factory.DeleteForwarder(spec.Path())
|
||||
}
|
||||
x.Refresh()
|
||||
}, func() {})
|
||||
|
|
@ -661,15 +698,7 @@ func makeTreeNode(node *xray.TreeNode, expanded bool, styles *config.Styles) *tv
|
|||
n := tview.NewTreeNode("No data...")
|
||||
if node != nil {
|
||||
n.SetText(node.Title(styles.Xray()))
|
||||
spec := xray.NodeSpec{}
|
||||
if p := node.Parent; p != nil {
|
||||
spec.GVR, spec.Path = p.GVR, p.ID
|
||||
}
|
||||
n.SetReference(xray.NodeSpec{
|
||||
GVR: node.GVR,
|
||||
Path: node.ID,
|
||||
Parent: &spec,
|
||||
})
|
||||
n.SetReference(node.Spec())
|
||||
}
|
||||
n.SetSelectable(true)
|
||||
n.SetExpanded(expanded)
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
|
|||
}
|
||||
pns, _ := client.Namespaced(parent.ID)
|
||||
c.envRefs(f, root, pns, co.Container)
|
||||
if !root.IsLeaf() {
|
||||
// if !root.IsLeaf() {
|
||||
parent.Add(root)
|
||||
}
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ func makeDoubleCMKeysContainer(n string, optional bool) *v1.Container {
|
|||
}
|
||||
|
||||
func load(t *testing.T, n string) *unstructured.Unstructured {
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("test_assets/%s.json", n))
|
||||
raw, err := ioutil.ReadFile(fmt.Sprintf("testdata/%s.json", n))
|
||||
assert.Nil(t, err)
|
||||
|
||||
var o unstructured.Unstructured
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||
}
|
||||
parent.Add(node)
|
||||
|
||||
if err := p.containerRefs(ctx, node, po.Namespace, po.Spec); err != nil {
|
||||
return err
|
||||
|
|
@ -50,6 +49,14 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, po.Namespace)
|
||||
nsn := parent.Find(gvr, nsID)
|
||||
if nsn == nil {
|
||||
nsn = NewTreeNode(gvr, nsID)
|
||||
parent.Add(nsn)
|
||||
}
|
||||
nsn.Add(node)
|
||||
|
||||
return p.validate(node, po)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,25 +13,25 @@ import (
|
|||
func TestPodRender(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
file string
|
||||
level1, level2 int
|
||||
count, children int
|
||||
status string
|
||||
}{
|
||||
"plain": {
|
||||
file: "po",
|
||||
level1: 1,
|
||||
level2: 3,
|
||||
children: 1,
|
||||
count: 7,
|
||||
status: xray.OkStatus,
|
||||
},
|
||||
"withInit": {
|
||||
file: "init",
|
||||
level1: 1,
|
||||
level2: 2,
|
||||
children: 1,
|
||||
count: 7,
|
||||
status: xray.OkStatus,
|
||||
},
|
||||
"cilium": {
|
||||
file: "cilium",
|
||||
level1: 1,
|
||||
level2: 3,
|
||||
children: 1,
|
||||
count: 8,
|
||||
status: xray.OkStatus,
|
||||
},
|
||||
}
|
||||
|
|
@ -46,8 +46,8 @@ func TestPodRender(t *testing.T) {
|
|||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
||||
|
||||
assert.Nil(t, re.Render(ctx, "", &render.PodWithMetrics{Raw: o}))
|
||||
assert.Equal(t, u.level1, root.CountChildren())
|
||||
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||
assert.Equal(t, u.children, root.CountChildren())
|
||||
assert.Equal(t, u.count, root.Count(""))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,51 @@ type TreeRef string
|
|||
|
||||
// NodeSpec represents a node resource specification.
|
||||
type NodeSpec struct {
|
||||
GVR, Path, Status string
|
||||
Parent *NodeSpec
|
||||
GVRs, Paths, Statuses []string
|
||||
}
|
||||
|
||||
func (s NodeSpec) ParentGVR() *string {
|
||||
if len(s.GVRs) > 1 {
|
||||
return &s.GVRs[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s NodeSpec) ParentPath() *string {
|
||||
if len(s.Paths) > 1 {
|
||||
return &s.Paths[1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GVR returns the current GVR.
|
||||
func (s NodeSpec) GVR() string {
|
||||
return s.GVRs[0]
|
||||
}
|
||||
|
||||
// Path returns the current path.
|
||||
func (s NodeSpec) Path() string {
|
||||
return s.Paths[0]
|
||||
}
|
||||
|
||||
// Status returns the current status.
|
||||
func (s NodeSpec) Status() string {
|
||||
return s.Statuses[0]
|
||||
}
|
||||
|
||||
// AsPath returns path hierarchy as string.
|
||||
func (s NodeSpec) AsPath() string {
|
||||
return strings.Join(s.Paths, PathSeparator)
|
||||
}
|
||||
|
||||
// AsGVR returns a gvr hierarchy as string.
|
||||
func (s NodeSpec) AsGVR() string {
|
||||
return strings.Join(s.GVRs, PathSeparator)
|
||||
}
|
||||
|
||||
// AsStatus returns a status hierarchy as string.
|
||||
func (s NodeSpec) AsStatus() string {
|
||||
return strings.Join(s.Statuses, PathSeparator)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -145,19 +188,17 @@ func (t *TreeNode) Sort() {
|
|||
|
||||
// Spec returns this node specification.
|
||||
func (t *TreeNode) Spec() NodeSpec {
|
||||
parent := t
|
||||
var gvr, path, status []string
|
||||
for parent != nil {
|
||||
gvr = append(gvr, parent.GVR)
|
||||
path = append(path, parent.ID)
|
||||
status = append(status, parent.Extras[StatusKey])
|
||||
parent = parent.Parent
|
||||
var GVRs, Paths, Statuses []string
|
||||
for parent := t; parent != nil; parent = parent.Parent {
|
||||
GVRs = append(GVRs, parent.GVR)
|
||||
Paths = append(Paths, parent.ID)
|
||||
Statuses = append(Statuses, parent.Extras[StatusKey])
|
||||
}
|
||||
|
||||
return NodeSpec{
|
||||
GVR: strings.Join(gvr, PathSeparator),
|
||||
Path: strings.Join(path, PathSeparator),
|
||||
Status: strings.Join(status, PathSeparator),
|
||||
GVRs: GVRs,
|
||||
Paths: Paths,
|
||||
Statuses: Statuses,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,21 +221,18 @@ func (t *TreeNode) Blank() bool {
|
|||
}
|
||||
|
||||
// Hydrate hydrates a full tree bases on a collection of specifications.
|
||||
func Hydrate(refs []NodeSpec) *TreeNode {
|
||||
func Hydrate(specs []NodeSpec) *TreeNode {
|
||||
root := NewTreeNode("", "")
|
||||
nav := root
|
||||
for _, ref := range refs {
|
||||
gvrs := strings.Split(ref.GVR, PathSeparator)
|
||||
paths := strings.Split(ref.Path, PathSeparator)
|
||||
statuses := strings.Split(ref.Status, PathSeparator)
|
||||
for i := len(paths) - 1; i >= 0; i-- {
|
||||
for _, spec := range specs {
|
||||
for i := len(spec.Paths) - 1; i >= 0; i-- {
|
||||
if nav.Blank() {
|
||||
nav.GVR, nav.ID, nav.Extras[StatusKey] = gvrs[i], paths[i], statuses[i]
|
||||
nav.GVR, nav.ID, nav.Extras[StatusKey] = spec.GVRs[i], spec.Paths[i], spec.Statuses[i]
|
||||
continue
|
||||
}
|
||||
c := NewTreeNode(gvrs[i], paths[i])
|
||||
c.Extras[StatusKey] = statuses[i]
|
||||
if n := nav.Find(gvrs[i], paths[i]); n == nil {
|
||||
c := NewTreeNode(spec.GVRs[i], spec.Paths[i])
|
||||
c.Extras[StatusKey] = spec.Statuses[i]
|
||||
if n := nav.Find(spec.GVRs[i], spec.Paths[i]); n == nil {
|
||||
nav.Add(c)
|
||||
nav = c
|
||||
} else {
|
||||
|
|
@ -260,7 +298,7 @@ func (t *TreeNode) Filter(q string, filter func(q, path string) bool) *TreeNode
|
|||
specs := t.Flatten()
|
||||
matches := make([]NodeSpec, 0, len(specs))
|
||||
for _, s := range specs {
|
||||
if filter(q, s.Path+s.Status) {
|
||||
if filter(q, s.AsPath()+s.AsStatus()) {
|
||||
matches = append(matches, s)
|
||||
}
|
||||
}
|
||||
|
|
@ -461,7 +499,7 @@ func toEmoji(gvr string) string {
|
|||
|
||||
// EmojiInfo returns emoji help.
|
||||
func EmojiInfo() map[string]string {
|
||||
gvrs := []string{
|
||||
GVRs := []string{
|
||||
"containers",
|
||||
"v1/namespaces",
|
||||
"v1/pods",
|
||||
|
|
@ -476,8 +514,8 @@ func EmojiInfo() map[string]string {
|
|||
"apps/v1/daemonsets",
|
||||
}
|
||||
|
||||
m := make(map[string]string, len(gvrs))
|
||||
for _, g := range gvrs {
|
||||
m := make(map[string]string, len(GVRs))
|
||||
for _, g := range GVRs {
|
||||
m[client.NewGVR(g).R()] = toEmoji(g)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ func TestTreeNodeFilter(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTreeNodeHydrate(t *testing.T) {
|
||||
threeOK := strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator)
|
||||
fiveOK := strings.Join([]string{"ok", "ok", "ok", "ok", "ok"}, xray.PathSeparator)
|
||||
threeOK := []string{"ok", "ok", "ok"}
|
||||
fiveOK := append(threeOK, "ok", "ok")
|
||||
|
||||
uu := map[string]struct {
|
||||
spec []xray.NodeSpec
|
||||
|
|
@ -96,14 +96,14 @@ func TestTreeNodeHydrate(t *testing.T) {
|
|||
"flat_simple": {
|
||||
spec: []xray.NodeSpec{
|
||||
{
|
||||
GVR: "containers::v1/pods",
|
||||
Path: "c1::default/p1",
|
||||
Status: threeOK,
|
||||
GVRs: []string{"containers", "v1/pods"},
|
||||
Paths: []string{"c1", "default/p1"},
|
||||
Statuses: threeOK,
|
||||
},
|
||||
{
|
||||
GVR: "containers::v1/pods",
|
||||
Path: "c2::default/p1",
|
||||
Status: threeOK,
|
||||
GVRs: []string{"containers", "v1/pods"},
|
||||
Paths: []string{"c2", "default/p1"},
|
||||
Statuses: threeOK,
|
||||
},
|
||||
},
|
||||
e: root1(),
|
||||
|
|
@ -111,14 +111,14 @@ func TestTreeNodeHydrate(t *testing.T) {
|
|||
"flat_complex": {
|
||||
spec: []xray.NodeSpec{
|
||||
{
|
||||
GVR: "v1/secrets::containers::v1/pods",
|
||||
Path: "s1::c1::default/p1",
|
||||
Status: threeOK,
|
||||
GVRs: []string{"v1/secrets", "containers", "v1/pods"},
|
||||
Paths: []string{"s1", "c1", "default/p1"},
|
||||
Statuses: threeOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::containers::v1/pods",
|
||||
Path: "s2::c2::default/p1",
|
||||
Status: threeOK,
|
||||
GVRs: []string{"v1/secrets", "containers", "v1/pods"},
|
||||
Paths: []string{"s2", "c2", "default/p1"},
|
||||
Statuses: threeOK,
|
||||
},
|
||||
},
|
||||
e: root2(),
|
||||
|
|
@ -126,49 +126,49 @@ func TestTreeNodeHydrate(t *testing.T) {
|
|||
"complex1": {
|
||||
spec: []xray.NodeSpec{
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "default/default-token-rr22g::default/nginx-6b866d578b-c6tcn::default/nginx::-/default::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"default/default-token-rr22g", "default/nginx-6b866d578b-c6tcn", "default/nginx", "-/default", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/coredns::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/configmaps", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/coredns", "kube-system/coredns-6955765f44-89q2p", "kube-system/coredns", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/coredns-token-5cq9j", "kube-system/coredns-6955765f44-89q2p", "kube-system/coredns", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/coredns::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/configmaps", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/coredns", "kube-system/coredns-6955765f44-r9j9t", "kube-system/coredns", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/coredns-token-5cq9j", "kube-system/coredns-6955765f44-r9j9t", "kube-system/coredns", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/default-token-thzt8::kube-system/metrics-server-6754dbc9df-88bk4::kube-system/metrics-server::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/default-token-thzt8", "kube-system/metrics-server-6754dbc9df-88bk4", "kube-system/metrics-server", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kube-system/nginx-ingress-token-kff5q::kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55::kube-system/nginx-ingress-controller::-/kube-system::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kube-system/nginx-ingress-token-kff5q", "kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55", "kube-system/nginx-ingress-controller", "-/kube-system", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56::kubernetes-dashboard/dashboard-metrics-scraper::-/kubernetes-dashboard::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kubernetes-dashboard/kubernetes-dashboard-token-d6rt4", "kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56", "kubernetes-dashboard/dashboard-metrics-scraper", "-/kubernetes-dashboard", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d::kubernetes-dashboard/kubernetes-dashboard::-/kubernetes-dashboard::deployments",
|
||||
Status: fiveOK,
|
||||
GVRs: []string{"v1/secrets", "v1/pods", "apps/v1/deployments", "v1/namespaces", "apps/v1/deployments"},
|
||||
Paths: []string{"kubernetes-dashboard/kubernetes-dashboard-token-d6rt4", "kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d", "kubernetes-dashboard/kubernetes-dashboard", "-/kubernetes-dashboard", "deployments"},
|
||||
Statuses: fiveOK,
|
||||
},
|
||||
},
|
||||
e: root3(),
|
||||
|
|
@ -193,14 +193,14 @@ func TestTreeNodeFlatten(t *testing.T) {
|
|||
root: root1(),
|
||||
e: []xray.NodeSpec{
|
||||
{
|
||||
GVR: "containers::v1/pods",
|
||||
Path: "c1::default/p1",
|
||||
Status: strings.Join([]string{"ok", "ok"}, xray.PathSeparator),
|
||||
GVRs: []string{"containers", "v1/pods"},
|
||||
Paths: []string{"c1", "default/p1"},
|
||||
Statuses: []string{"ok", "ok"},
|
||||
},
|
||||
{
|
||||
GVR: "containers::v1/pods",
|
||||
Path: "c2::default/p1",
|
||||
Status: strings.Join([]string{"ok", "ok"}, xray.PathSeparator),
|
||||
GVRs: []string{"containers", "v1/pods"},
|
||||
Paths: []string{"c2", "default/p1"},
|
||||
Statuses: []string{"ok", "ok"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -208,14 +208,14 @@ func TestTreeNodeFlatten(t *testing.T) {
|
|||
root: root2(),
|
||||
e: []xray.NodeSpec{
|
||||
{
|
||||
GVR: "v1/secrets::containers::v1/pods",
|
||||
Path: "s1::c1::default/p1",
|
||||
Status: strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator),
|
||||
GVRs: []string{"v1/secrets", "containers", "v1/pods"},
|
||||
Paths: []string{"s1", "c1", "default/p1"},
|
||||
Statuses: []string{"ok", "ok", "ok"},
|
||||
},
|
||||
{
|
||||
GVR: "v1/secrets::containers::v1/pods",
|
||||
Path: "s2::c2::default/p1",
|
||||
Status: strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator),
|
||||
GVRs: []string{"v1/secrets", "containers", "v1/pods"},
|
||||
Paths: []string{"s2", "c2", "default/p1"},
|
||||
Statuses: []string{"ok", "ok", "ok"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -323,18 +323,18 @@ func diff1() *xray.TreeNode {
|
|||
}
|
||||
|
||||
func root2() *xray.TreeNode {
|
||||
n := xray.NewTreeNode("v1/pods", "default/p1")
|
||||
c1 := xray.NewTreeNode("containers", "c1")
|
||||
c2 := xray.NewTreeNode("containers", "c2")
|
||||
n.Add(c1)
|
||||
n.Add(c2)
|
||||
|
||||
s1 := xray.NewTreeNode("v1/secrets", "s1")
|
||||
c1.Add(s1)
|
||||
|
||||
c2 := xray.NewTreeNode("containers", "c2")
|
||||
s2 := xray.NewTreeNode("v1/secrets", "s2")
|
||||
c2.Add(s2)
|
||||
|
||||
n := xray.NewTreeNode("v1/pods", "default/p1")
|
||||
n.Add(c1)
|
||||
n.Add(c2)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue