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
}