You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2019/11/10 00:05:43 UTC

[skywalking-cli] 18/29: Set up CI flow

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 <ke...@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
 }