This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git

commit 8d4c95f758cf7cce163119cb2656b04ad5849824
Author: kezhenxu94 <kezhenx...@163.com>
AuthorDate: Sat Nov 9 12:34:35 2019 +0800

    Set up CI flow
---
 .github/workflows/go.yml                           |  12 +-
 commands/interceptor.go                            |  71 ------------
 commands/interceptor/duration.go                   | 121 +++++++++++++++++++++
 commands/interceptor/duration_test.go              |  98 +++++++++++++++++
 .../log.go => commands/interceptor/interceptor.go  |  25 ++---
 commands/{model.go => model/step.go}               |   5 +-
 commands/service/list.go                           |  24 ++--
 config/config.go                                   |   2 +-
 go.mod                                             |   1 +
 graphql/client/client.go                           |   2 +-
 logger/log.go                                      |   4 +-
 swctl/main.go                                      |  14 +--
 commands/model.go => util/io.go                    |  39 +++----
 13 files changed, 287 insertions(+), 131 deletions(-)

diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 1abc12e..de9ad27 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -40,5 +40,15 @@ jobs:
               dep ensure
           fi
 
+      - name: Lint
+        run: |
+          curl -sfL 
https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh 
-s -- -b $(go env GOPATH)/bin v1.21.0
+          $(go env GOPATH)/bin/golangci-lint run -v ./...
+
+      - name: Test
+        run: |
+          go test ./... -coverprofile=coverage.txt -covermode=atomic
+          bash <(curl -s https://codecov.io/bash)
+
       - name: Build
-        run: make
+        run: make clean && make
diff --git a/commands/interceptor.go b/commands/interceptor.go
deleted file mode 100644
index 6837529..0000000
--- a/commands/interceptor.go
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package commands
-
-import (
-       "github.com/apache/skywalking-cli/graphql/schema"
-       "github.com/apache/skywalking-cli/logger"
-       "github.com/urfave/cli"
-       "time"
-)
-
-// Convenient function to chain up multiple cli.BeforeFunc
-func BeforeChain(beforeFunctions []cli.BeforeFunc) cli.BeforeFunc {
-       return func(ctx *cli.Context) error {
-               for _, beforeFunc := range beforeFunctions {
-                       if err := beforeFunc(ctx); err != nil {
-                               return err
-                       }
-               }
-               return nil
-       }
-}
-
-var StepFormats = map[schema.Step]string{
-       schema.StepMonth:  "2006-01-02",
-       schema.StepDay:    "2006-01-02",
-       schema.StepHour:   "2006-01-02 15",
-       schema.StepMinute: "2006-01-02 1504",
-       schema.StepSecond: "2006-01-02 1504",
-}
-
-// Set the duration if not set, and format it according to
-// the given step
-func SetUpDuration(ctx *cli.Context) error {
-       step := ctx.Generic("step").(*StepEnumValue).Selected
-       end := ctx.String("end")
-       if len(end) == 0 {
-               end = time.Now().Format(StepFormats[step])
-               logger.Log.Debugln("Missing --end, defaults to", end)
-               if err := ctx.Set("end", end); err != nil {
-                       return err
-               }
-       }
-
-       start := ctx.String("start")
-       if len(start) == 0 {
-               start = time.Now().Add(-15 * 
time.Minute).Format(StepFormats[step])
-               logger.Log.Debugln("Missing --start, defaults to", start)
-               if err := ctx.Set("start", start); err != nil {
-                       return err
-               }
-       }
-
-       return nil
-}
diff --git a/commands/interceptor/duration.go b/commands/interceptor/duration.go
new file mode 100644
index 0000000..55c4b86
--- /dev/null
+++ b/commands/interceptor/duration.go
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package interceptor
+
+import (
+       "github.com/apache/skywalking-cli/graphql/schema"
+       "github.com/apache/skywalking-cli/logger"
+       "github.com/urfave/cli"
+       "time"
+)
+
+var stepFormats = map[schema.Step]string{
+       schema.StepSecond: "2006-01-02 1504",
+       schema.StepMinute: "2006-01-02 1504",
+       schema.StepHour:   "2006-01-02 15",
+       schema.StepDay:    "2006-01-02",
+       schema.StepMonth:  "2006-01-02",
+}
+
+var supportedTimeLayouts = []string{
+       "2006-01-02 150400",
+       "2006-01-02 1504",
+       "2006-01-02 15",
+       "2006-01-02",
+       "2006-01",
+}
+
+func tryParseTime(unparsed string, parsed *time.Time) error {
+       var possibleError error = nil
+       for _, layout := range supportedTimeLayouts {
+               t, err := time.Parse(layout, unparsed)
+               if err == nil {
+                       *parsed = t
+                       return nil
+               }
+               possibleError = err
+       }
+       return possibleError
+}
+
+// DurationInterceptor sets the duration if absent, and formats it accordingly,
+// see ParseDuration
+func DurationInterceptor(ctx *cli.Context) error {
+       start := ctx.String("start")
+       end := ctx.String("end")
+
+       startTime, endTime, step := ParseDuration(start, end)
+
+       if err := ctx.Set("start", startTime.Format(stepFormats[step])); err != 
nil {
+               return err
+       } else if err := ctx.Set("end", endTime.Format(stepFormats[step])); err 
!= nil {
+               return err
+       } else if err := ctx.Set("step", step.String()); err != nil {
+               return err
+       }
+       return nil
+}
+
+// ParseDuration parses the `start` and `end` to a triplet, (startTime, 
endTime, step)
+// if --start and --end are both absent, then: start := now - 30min; end := now
+// if --start is given, --end is absent, then: end := now
+// if --start is absent, --end is given, then: start := end - 30min
+// NOTE that when either(both) `start` or `end` is(are) given, there is no 
timezone info
+// in the format, (e.g. 2019-11-09 1001), so they'll be considered as 
UTC-based,
+// and generate the missing `start`(`end`) based on the same timezone, UTC
+func ParseDuration(start string, end string) (time.Time, time.Time, 
schema.Step) {
+       now := time.Now().UTC()
+
+       startTime := now
+       endTime := now
+       logger.Log.Debugln("Start time:", start, "end time:", end)
+       if len(start) == 0 && len(end) == 0 { // both absent
+               startTime = now.Add(-30 * time.Minute)
+               endTime = now
+       } else if len(end) == 0 { // start is present
+               if err := tryParseTime(start, &startTime); err != nil {
+                       logger.Log.Fatalln("Unsupported time format:", start, 
err)
+               }
+       } else if len(start) == 0 { // end is present
+               if err := tryParseTime(end, &endTime); err != nil {
+                       logger.Log.Fatalln("Unsupported time format:", end, err)
+               }
+       } else { // both are present
+               if err := tryParseTime(start, &startTime); err != nil {
+                       logger.Log.Fatalln("Unsupported time format:", start, 
err)
+               }
+               if err := tryParseTime(end, &endTime); err != nil {
+                       logger.Log.Fatalln("Unsupported time format:", end, err)
+               }
+       }
+       duration := endTime.Sub(startTime)
+       step := schema.StepSecond
+       if duration.Hours() >= 24*30 { // time range > 1 month
+               step = schema.StepMonth
+       } else if duration.Hours() > 24 { // time range > 1 day
+               step = schema.StepDay
+       } else if duration.Minutes() > 60 { // time range > 1 hour
+               step = schema.StepHour
+       } else if duration.Seconds() > 60 { // time range > 1 minute
+               step = schema.StepMinute
+       } else if duration.Seconds() <= 0 { // illegal
+               logger.Log.Fatalln("end time must be later than start time, end 
time:", endTime, ", start time:", startTime)
+       }
+       return startTime, endTime, step
+}
diff --git a/commands/interceptor/duration_test.go 
b/commands/interceptor/duration_test.go
new file mode 100644
index 0000000..7879cfd
--- /dev/null
+++ b/commands/interceptor/duration_test.go
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package interceptor
+
+import (
+       "github.com/apache/skywalking-cli/graphql/schema"
+       "reflect"
+       "testing"
+       "time"
+)
+
+func TestParseDuration(t *testing.T) {
+       now := time.Now().UTC()
+
+       type args struct {
+               start string
+               end   string
+       }
+       timeFormat := "2006-01-02 1504"
+       tests := []struct {
+               name            string
+               args            args
+               wantedStartTime time.Time
+               wantedEndTime   time.Time
+               wantedStep      schema.Step
+       }{
+               {
+                       name: "Should set current time if start is absent",
+                       args: args{
+                               start: "",
+                               end:   now.Add(10 * 
time.Minute).Format(timeFormat),
+                       },
+                       wantedStartTime: now,
+                       wantedEndTime:   now.Add(10 * time.Minute),
+                       wantedStep:      schema.StepMinute,
+               },
+               {
+                       name: "Should set current time if end is absent",
+                       args: args{
+                               start: now.Add(-10 * 
time.Minute).Format(timeFormat),
+                               end:   "",
+                       },
+                       wantedStartTime: now.Add(-10 * time.Minute),
+                       wantedEndTime:   now,
+                       wantedStep:      schema.StepMinute,
+               },
+               {
+                       name: "Should keep both if both are present",
+                       args: args{
+                               start: now.Add(-10 * 
time.Minute).Format(timeFormat),
+                               end:   now.Add(10 * 
time.Minute).Format(timeFormat),
+                       },
+                       wantedStartTime: now.Add(-10 * time.Minute),
+                       wantedEndTime:   now.Add(10 * time.Minute),
+                       wantedStep:      schema.StepMinute,
+               },
+               {
+                       name: "Should set both if both are absent",
+                       args: args{
+                               start: "",
+                               end:   "",
+                       },
+                       wantedStartTime: now.Add(-30 * time.Minute),
+                       wantedEndTime:   now,
+                       wantedStep:      schema.StepMinute,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       gotStartTime, gotEndTime, gotStep := 
ParseDuration(tt.args.start, tt.args.end)
+                       if 
!reflect.DeepEqual(gotStartTime.Truncate(time.Minute), 
tt.wantedStartTime.Truncate(time.Minute)) {
+                               t.Errorf("ParseDuration() got start time = %v, 
wanted start time %v", gotStartTime.Truncate(time.Minute), 
tt.wantedStartTime.Truncate(time.Minute))
+                       }
+                       if !reflect.DeepEqual(gotEndTime.Truncate(time.Minute), 
tt.wantedEndTime.Truncate(time.Minute)) {
+                               t.Errorf("ParseDuration() got end time = %v, 
wanted end time %v", gotEndTime.Truncate(time.Minute), 
tt.wantedEndTime.Truncate(time.Minute))
+                       }
+                       if gotStep != tt.wantedStep {
+                               t.Errorf("ParseDuration() got step = %v, wanted 
step %v", gotStep, tt.wantedStep)
+                       }
+               })
+       }
+}
diff --git a/logger/log.go b/commands/interceptor/interceptor.go
similarity index 69%
copy from logger/log.go
copy to commands/interceptor/interceptor.go
index 4a5d482..111b73b 100644
--- a/logger/log.go
+++ b/commands/interceptor/interceptor.go
@@ -16,23 +16,20 @@
  *
  */
 
-package logger
+package interceptor
 
 import (
-       "os"
-
-       "github.com/sirupsen/logrus"
+       "github.com/urfave/cli"
 )
 
-var Log *logrus.Logger
-
-func init() {
-       if Log == nil {
-               Log = logrus.New()
+// BeforeChain is a convenient function to chain up multiple cli.BeforeFunc
+func BeforeChain(beforeFunctions []cli.BeforeFunc) cli.BeforeFunc {
+       return func(ctx *cli.Context) error {
+               for _, beforeFunc := range beforeFunctions {
+                       if err := beforeFunc(ctx); err != nil {
+                               return err
+                       }
+               }
+               return nil
        }
-       Log.SetOutput(os.Stdout)
-       Log.SetFormatter(&logrus.TextFormatter{
-               FullTimestamp:   true,
-               TimestampFormat: "2006-01-02 15:04:05",
-       })
 }
diff --git a/commands/model.go b/commands/model/step.go
similarity index 89%
copy from commands/model.go
copy to commands/model/step.go
index 9da2fba..eed3b3f 100644
--- a/commands/model.go
+++ b/commands/model/step.go
@@ -16,7 +16,7 @@
  *
  */
 
-package commands
+package model
 
 import (
        "fmt"
@@ -24,12 +24,14 @@ import (
        "strings"
 )
 
+// StepEnumValue defines the values domain of --step option
 type StepEnumValue struct {
        Enum     []schema.Step
        Default  schema.Step
        Selected schema.Step
 }
 
+// Set the --step value, from raw string to StepEnumValue
 func (s *StepEnumValue) Set(value string) error {
        for _, enum := range s.Enum {
                if enum.String() == value {
@@ -44,6 +46,7 @@ func (s *StepEnumValue) Set(value string) error {
        return fmt.Errorf("allowed steps are %s", strings.Join(steps, ", "))
 }
 
+// String representation of the step
 func (s StepEnumValue) String() string {
        return s.Selected.String()
 }
diff --git a/commands/service/list.go b/commands/service/list.go
index e2c91af..257bc3a 100644
--- a/commands/service/list.go
+++ b/commands/service/list.go
@@ -21,7 +21,8 @@ package service
 import (
        "encoding/json"
        "fmt"
-       "github.com/apache/skywalking-cli/commands"
+       "github.com/apache/skywalking-cli/commands/interceptor"
+       "github.com/apache/skywalking-cli/commands/model"
        "github.com/apache/skywalking-cli/graphql/client"
        "github.com/apache/skywalking-cli/graphql/schema"
        "github.com/urfave/cli"
@@ -34,23 +35,24 @@ var ListCommand = cli.Command{
        Flags: []cli.Flag{
                cli.StringFlag{
                        Name:  "start",
-                       Usage: "Query start time",
+                       Usage: "query start `TIME`",
                },
                cli.StringFlag{
                        Name:  "end",
-                       Usage: "Query end time",
+                       Usage: "query end `TIME`",
                },
                cli.GenericFlag{
-                       Name: "step",
-                       Value: &commands.StepEnumValue{
+                       Name:   "step",
+                       Hidden: true,
+                       Value: &model.StepEnumValue{
                                Enum:     schema.AllStep,
                                Default:  schema.StepMinute,
                                Selected: schema.StepMinute,
                        },
                },
        },
-       Before: commands.BeforeChain([]cli.BeforeFunc{
-               commands.SetUpDuration,
+       Before: interceptor.BeforeChain([]cli.BeforeFunc{
+               interceptor.DurationInterceptor,
        }),
        Action: func(ctx *cli.Context) error {
                end := ctx.String("end")
@@ -59,13 +61,13 @@ var ListCommand = cli.Command{
                services := client.Services(schema.Duration{
                        Start: start,
                        End:   end,
-                       Step:  step.(*commands.StepEnumValue).Selected,
+                       Step:  step.(*model.StepEnumValue).Selected,
                })
 
-               if bytes, e := json.Marshal(services); e != nil {
-                       return e
-               } else {
+               if bytes, e := json.Marshal(services); e == nil {
                        fmt.Printf("%v\n", string(bytes))
+               } else {
+                       return e
                }
 
                return nil
diff --git a/config/config.go b/config/config.go
index bc33da2..0565fee 100644
--- a/config/config.go
+++ b/config/config.go
@@ -20,6 +20,6 @@ package config
 
 var Config struct {
        Global struct {
-               BaseUrl string `yaml:"base-url"`
+               BaseURL string `yaml:"base-url"`
        }
 }
diff --git a/go.mod b/go.mod
index 062d750..ad56fce 100644
--- a/go.mod
+++ b/go.mod
@@ -7,4 +7,5 @@ require (
        github.com/pkg/errors v0.8.1 // indirect
        github.com/sirupsen/logrus v1.4.2
        github.com/urfave/cli v1.22.1
+       gopkg.in/yaml.v2 v2.2.2
 )
diff --git a/graphql/client/client.go b/graphql/client/client.go
index 0b050e3..d74f5c3 100644
--- a/graphql/client/client.go
+++ b/graphql/client/client.go
@@ -28,7 +28,7 @@ import (
 
 func Services(duration schema.Duration) []schema.Service {
        ctx := context.Background()
-       client := graphql.NewClient(config.Config.Global.BaseUrl)
+       client := graphql.NewClient(config.Config.Global.BaseURL)
        client.Log = func(msg string) {
                logger.Log.Debugln(msg)
        }
diff --git a/logger/log.go b/logger/log.go
index 4a5d482..ce1cda7 100644
--- a/logger/log.go
+++ b/logger/log.go
@@ -32,7 +32,7 @@ func init() {
        }
        Log.SetOutput(os.Stdout)
        Log.SetFormatter(&logrus.TextFormatter{
-               FullTimestamp:   true,
-               TimestampFormat: "2006-01-02 15:04:05",
+               DisableTimestamp:       true,
+               DisableLevelTruncation: true,
        })
 }
diff --git a/swctl/main.go b/swctl/main.go
index 5619a28..2f3f967 100644
--- a/swctl/main.go
+++ b/swctl/main.go
@@ -22,13 +22,13 @@ import (
        "encoding/json"
        "github.com/apache/skywalking-cli/commands/service"
        "github.com/apache/skywalking-cli/config"
+       "github.com/apache/skywalking-cli/logger"
+       "github.com/apache/skywalking-cli/util"
        "github.com/sirupsen/logrus"
        "github.com/urfave/cli"
        "gopkg.in/yaml.v2"
        "io/ioutil"
        "os"
-
-       "github.com/apache/skywalking-cli/logger"
 )
 
 var log *logrus.Logger
@@ -43,8 +43,8 @@ func main() {
        app.Flags = []cli.Flag{
                cli.StringFlag{
                        Name:  "config",
-                       Value: "./settings.yml",
-                       Usage: "load configuration `FILE`, default to 
./settings.yml",
+                       Value: "~/.skywalking.yml",
+                       Usage: "load configuration `FILE`",
                },
                cli.BoolFlag{
                        Name:     "debug",
@@ -56,20 +56,20 @@ func main() {
                service.Command,
        }
 
-       app.Before = BeforeCommand
+       app.Before = beforeCommand
 
        if err := app.Run(os.Args); err != nil {
                log.Fatalln(err)
        }
 }
 
-func BeforeCommand(c *cli.Context) error {
+func beforeCommand(c *cli.Context) error {
        if c.Bool("debug") {
                log.SetLevel(logrus.DebugLevel)
                log.Debugln("Debug mode is enabled")
        }
 
-       configFile := c.String("config")
+       configFile := util.ExpandFilePath(c.String("config"))
        log.Debugln("Using configuration file:", configFile)
 
        if bytes, err := ioutil.ReadFile(configFile); err != nil {
diff --git a/commands/model.go b/util/io.go
similarity index 59%
rename from commands/model.go
rename to util/io.go
index 9da2fba..5156856 100644
--- a/commands/model.go
+++ b/util/io.go
@@ -16,34 +16,29 @@
  *
  */
 
-package commands
+package util
 
 import (
-       "fmt"
-       "github.com/apache/skywalking-cli/graphql/schema"
+       "github.com/apache/skywalking-cli/logger"
+       "os/user"
        "strings"
 )
 
-type StepEnumValue struct {
-       Enum     []schema.Step
-       Default  schema.Step
-       Selected schema.Step
-}
-
-func (s *StepEnumValue) Set(value string) error {
-       for _, enum := range s.Enum {
-               if enum.String() == value {
-                       s.Selected = enum
-                       return nil
-               }
+// UserHomeDir returns the current user's home directory absolute path,
+// which is usually represented as `~` in most shells
+func UserHomeDir() string {
+       if currentUser, err := user.Current(); err != nil {
+               logger.Log.Warnln("Cannot obtain user home directory")
+       } else {
+               return currentUser.HomeDir
        }
-       steps := make([]string, len(schema.AllStep))
-       for i, step := range schema.AllStep {
-               steps[i] = step.String()
-       }
-       return fmt.Errorf("allowed steps are %s", strings.Join(steps, ", "))
+       return ""
 }
 
-func (s StepEnumValue) String() string {
-       return s.Selected.String()
+// ExpandFilePath expands the leading `~` to absolute path
+func ExpandFilePath(path string) string {
+       if strings.HasPrefix(path, "~") {
+               return strings.Replace(path, "~", UserHomeDir(), 1)
+       }
+       return path
 }

Reply via email to