From 17a55bb41c22f26fa09ee356fc3fcfcda1bcc5b3 Mon Sep 17 00:00:00 2001 From: tongjingran Date: Mon, 12 Jul 2021 16:39:16 +0800 Subject: [PATCH] add goc list --- cmd/list.go | 27 +++++++++ go.mod | 3 + go.sum | 5 ++ pkg/client/client.go | 118 ++++++++++++++++++++++++++++++++++++++ pkg/client/client_test.go | 60 +++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 cmd/list.go create mode 100644 pkg/client/client.go create mode 100644 pkg/client/client_test.go diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..3eb4b2d --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "github.com/qiniu/goc/v2/pkg/client" + "github.com/qiniu/goc/v2/pkg/config" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "Lists all the registered services", + Long: "Lists all the registered services", + Example: ` +goc list [flags] +`, + + Run: list, +} + +func init() { + listCmd.Flags().StringVarP(&config.GocConfig.Host, "host", "", "127.0.0.1:7777", "specify the host of the goc server") + rootCmd.AddCommand(listCmd) +} + +func list(cmd *cobra.Command, args []string) { + client.NewWorker("http://" + config.GocConfig.Host).ListAgents() +} diff --git a/go.mod b/go.mod index 759dc88..9e97b01 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,11 @@ require ( github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d + github.com/olekukonko/tablewriter v0.0.5 + github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.7.0 github.com/tongjingran/copy v1.4.2 go.uber.org/zap v1.17.0 golang.org/x/mod v0.4.2 diff --git a/go.sum b/go.sum index a252527..65f0ce3 100644 --- a/go.sum +++ b/go.sum @@ -853,6 +853,8 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v0.0.0-20160514122348-38ee283dabf1/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -932,6 +934,8 @@ github.com/octago/sflags v0.2.0/go.mod h1:G0bjdxh4qPRycF74a2B8pU36iTp9QHGx0w0dFZ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1096,6 +1100,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= diff --git a/pkg/client/client.go b/pkg/client/client.go new file mode 100644 index 0000000..3af0e91 --- /dev/null +++ b/pkg/client/client.go @@ -0,0 +1,118 @@ +package client + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/qiniu/goc/v2/pkg/log" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" +) + +// Action provides methods to contact with the covered agent under test +type Action interface { + ListAgents() +} + +const ( + // CoverAgentsListAPI list all the registered agents + CoverAgentsListAPI = "/v2/rpcagents" +) + +var ( + ERROR_GOC_LIST = errors.New("goc list failed") +) + +type client struct { + Host string + client *http.Client +} + +// gocListAgents response of the list request +type gocListAgents struct { + Items []gocCoveredAgent `json:"items"` +} + +// gocCoveredAgent represents a covered client +type gocCoveredAgent struct { + Id string `json:"id"` + RemoteIP string `json:"remoteip"` + Hostname string `json:"hostname"` + CmdLine string `json:"cmdline"` + Pid string `json:"pid"` +} + +// NewWorker creates a worker to contact with host +func NewWorker(host string) Action { + _, err := url.ParseRequestURI(host) + if err != nil { + log.Fatalf("Parse url %s failed, err: %v", host, err) + } + return &client{ + Host: host, + client: http.DefaultClient, + } +} + +func (c *client) ListAgents() { + u := fmt.Sprintf("%s%s", c.Host, CoverAgentsListAPI) + _, body, err := c.do("GET", u, "", nil) + if err != nil && isNetworkError(err) { + _, body, err = c.do("GET", u, "", nil) + } + if err != nil { + err = fmt.Errorf("%w: %v", ERROR_GOC_LIST, err) + log.Fatalf(err.Error()) + } + agents := gocListAgents{} + err = json.Unmarshal(body, &agents) + if err != nil { + err = fmt.Errorf("%w: json unmarshal failed: %v", ERROR_GOC_LIST, err) + log.Fatalf(err.Error()) + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"ID", "RemoteIP", "Hostname", "Pid", "CMD"}) + table.SetAutoFormatHeaders(false) + table.SetColumnAlignment([]int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}) + for _, agent := range agents.Items { + table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine}) + } + table.Render() + return +} + +func (c *client) do(method, url, contentType string, body io.Reader) (*http.Response, []byte, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, nil, err + } + + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + + res, err := c.client.Do(req) + if err != nil { + return nil, nil, err + } + defer res.Body.Close() + + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return res, nil, err + } + return res, responseBody, nil +} + +func isNetworkError(err error) bool { + if err == io.EOF { + return true + } + _, ok := err.(net.Error) + return ok +} diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go new file mode 100644 index 0000000..0780a33 --- /dev/null +++ b/pkg/client/client_test.go @@ -0,0 +1,60 @@ +package client + +import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func captureStdout(f func()) string { + r, w, err := os.Pipe() + if err != nil { + logrus.WithError(err).Fatal("os pipe fail") + } + stdout := os.Stdout + os.Stdout = w + defer func() { + os.Stdout = stdout + }() + + f() + w.Close() + + var buf bytes.Buffer + io.Copy(&buf, r) + + return buf.String() +} + +func TestClientListAgents(t *testing.T) { + mockAgents := `{"items": [{"id": "testID", "remoteip": "1.1.1.1", "hostname": "testHost", "cmdline": "testCmd", "pid": "0"}]}` + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(mockAgents)) + })) + defer mockServer.Close() + + c := NewWorker(mockServer.URL) + output := captureStdout(c.ListAgents) + expected := `+--------+----------+----------+-----+---------+ +| ID | RemoteIP | Hostname | Pid | CMD | ++--------+----------+----------+-----+---------+ +| testID | 1.1.1.1 | testHost | 0 | testCmd | ++--------+----------+----------+-----+---------+ +` + assert.Equal(t, output, expected) +} + +func TestClientDo(t *testing.T) { + c := &client{ + client: http.DefaultClient, + } + _, _, err := c.do(" ", "http://127.0.0.1:7777", "", nil) // a invalid method + assert.Contains(t, err.Error(), "invalid method") +}