From 1476299d6fb774040fb5f5d0c83778dbc68c79f1 Mon Sep 17 00:00:00 2001 From: Craig Goldstein Date: Thu, 7 Sep 2023 15:30:12 -0400 Subject: [PATCH] Add new exec utility and import yugatool/yugaware-client - cobra commands are now broken out by upload/exec/import - new exec command allows commands to be executed across all universe nodes - exec can lookup inventory using yugaware client or except a json/yaml file - existing yugatool and yugaware-client applications imported into yb-support-tool --- go.mod | 12 +- go.sum | 19 ++- yb-support-tool/README.md | 216 +++++++++++++++++++++++++++++- yb-support-tool/cmd/exec.go | 84 ++++++++++++ yb-support-tool/cmd/import.go | 21 +++ yb-support-tool/cmd/root.go | 181 ++----------------------- yb-support-tool/cmd/upload.go | 181 +++++++++++++++++++++++++ yb-support-tool/exec/inventory.go | 130 ++++++++++++++++++ yb-support-tool/exec/ssh.go | 140 +++++++++++++++++++ yb-support-tool/main.go | 1 - 10 files changed, 802 insertions(+), 183 deletions(-) create mode 100644 yb-support-tool/cmd/exec.go create mode 100644 yb-support-tool/cmd/import.go create mode 100644 yb-support-tool/cmd/upload.go create mode 100644 yb-support-tool/exec/inventory.go create mode 100644 yb-support-tool/exec/ssh.go diff --git a/go.mod b/go.mod index e525a2c2..c41f9d51 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.22.1 github.com/pkg/errors v0.9.1 + github.com/schollz/progressbar/v3 v3.13.1 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.13.0 @@ -39,6 +40,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/term v0.7.0 google.golang.org/protobuf v1.28.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -78,12 +80,14 @@ require ( github.com/ettle/strcase v0.1.1 // indirect github.com/fatih/color v1.14.1 // indirect github.com/fatih/structtag v1.2.0 // indirect + github.com/felixge/httpsnoop v1.0.2 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/getkin/kin-openapi v0.94.0 // indirect github.com/go-critic/go-critic v0.6.5 // indirect github.com/go-openapi/analysis v0.21.2 // indirect + github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/loads v0.21.0 // indirect @@ -114,6 +118,7 @@ require ( github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect + github.com/gorilla/handlers v1.5.1 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect @@ -125,6 +130,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/jessevdk/go-flags v1.5.0 // indirect github.com/jgautheron/goconst v1.5.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect @@ -135,6 +141,8 @@ require ( github.com/kisielk/errcheck v1.6.3 // indirect github.com/kisielk/gotool v1.0.0 // indirect github.com/kkHAIKE/contextcheck v1.1.3 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.6 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect @@ -180,12 +188,12 @@ require ( github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/ryancurrah/gomodguard v1.3.0 // indirect github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.21.1 // indirect - github.com/schollz/progressbar/v3 v3.13.1 // indirect github.com/securego/gosec/v2 v2.14.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect @@ -210,6 +218,7 @@ require ( github.com/timonwong/loggercheck v0.9.3 // indirect github.com/tomarrell/wrapcheck/v2 v2.8.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/ultraware/funlen v0.0.3 // indirect github.com/ultraware/whitespace v0.0.5 // indirect github.com/uudashr/gocognit v1.0.6 // indirect @@ -234,7 +243,6 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/retry.v1 v1.0.3 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.4.0 // indirect mvdan.cc/gofumpt v0.4.0 // indirect diff --git a/go.sum b/go.sum index 5ae4477d..db65cb94 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= -github.com/ProtonMail/go-crypto v0.0.0-20220822140716-1678d6eb0cbe h1:R2HeCk7SG/XpoYZlEeI1v7sId7w2AMWwzOaVqXn45FE= -github.com/ProtonMail/go-crypto v0.0.0-20220822140716-1678d6eb0cbe/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5 h1:QXMwHM/lB4ZQhdEF7JUTNgYOJR/gWoFbgQ/2Aj1h3Dk= github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -172,6 +170,9 @@ github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= @@ -229,6 +230,8 @@ github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= @@ -470,6 +473,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U= github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= @@ -510,6 +515,8 @@ github.com/icza/gox v0.0.0-20210726201659-cd40a3f8d324/go.mod h1:VbcN86fRkkUMPX2 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= @@ -624,7 +631,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -742,7 +748,6 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= @@ -755,6 +760,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= @@ -853,6 +859,8 @@ github.com/tomarrell/wrapcheck/v2 v2.8.0 h1:qDzbir0xmoE+aNxGCPrn+rUSxAX+nG6vREgb github.com/tomarrell/wrapcheck/v2 v2.8.0/go.mod h1:ao7l5p0aOlUNJKI0qVwB4Yjlqutd0IvAB9Rdwyilxvg= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= +github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= @@ -1125,6 +1133,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1153,7 +1162,6 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1165,7 +1173,6 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= diff --git a/yb-support-tool/README.md b/yb-support-tool/README.md index 85b1dc43..05b391a1 100644 --- a/yb-support-tool/README.md +++ b/yb-support-tool/README.md @@ -2,7 +2,35 @@ `yb-support-tool` is a collection of tools to ease interaction with Yugabyte Support. -Today, the tool supports an "upload" command which enables files to be uploaded to a provided support ticket +- `upload` -> allows direct upload of files to support tickets +- `exec` -> allows commands to be executed across all nodes in a universe (or multiple universes) +- `yugaware-client` -> bundles the existing `yugaware-client` from `yb-tools` +- `yugatool` -> bundles the existing `yugatool` from `yb-tools` + +
+ +``` +$ ./yb-support-tool +Yugabyte Support Tool + +Usage: + yb-support-tool [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + exec Run commands across all nodes in a universe + help Help about any command + upload Upload attachment to a support case + version + yugatool A tool to make troubleshooting yugabyte somewhat easier + yugaware-client A tool to deploy/configure yugaware + +Flags: + -h, --help help for yb-support-tool + --verbose Print verbose logs + +Use "yb-support-tool [command] --help" for more information about a command. +``` ## upload @@ -11,7 +39,193 @@ The upload command uploads one or more files to a support ticket. It can also be There is a limit of 10 files and aggregate file size of 100 GB, so files larger than 100 GB must be compressed or split before uploading. This is not enforced, but the upload will fail. +``` +Usage: + yb-support-tool upload -c [case number] -e [email] [files] [flags] + +Flags: + -c, --case int Zendesk case number to attach files to (required) + --dropzone_id string Override default dropzone ID (default "BdFZz_JoZqtqPVueANkspD86KZ_PJsW1kIf_jVHeCO0") + -e, --email string Email address of submitter (required) + -h, --help help for upload + --retries uint number file upload retry attempts (default 5) + +Global Flags: + --verbose Print verbose logs +``` + +## exec + +The exec command is used to execute commands over all nodes in a universe (or multiple universes). Universe details can be provided in two ways: + +- using the "yba" command, which will invoke `yugaware-client` to programmatically query the YBA API. + +- using the "file" command, and manually providing a `json` or `yaml +inventory file. + + +``` +Usage: + yb-support-tool exec [flags] + yb-support-tool exec [command] + +Available Commands: + file Lookup inventory using inventory file + yba Lookup inventory using YBA API + +Flags: + --cmd string command to run across all universe nodes + -h, --help help for exec + -p, --parallel run commands against all nodes simultaneously + --universe string universe to run commands against + -u, --user string ssh user to run commands with (default "yugabyte") + --verbose show node details next to ssh output + +Use "yb-support-tool exec [command] --help" for more information about a command. +``` + +#### inventory file templates + +```json +[ + { + "name":"yba-replicated-universe-1", + "universeDetails":{ + "clusters":[ + { + "clusterType":"PRIMARY", + "userIntent":{ + "accessKeyCode":"yb-dev--key", + "provider":"" + } + } + ], + "nodeDetailsSet":[ + { + "cloudInfo":{ + "private_ip":"10.10.10.100" + }, + "nodeName":"yb-dev-universe-1-n2", + "nodeIdx":"2" + }, + { + "cloudInfo":{ + "private_ip":"10.10.10.101" + }, + "nodeName":"yb-dev-universe-1-n3", + "nodeIdx":"3" + }, + { + "cloudInfo":{ + "private_ip":"10.10.10.102" + }, + "nodeName":"yb-dev-universe-1-n1", + "nodeIdx":"1" + } + ], + "nodePrefix":"yb-dev-universe-1" + } + } + ] + ``` + +```yaml +--- +- name: yba-replicated-universe-1 + universeDetails: + clusters: + - clusterType: PRIMARY + userIntent: + accessKeyCode: yb-dev--key + provider: + nodeDetailsSet: + - cloudInfo: + private_ip: 10.10.10.100 + nodeName: yb-dev-universe-1-n2 + nodeIdx: '2' + - cloudInfo: + private_ip: 10.10.10.101 + nodeName: yb-dev-universe-1-n3 + nodeIdx: '3' + - cloudInfo: + private_ip: 10.10.10.102 + nodeName: yb-dev-universe-1-n1 + nodeIdx: '1' + nodePrefix: yb-dev-universe-1 +``` + + +## `yugatool` and `yugaware-client` + +Running `./yb-support-tool yugatool` and `./yb-support-tool yugaware` will run the existing utilities from `yb-tools` + +``` +./yb-support-tool yugatool +A tool to make troubleshooting yugabyte somewhat easier + +Usage: + yb-support-tool yugatool [command] + +Available Commands: + cluster_info Export cluster information + healthcheck Run yugabyte health checks + tablet_info Get tablet consensus info and state + util Miscellaneous utilities + xcluster Various utilities to interract with xcluster replication + +Flags: + -c, --cacert string the path to the CA certificate + --client-cert string the path to the client certificate + --client-key string the path to the client key file + --config string config file (default is $HOME/.yugatool.yaml) + --debug debug mode + --dial-timeout int number of seconds for dial timeouts (default 10) + -h, --help help for yugatool + -m, --master-addresses string comma-separated list of YB Master server addresses (minimum of one) + -o, --output string Output options as one of: [table, json, yaml] (default "table") + --skiphostverification skip tls host verification + -v, --version version for yugatool + +Global Flags: + --verbose Print verbose logs + +Use "yb-support-tool yugatool [command] --help" for more information about a command. +``` + +``` +$ ./yb-support-tool yugaware-client +A tool to deploy/configure yugaware + +Usage: + yb-support-tool yugaware-client [command] + +Available Commands: + backup Interact with Yugabyte backups + certificate Interact with Yugabyte certificates + login Login to a Yugaware server + provider Interact with Yugaware providers + register Register a Yugaware server + session Session management utilities + storage Interact with Yugaware storage + universe Interact with Yugabyte universes + version Print the client and server version + +Flags: + --api-token string api token for yugaware session + -c, --cacert string the path to the CA certificate + --client-cert string the path to the client certificate + --client-key string the path to the client key file + --config string config file (default is $HOME/.yugatool.yaml) + --debug debug mode + --dialtimeout int number of seconds for dial timeouts (default 10) + -h, --help help for yugaware-client + --hostname string hostname of yugaware (default "localhost:8080") + -o, --output string Output options as one of: [table, json, yaml] (default "table") + --skiphostverification skip tls host verification +Global Flags: + --verbose Print verbose logs +``` ## TODO diff --git a/yb-support-tool/cmd/exec.go b/yb-support-tool/cmd/exec.go new file mode 100644 index 00000000..310be24a --- /dev/null +++ b/yb-support-tool/cmd/exec.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "github.com/yugabyte/yb-tools/yb-support-tool/exec" +) + +var ( + command string + universe string + iFileName string + user string + isParallel bool + hostname string + apiToken string + isInsecure bool + isVerbose bool +) + +var inventories []exec.Inventory + +var execCmd = &cobra.Command{ + Use: "exec", + Short: "Run commands across all nodes in a universe", + Args: cobra.OnlyValidArgs, + + // nolint: errcheck + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + os.Exit(0) + }, +} + +var ybaCmd = &cobra.Command{ + Use: "yba", + Short: "Lookup inventory using YBA API", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + inventories = exec.YbaLookup(hostname, apiToken, isInsecure, isVerbose) + exec.SshCmd(inventories, universe, user, command, isParallel, isVerbose) + }, +} + +var fileCmd = &cobra.Command{ + Use: "file", + Short: "Lookup inventory using inventory file", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + inventories = exec.FileLookup(iFileName, isVerbose) + exec.SshCmd(inventories, universe, user, command, isParallel, isVerbose) + }, +} + +// nolint: errcheck +func addExecCmdsFlags() { + + // global flags + execCmd.PersistentFlags().StringVar(&command, "cmd", "", "command to run across all universe nodes") + execCmd.PersistentFlags().StringVar(&universe, "universe", "", "universe to run commands against") + execCmd.PersistentFlags().BoolVarP(&isParallel, "parallel", "p", false, "run commands against all nodes simultaneously") + execCmd.PersistentFlags().StringVarP(&user, "user", "u", "yugabyte", "ssh user to run commands with") + execCmd.PersistentFlags().BoolVar(&isVerbose, "verbose", false, "show node details next to ssh output") + + execCmd.MarkPersistentFlagRequired("cmd") + + // yba command + execCmd.AddCommand(ybaCmd) + + ybaCmd.Flags().StringVar(&hostname, "hostname", "", "YBA hostname or IP") + ybaCmd.Flags().StringVar(&apiToken, "api-token", "", "API token for YBA authentication") + ybaCmd.Flags().BoolVarP(&isInsecure, "insecure", "k", false, "Allow self-signed certificates") + + ybaCmd.MarkFlagRequired("hostname") + ybaCmd.MarkFlagRequired("api-token") + + // file command + execCmd.AddCommand(fileCmd) + + fileCmd.Flags().StringVarP(&iFileName, "inventory", "i", "", "file containing universe inventory") + fileCmd.MarkFlagRequired("inventory") + +} diff --git a/yb-support-tool/cmd/import.go b/yb-support-tool/cmd/import.go new file mode 100644 index 00000000..26276d58 --- /dev/null +++ b/yb-support-tool/cmd/import.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "github.com/blang/vfs" + ytCmd "github.com/yugabyte/yb-tools/yugatool/cmd" + ywcCmd "github.com/yugabyte/yb-tools/yugaware-client/cmd" +) + +func importYbTools() { + + // needed for ywc + var fs vfs.Filesystem + + // import as type "cobra.Command" from github repos + importYtCmd := ytCmd.RootInit(vfs.OS()) + importYwcCmd := ywcCmd.RootInit(fs) + + // add new cobra commands to our root cobra commands + rootCmd.AddCommand(importYtCmd) + rootCmd.AddCommand(importYwcCmd) +} diff --git a/yb-support-tool/cmd/root.go b/yb-support-tool/cmd/root.go index fb339558..7c7ad53b 100644 --- a/yb-support-tool/cmd/root.go +++ b/yb-support-tool/cmd/root.go @@ -2,68 +2,19 @@ package cmd import ( "fmt" - "net/mail" "os" - "github.com/docker/go-units" - "github.com/schollz/progressbar/v3" - - uploader "github.com/yugabyte/yb-tools/yb-support-tool/sendsafelyuploader" - "github.com/spf13/cobra" ) var Version = "pre-release" -// uploader variables -var ( - verbose bool - - // dropzone details - dropzoneID string - uploaderURL string - - // uploader variables - caseNum int - email string - - // package settings - concurrency int - retries uint - partSize int64 - partSizeString string -) - -const ( - defaultRetries = 5 - // testing shows these to be a good balance of speed and lower memory / CPU usage - defaultConcurency = 10 - defaultPartSize = 10 * units.MiB -) - -// globals -const ( - YBUploaderURL = "https://secure-upload.yugabyte.com" - YBDropzoneID = "BdFZz_JoZqtqPVueANkspD86KZ_PJsW1kIf_jVHeCO0" -) - var ( rootCmd = &cobra.Command{ Use: "yb-support-tool", Short: "Yugabyte Support Tool", } - uploadCmd = &cobra.Command{ - Use: "upload -c [case number] -e [email] [files]", - Short: "Upload attachment to a support case", - Long: `Uploads a file or files up to a limit of 100GB to a support ticket. Requires exising ticket to be created`, - Args: cobra.RangeArgs(1, 10), - Run: func(cmd *cobra.Command, args []string) { - if err := Upload(email, caseNum, args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) - } - }, - } + versionCmd = &cobra.Command{ Use: "version", Run: func(cmd *cobra.Command, args []string) { @@ -86,131 +37,15 @@ func Execute() { // nolint: errcheck func init() { - // root command flags - //subcommands - rootCmd.AddCommand(uploadCmd) + // import existing tools from yb-tools repo + importYbTools() + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(uploadCmd) + rootCmd.AddCommand(execCmd) rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Print verbose logs") - // upload command flags - uploadCmd.Flags().IntVarP(&caseNum, "case", "c", 0, "Zendesk case number to attach files to (required)") - uploadCmd.Flags().StringVarP(&email, "email", "e", "", "Email address of submitter (required)") - uploadCmd.Flags().IntVarP(&concurrency, "parallelism", "p", defaultConcurency, "parallelism") - uploadCmd.Flags().UintVar(&retries, "retries", defaultRetries, "number file upload retry attempts") - uploadCmd.Flags().StringVar(&partSizeString, "partSize", "", "Part size used to upload file. Must be between 1KB and 2.5MB") - - uploadCmd.MarkFlagRequired("case") - uploadCmd.MarkFlagRequired("email") - - // default dropzone ID is set to Yugabyte Support's anonymous dropzone - // this can be overridden with the --dropzone_id flag - uploadCmd.Flags().StringVar(&dropzoneID, "dropzone_id", YBDropzoneID, "Override default dropzone ID") - // hide the dropzone_id flag from the help menu - - // default uploader URL is set to Yugabyte Support's instance of sendsafely - // this can be overridden with the --secure_upload_url flag - uploadCmd.Flags().StringVar(&uploaderURL, "secure_upload_url", YBUploaderURL, "Override default secure uploader URL") - - // Hidden flags: partSize, retries, parallelism, url - uploadCmd.Flags().MarkHidden("partSize") - uploadCmd.Flags().MarkHidden("secure_upload_url") - uploadCmd.Flags().MarkHidden("parallelism") - -} - -func Upload(email string, caseNum int, files []string) error { - - validateArgs() - - u := uploader.CreateUploader(uploaderURL, dropzoneID, "DROP_ZONE") - - p, err := u.CreateDropzonePackage(uploader.WithConcurrency(concurrency), uploader.WithRetries(retries), uploader.WithChunkSize(partSize)) - if err != nil { - return fmt.Errorf("Unable to create dropzone package: %s", err) - } - - for _, fileName := range files { - file, err := os.Open(fileName) - if err != nil { - return err - } - fmt.Printf("Preparing to upload %s\n", fileName) - f, err := p.AddFileToPackage(file) - if err != nil { - return fmt.Errorf("Unable to add file to package: %s", err) - } - - bar := progressbar.Default(int64(f.Info.Parts), fmt.Sprintf("Uploading file: %s", f.Info.Name)) - progressbar.OptionSetItsString("part")(bar) - go func() { - for i := range f.Status { - _ = bar.Add(i) - } - }() - - if err := p.UploadFileParts(f); err != nil { - return fmt.Errorf("Unable to upload file parts: %s", err) - } - - if err := p.MarkFileComplete(f); err != nil { - return err - } - - } - - fmt.Printf("File uploads complete\nFinalizing Package...\n") - - err = p.FinalizePackage() - if err != nil { - return fmt.Errorf("Unable to finalize package: %s", err) - } - - if verbose { - fmt.Printf("Package available at: %s\n", p.URL) - } - - // Step 7 - Invoke the Hosted Dropzone Submission Endpoint - if err := p.SubmitHostedDropzone(fmt.Sprint(caseNum), email); err != nil { - return fmt.Errorf("Unable to push file to Hosted Dropzone: %s", err) - } - - return nil -} - -func validateArgs() { - var err error - if partSizeString != "" { - partSize, err = units.FromHumanSize(partSizeString) - if err != nil { - printErrorAndExit("Unable to parse --partSize flag: %s\n", err) - } - } else { - partSize = defaultPartSize - } - - if concurrency < 1 || concurrency > 100 { - fmt.Printf("Parallism must be a value between 1 and 100, reverting to default of %d\n", defaultConcurency) - concurrency = defaultConcurency - } - - if retries > 100 { - fmt.Fprintf(os.Stderr, "Setting retries to the max of 100\n") - retries = defaultRetries - } - - if caseNum < 1 { - printErrorAndExit("Case number must be a positive value\n") - } - - _, err = mail.ParseAddress(email) - if err != nil { - printErrorAndExit("Invalid email provided: %s\n", email) - } - -} - -func printErrorAndExit(format string, a ...any) { - fmt.Fprintf(os.Stderr, format, a...) - os.Exit(1) + addUploadFlags() + addExecCmdsFlags() } diff --git a/yb-support-tool/cmd/upload.go b/yb-support-tool/cmd/upload.go new file mode 100644 index 00000000..2804c1a5 --- /dev/null +++ b/yb-support-tool/cmd/upload.go @@ -0,0 +1,181 @@ +package cmd + +import ( + "fmt" + "net/mail" + "os" + + "github.com/docker/go-units" + "github.com/schollz/progressbar/v3" + "github.com/spf13/cobra" + uploader "github.com/yugabyte/yb-tools/yb-support-tool/sendsafelyuploader" +) + +// uploader variables +var ( + verbose bool + + // dropzone details + dropzoneID string + uploaderURL string + + // uploader variables + caseNum int + email string + + // package settings + concurrency int + retries uint + partSize int64 + partSizeString string +) + +const ( + defaultRetries = 5 + // testing shows these to be a good balance of speed and lower memory / CPU usage + defaultConcurency = 10 + defaultPartSize = 10 * units.MiB +) + +// globals +const ( + YBUploaderURL = "https://secure-upload.yugabyte.com" + YBDropzoneID = "BdFZz_JoZqtqPVueANkspD86KZ_PJsW1kIf_jVHeCO0" +) + +var uploadCmd = &cobra.Command{ + Use: "upload -c [case number] -e [email] [files]", + Short: "Upload attachment to a support case", + Long: `Uploads a file or files up to a limit of 100GB to a support ticket. Requires exising ticket to be created`, + Args: cobra.RangeArgs(1, 10), + Run: func(cmd *cobra.Command, args []string) { + if err := Upload(email, caseNum, args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + }, +} + +// nolint: errcheck +func addUploadFlags() { + // upload command flags + uploadCmd.Flags().IntVarP(&caseNum, "case", "c", 0, "Zendesk case number to attach files to (required)") + uploadCmd.Flags().StringVarP(&email, "email", "e", "", "Email address of submitter (required)") + uploadCmd.Flags().IntVarP(&concurrency, "parallelism", "p", defaultConcurency, "parallelism") + uploadCmd.Flags().UintVar(&retries, "retries", defaultRetries, "number file upload retry attempts") + uploadCmd.Flags().StringVar(&partSizeString, "partSize", "", "Part size used to upload file. Must be between 1KB and 2.5MB") + + uploadCmd.MarkFlagRequired("case") + uploadCmd.MarkFlagRequired("email") + + // default dropzone ID is set to Yugabyte Support's anonymous dropzone + // this can be overridden with the --dropzone_id flag + uploadCmd.Flags().StringVar(&dropzoneID, "dropzone_id", YBDropzoneID, "Override default dropzone ID") + // hide the dropzone_id flag from the help menu + + // default uploader URL is set to Yugabyte Support's instance of sendsafely + // this can be overridden with the --secure_upload_url flag + uploadCmd.Flags().StringVar(&uploaderURL, "secure_upload_url", YBUploaderURL, "Override default secure uploader URL") + + // Hidden flags: partSize, retries, parallelism, url + uploadCmd.Flags().MarkHidden("partSize") + uploadCmd.Flags().MarkHidden("secure_upload_url") + uploadCmd.Flags().MarkHidden("parallelism") + +} + +func Upload(email string, caseNum int, files []string) error { + + validateArgs() + + u := uploader.CreateUploader(uploaderURL, dropzoneID, "DROP_ZONE") + + p, err := u.CreateDropzonePackage(uploader.WithConcurrency(concurrency), uploader.WithRetries(retries), uploader.WithChunkSize(partSize)) + if err != nil { + return fmt.Errorf("Unable to create dropzone package: %s", err) + } + + for _, fileName := range files { + file, err := os.Open(fileName) + if err != nil { + return err + } + fmt.Printf("Preparing to upload %s\n", fileName) + f, err := p.AddFileToPackage(file) + if err != nil { + return fmt.Errorf("Unable to add file to package: %s", err) + } + + bar := progressbar.Default(int64(f.Info.Parts), fmt.Sprintf("Uploading file: %s", f.Info.Name)) + progressbar.OptionSetItsString("part")(bar) + go func() { + for i := range f.Status { + _ = bar.Add(i) + } + }() + + if err := p.UploadFileParts(f); err != nil { + return fmt.Errorf("Unable to upload file parts: %s", err) + } + + if err := p.MarkFileComplete(f); err != nil { + return err + } + + } + + fmt.Printf("File uploads complete\nFinalizing Package...\n") + + err = p.FinalizePackage() + if err != nil { + return fmt.Errorf("Unable to finalize package: %s", err) + } + + if verbose { + fmt.Printf("Package available at: %s\n", p.URL) + } + + // Step 7 - Invoke the Hosted Dropzone Submission Endpoint + if err := p.SubmitHostedDropzone(fmt.Sprint(caseNum), email); err != nil { + return fmt.Errorf("Unable to push file to Hosted Dropzone: %s", err) + } + + return nil +} + +func validateArgs() { + var err error + if partSizeString != "" { + partSize, err = units.FromHumanSize(partSizeString) + if err != nil { + printErrorAndExit("Unable to parse --partSize flag: %s\n", err) + } + } else { + partSize = defaultPartSize + } + + if concurrency < 1 || concurrency > 100 { + fmt.Printf("Parallism must be a value between 1 and 100, reverting to default of %d\n", defaultConcurency) + concurrency = defaultConcurency + } + + if retries > 100 { + fmt.Fprintf(os.Stderr, "Setting retries to the max of 100\n") + retries = defaultRetries + } + + if caseNum < 1 { + printErrorAndExit("Case number must be a positive value\n") + } + + _, err = mail.ParseAddress(email) + if err != nil { + printErrorAndExit("Invalid email provided: %s\n", email) + } + +} + +func printErrorAndExit(format string, a ...any) { + fmt.Fprintf(os.Stderr, format, a...) + os.Exit(1) +} diff --git a/yb-support-tool/exec/inventory.go b/yb-support-tool/exec/inventory.go new file mode 100644 index 00000000..199f71bb --- /dev/null +++ b/yb-support-tool/exec/inventory.go @@ -0,0 +1,130 @@ +package exec + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/blang/vfs" + ywcCmd "github.com/yugabyte/yb-tools/yugaware-client/cmd" + "gopkg.in/yaml.v2" +) + +type Inventory struct { + Name string `json:"name" yaml:"name"` + UniverseDetails struct { + Clusters []struct { + ClusterType string `json:"clusterType" yaml:"clusterType"` + UserIntent struct { + AccessKeyCode string `json:"accessKeyCode" yaml:"accessKeyCode"` + Provider string `json:"provider" yaml:"provider"` + } `json:"userIntent" yaml:"userIntent"` + } `json:"clusters" yaml:"clusters"` + NodeDetailsSet []struct { + CloudInfo struct { + PrivateIP string `json:"private_ip" yaml:"private_ip"` + } `json:"cloudInfo" yaml:"cloudInfo"` + NodeName string `json:"nodeName" yaml:"nodeName"` + NodeIdx int `json:"nodeIdx" yaml:"nodeIdx"` + IsMaster bool `json:"isMaster" yaml:"isMaster"` + } `json:"nodeDetailsSet" yaml:"nodeDetailsSet"` + NodePrefix string `json:"nodePrefix" yaml:"nodePrefix"` + } `json:"universeDetails" yaml:"universeDetails"` +} + +func YbaLookup(hostname, apiToken string, isInsecure, isVerbose bool) []Inventory { + + // yugaware-client returns the API response wrapped in "content: " + type apiResp struct { + Content []Inventory `json:"content"` + } + + if isVerbose { + fmt.Println("Initializing yugaware-client") + } + + var fs vfs.Filesystem + ywCommand := ywcCmd.RootInit(fs) + + var ( + content apiResp + args []string + ) + + if isVerbose { + fmt.Printf("Command: \"yugaware-client universe list -o json --hostname %s --api-token \"\n", hostname) + } + + args = append(args, "universe", "list", "-o", "json") + args = append(args, "--hostname", hostname, "--api-token", apiToken) + if isInsecure { + args = append(args, "--skiphostverification") + } + buf := new(bytes.Buffer) + ywCommand.SetOut(buf) + ywCommand.SetErr(buf) + + ywCommand.SetArgs(args) + + // run yugaware-client command using cobra + err := ywCommand.Execute() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + buffer, err := buf.Bytes(), err + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + err = json.Unmarshal(buffer, &content) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // content.Content is the raw api response, which is "[]inventory" + return content.Content +} + +func FileLookup(iFileName string, isVerbose bool) []Inventory { + + var inventories []Inventory + + if isVerbose { + fmt.Printf("Opening inventory file: \"%s\"\n", iFileName) + } + + f, _ := filepath.Abs(iFileName) + inventoryFile, err := os.Open(f) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + defer inventoryFile.Close() + + inventoryBytes, err := io.ReadAll(inventoryFile) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // this will unmarshal both json and yaml properly + err = yaml.Unmarshal(inventoryBytes, &inventories) + + if err != nil { + fmt.Printf("Unable to parse inventory file \"%s\": %s", iFileName, err) + os.Exit(1) + } + + return inventories +} diff --git a/yb-support-tool/exec/ssh.go b/yb-support-tool/exec/ssh.go new file mode 100644 index 00000000..52e9ef82 --- /dev/null +++ b/yb-support-tool/exec/ssh.go @@ -0,0 +1,140 @@ +package exec + +import ( + "fmt" + "net" + "os" + "strconv" + "strings" + "sync" + + "golang.org/x/crypto/ssh" +) + +func runSsh(conn *ssh.Client, cmd, nodeIdx, nodeName, nodePrivateIp string, isVerbose bool) []byte { + + var res []byte + + session, err := conn.NewSession() + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + defer session.Close() + res, err = session.CombinedOutput(cmd) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + output := string(res) + + // by default, only the output is shown with no additional details + // if the verbose flag is used, then node information is printed before the output of each command + if isVerbose { + + // remove the last newline (if applicable) so we can accurately determine if this is multi-line output + output = strings.TrimSuffix(output, "\n") + + newlineCount := len(strings.Split(output, "\n")) + verboseFmt := "n" + nodeIdx + ": " + nodeName + ": " + nodePrivateIp + + if newlineCount > 1 { + // print output on a separate line if it's multi-line output + fmt.Println("-- " + verboseFmt + " --") + fmt.Println(output) + } else { + // print everything on a single line if the output is single-line + fmt.Println(verboseFmt + ": " + output) + } + } else { + fmt.Print(output) + } + + return res +} + +func SshCmd(inventories []Inventory, universe, user, cmd string, isParallel bool, isVerbose bool) { + + var privateKeyPathPrefix = "/opt/yugabyte/yugaware/data/keys/" + var privateKeyPath string + + for _, inventory := range inventories { + for _, cluster := range inventory.UniverseDetails.Clusters { + if cluster.ClusterType == "PRIMARY" { + privateKeyPath = privateKeyPathPrefix + cluster.UserIntent.Provider + "/" + cluster.UserIntent.AccessKeyCode + ".pem" + break + } + } + } + + pemBytes, err := os.ReadFile(privateKeyPath) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + config := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.HostKeyCallback( + func(host string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + ), + } + + for _, inventory := range inventories { + + // was the universe flag used by the user? + if universe != "" { + + // check to see if the universe name in this inventory matches the universe supplied by the user + if inventory.Name != universe { + continue + } + } + + // workgroup to use if isParallel is true + wg := &sync.WaitGroup{} + + for _, nodeDetailsSet := range inventory.UniverseDetails.NodeDetailsSet { + + nodeIdx := strconv.Itoa(nodeDetailsSet.NodeIdx) + nodeName := nodeDetailsSet.NodeName + nodePrivateIp := nodeDetailsSet.CloudInfo.PrivateIP + + host := nodeDetailsSet.CloudInfo.PrivateIP + ":22" + conn, err := ssh.Dial("tcp", host, config) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if isParallel { + wg.Add(1) + go func(wg *sync.WaitGroup) { + runSsh(conn, cmd, nodeIdx, nodeName, nodePrivateIp, isVerbose) + wg.Done() + }(wg) + } else { + runSsh(conn, cmd, nodeIdx, nodeName, nodePrivateIp, isVerbose) + } + } + wg.Wait() + } + +} diff --git a/yb-support-tool/main.go b/yb-support-tool/main.go index 1eb2e454..ae3a978b 100644 --- a/yb-support-tool/main.go +++ b/yb-support-tool/main.go @@ -5,7 +5,6 @@ import ( ) func main() { - // executes cobra for command line interaction cmd.Execute() }