diff --git a/cmd/list.go b/cmd/list.go index 3eb4b2d..38451cf 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -17,11 +17,14 @@ goc list [flags] Run: list, } +var listWide bool + func init() { - listCmd.Flags().StringVarP(&config.GocConfig.Host, "host", "", "127.0.0.1:7777", "specify the host of the goc server") + listCmd.Flags().StringVar(&config.GocConfig.Host, "host", "127.0.0.1:7777", "specify the host of the goc server") + listCmd.Flags().BoolVar(&listWide, "wide", false, "List all services with more information (such as pid)") rootCmd.AddCommand(listCmd) } func list(cmd *cobra.Command, args []string) { - client.NewWorker("http://" + config.GocConfig.Host).ListAgents() + client.NewWorker("http://" + config.GocConfig.Host).ListAgents(listWide) } diff --git a/go.mod b/go.mod index 9e97b01..59817bc 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( 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 @@ -18,6 +17,7 @@ require ( go.uber.org/zap v1.17.0 golang.org/x/mod v0.4.2 golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d golang.org/x/tools v0.1.3 k8s.io/kubectl v0.21.2 k8s.io/test-infra v0.0.0-20210618100605-34aa2f2aa75b diff --git a/go.sum b/go.sum index 65f0ce3..3bdd798 100644 --- a/go.sum +++ b/go.sum @@ -1100,7 +1100,6 @@ 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 index 3af0e91..2d1fac3 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -2,21 +2,22 @@ package client import ( "encoding/json" - "errors" "fmt" - "github.com/olekukonko/tablewriter" - "github.com/qiniu/goc/v2/pkg/log" + "golang.org/x/term" "io" "io/ioutil" "net" "net/http" "net/url" "os" + + "github.com/olekukonko/tablewriter" + "github.com/qiniu/goc/v2/pkg/log" ) // Action provides methods to contact with the covered agent under test type Action interface { - ListAgents() + ListAgents(bool) } const ( @@ -24,10 +25,6 @@ const ( CoverAgentsListAPI = "/v2/rpcagents" ) -var ( - ERROR_GOC_LIST = errors.New("goc list failed") -) - type client struct { Host string client *http.Client @@ -51,7 +48,7 @@ type gocCoveredAgent struct { func NewWorker(host string) Action { _, err := url.ParseRequestURI(host) if err != nil { - log.Fatalf("Parse url %s failed, err: %v", host, err) + log.Fatalf("parse url %s failed, err: %v", host, err) } return &client{ Host: host, @@ -59,33 +56,64 @@ func NewWorker(host string) Action { } } -func (c *client) ListAgents() { +func (c *client) ListAgents(wide bool) { 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) + err = fmt.Errorf("goc list failed: %v", 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) + err = fmt.Errorf("goc list failed: json unmarshal failed: %v", 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}) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding(" ") // pad with 3 blank spaces + table.SetNoWhiteSpace(true) + table.SetReflowDuringAutoWrap(false) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + if wide { + table.SetHeader([]string{"ID", "REMOTEIP", "HOSTNAME", "PID", "CMD"}) + table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) + } else { + table.SetHeader([]string{"ID", "REMOTEIP", "CMD"}) + table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) + } for _, agent := range agents.Items { - table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine}) + if wide { + table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine}) + } else { + preLen := len(agent.Id) + len(agent.RemoteIP) + 9 + table.Append([]string{agent.Id, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)}) + } } table.Render() return } +// getSimpleCmdLine +func getSimpleCmdLine(preLen int, cmdLine string) string { + pathLen := len(cmdLine) + width, _, err := term.GetSize(int(os.Stdin.Fd())) + if err != nil || width <= preLen+16 { + width = 16 + preLen // show at least 16 words of the command + } + if pathLen > width-preLen { + return cmdLine[:width-preLen] + } + return cmdLine +} + 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 { diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 0780a33..2bc5720 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -2,21 +2,18 @@ package client import ( "bytes" + "fmt" "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") - } + r, w, _ := os.Pipe() stdout := os.Stdout os.Stdout = w defer func() { @@ -33,7 +30,7 @@ func captureStdout(f func()) string { } func TestClientListAgents(t *testing.T) { - mockAgents := `{"items": [{"id": "testID", "remoteip": "1.1.1.1", "hostname": "testHost", "cmdline": "testCmd", "pid": "0"}]}` + mockAgents := `{"items": [{"id": "testID", "remoteip": "1.1.1.1", "hostname": "testHost", "cmdline": "./testCmd -f testArgs", "pid": "0"}]}` mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(mockAgents)) @@ -41,14 +38,31 @@ func TestClientListAgents(t *testing.T) { 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) + testCases := map[string]struct { + input bool + expected string + }{ + "simple list": { + false, + `ID REMOTEIP CMD +testID 1.1.1.1 ./testCmd -f tes +`, + }, + "wide list": { + true, + `ID REMOTEIP HOSTNAME PID CMD +testID 1.1.1.1 testHost 0 ./testCmd -f testArgs +`, + }, + } + for name, tt := range testCases { + t.Run(name, func(t *testing.T) { + f := func() { c.ListAgents(tt.input) } + output := captureStdout(f) + fmt.Println(output) + assert.Equal(t, output, tt.expected) + }) + } } func TestClientDo(t *testing.T) { @@ -58,3 +72,18 @@ func TestClientDo(t *testing.T) { _, _, err := c.do(" ", "http://127.0.0.1:7777", "", nil) // a invalid method assert.Contains(t, err.Error(), "invalid method") } + +func TestGetSimpleSvcName(t *testing.T) { + testCases := map[string]struct { + input string + expected string + }{ + "short path": {"1234567890abc.go", "1234567890abc.go"}, + "long path": {"1234567890abcdef.go", "1234567890abcdef"}, + } + for name, tt := range testCases { + t.Run(name, func(t *testing.T) { + assert.Equal(t, getSimpleCmdLine(0, tt.input), tt.expected) + }) + } +}