Propagate environment variables from user command to parent process (#31)
And some other enhancements:
- Replace linter `golint` to `revive` as the former is deprecated, and fix code styles found by `revive`.
- Polish the logs to make it not too lengthy.
- Add log level configuration.
- Bump up Go version to 1.16.
- Propagate environment variables from user command (sub-process) to parent process to make it available to other sub-processes.
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index d411fa9..ce03dd8 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -29,10 +29,10 @@
name: Build
runs-on: ubuntu-latest
steps:
- - name: Set up Go 1.14
+ - name: Set up Go 1.16
uses: actions/setup-go@v2
with:
- go-version: 1.14
+ go-version: 1.16
id: go
- name: Check out code into the Go module directory
diff --git a/.golangci.yml b/.golangci.yml
index 617deaf..f3d33b5 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -24,7 +24,7 @@
linters-settings:
govet:
check-shadowing: true
- golint:
+ revive:
min-confidence: 0
gocyclo:
min-complexity: 15
@@ -108,12 +108,11 @@
- gocyclo
- gofmt
- goimports
- - golint
+ - revive
- gosec
- gosimple
- govet
- ineffassign
- - interfacer
- lll
- misspell
- nakedret
diff --git a/Makefile b/Makefile
index 1eee4be..e0f675b 100644
--- a/Makefile
+++ b/Makefile
@@ -40,7 +40,7 @@
.PHONY: lint
lint:
- $(GO_LINT) version || curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin
+ $(GO_LINT) version || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin
$(GO_LINT) run -v --timeout 5m ./...
.PHONY: fix-lint
diff --git a/commands/root.go b/commands/root.go
index 8b5fe6c..ff625db 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -18,9 +18,12 @@
package commands
import (
+ "os"
+
+ "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
- "github.com/apache/skywalking-infra-e2e/internal/util"
+ "github.com/apache/skywalking-infra-e2e/internal/logger"
"github.com/apache/skywalking-infra-e2e/commands/cleanup"
"github.com/apache/skywalking-infra-e2e/commands/run"
@@ -29,6 +32,11 @@
"github.com/apache/skywalking-infra-e2e/commands/verify"
"github.com/apache/skywalking-infra-e2e/internal/config"
"github.com/apache/skywalking-infra-e2e/internal/constant"
+ "github.com/apache/skywalking-infra-e2e/internal/util"
+)
+
+var (
+ verbosity string
)
// Root represents the base command when called without any subcommands
@@ -38,8 +46,24 @@
Version: version,
SilenceErrors: true,
SilenceUsage: true,
- PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
config.ReadGlobalConfigFile()
+
+ level, err := logrus.ParseLevel(verbosity)
+ if err != nil {
+ return err
+ }
+ logger.Log.SetLevel(level)
+
+ util.WorkDir = util.ExpandFilePath(util.WorkDir)
+ if _, err := os.Stat(util.WorkDir); os.IsNotExist(err) {
+ if err := os.MkdirAll(util.WorkDir, os.ModePerm); err != nil {
+ logger.Log.Warnf("failed to create working directory %v", util.WorkDir)
+ return err
+ }
+ }
+
+ return nil
},
}
@@ -52,6 +76,8 @@
Root.AddCommand(verify.Verify)
Root.AddCommand(cleanup.Cleanup)
+ Root.PersistentFlags().StringVarP(&verbosity, "verbosity", "v", logrus.InfoLevel.String(), "log level (debug, info, warn, error, fatal, panic")
+ Root.PersistentFlags().StringVarP(&util.WorkDir, "work-dir", "w", "~/.skywalking-infra-e2e", "the working directory for skywalking-infra-e2e")
Root.PersistentFlags().StringVarP(&util.CfgFile, "config", "c", constant.E2EDefaultFile, "the config file")
return Root.Execute()
diff --git a/commands/verify/verify.go b/commands/verify/verify.go
index e979b41..776510b 100644
--- a/commands/verify/verify.go
+++ b/commands/verify/verify.go
@@ -59,7 +59,7 @@
return fmt.Errorf("failed to read the expected data file: %v", err)
}
- var actualData, sourceName string
+ var actualData, sourceName, stderr string
if actualFile != "" {
sourceName = actualFile
actualData, err = util.ReadFileContent(actualFile)
@@ -68,19 +68,19 @@
}
} else if query != "" {
sourceName = query
- actualData, err = util.ExecuteCommand(query)
+ actualData, stderr, err = util.ExecuteCommand(query)
if err != nil {
- return fmt.Errorf("failed to execute the query: %s, output: %s, error: %v", query, actualData, err)
+ return fmt.Errorf("failed to execute the query: %s, output: %s, error: %v", query, actualData, stderr)
}
}
if err = verifier.Verify(actualData, expectedData); err != nil {
if me, ok := err.(*verifier.MismatchError); ok {
- return fmt.Errorf("failed to verify the output: %s, error: %v", sourceName, me.Error())
+ return fmt.Errorf("failed to verify the output: %s, error:\n%v", sourceName, me.Error())
}
- return fmt.Errorf("failed to verify the output: %s, error: %v", sourceName, err)
+ return fmt.Errorf("failed to verify the output: %s, error:\n%v", sourceName, err)
}
- logger.Log.Infof("verified the output: %s\n", sourceName)
+ logger.Log.Infof("verified the output: %s", sourceName)
return nil
}
diff --git a/go.mod b/go.mod
index ce51fdd..004f535 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/apache/skywalking-infra-e2e
-go 1.13
+go 1.16
require (
github.com/docker/docker v20.10.7+incompatible
diff --git a/internal/components/cleanup/kind.go b/internal/components/cleanup/kind.go
index 1e19072..3e5f8f1 100644
--- a/internal/components/cleanup/kind.go
+++ b/internal/components/cleanup/kind.go
@@ -86,9 +86,5 @@
args := []string{"delete", "cluster", "--name", clusterName}
logger.Log.Debugf("cluster delete commands: %s %s", constant.KindCommand, strings.Join(args, " "))
- if err := kind.Run(kindcmd.NewLogger(), kindcmd.StandardIOStreams(), args); err != nil {
- return err
- }
-
- return nil
+ return kind.Run(kindcmd.NewLogger(), kindcmd.StandardIOStreams(), args)
}
diff --git a/internal/components/setup/common.go b/internal/components/setup/common.go
index 1e93b9d..e220fa8 100644
--- a/internal/components/setup/common.go
+++ b/internal/components/setup/common.go
@@ -22,6 +22,7 @@
"fmt"
"io/ioutil"
"os"
+ "strings"
"time"
"k8s.io/client-go/dynamic"
@@ -170,13 +171,13 @@
defer waitSet.WaitGroup.Done()
// executes commands
- logger.Log.Infof("executing commands [%s]", commands)
- result, err := util.ExecuteCommand(commands)
+ logger.Log.Infof("executing commands [%s]", strings.ReplaceAll(commands, "\n", "\\n"))
+ result, stderr, err := util.ExecuteCommand(commands)
if err != nil {
- err = fmt.Errorf("commands: [%s] runs error: %s", commands, err)
+ err = fmt.Errorf("commands: [%s] runs error: %s", strings.ReplaceAll(commands, "\n", "\\n"), stderr)
waitSet.ErrChan <- err
}
- logger.Log.Infof("executed commands [%s], result: %s", commands, result)
+ logger.Log.Infof("executed commands [%s], result: %s", strings.ReplaceAll(commands, "\n", "\\n"), result)
// waits for conditions meet
for idx := range waits {
diff --git a/internal/components/trigger/http.go b/internal/components/trigger/http.go
index ce4cbe2..2af36a3 100644
--- a/internal/components/trigger/http.go
+++ b/internal/components/trigger/http.go
@@ -113,9 +113,9 @@
logger.Log.Errorf("do request error %v", err)
return err
}
- response.Body.Close()
+ _ = response.Body.Close()
- logger.Log.Infof("do request %v response http code %v", h.url, response.StatusCode)
+ logger.Log.Debugf("do request %v response http code %v", h.url, response.StatusCode)
if response.StatusCode == http.StatusOK {
logger.Log.Debugf("do http action %+v success.", *h)
return nil
diff --git a/internal/logger/log.go b/internal/logger/log.go
index 7fc102e..1c641e8 100644
--- a/internal/logger/log.go
+++ b/internal/logger/log.go
@@ -29,7 +29,7 @@
if Log == nil {
Log = logrus.New()
}
- Log.Level = logrus.DebugLevel
+ Log.Level = logrus.InfoLevel
Log.SetOutput(os.Stdout)
Log.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
diff --git a/internal/util/config.go b/internal/util/config.go
index 15e38e1..c34eecb 100644
--- a/internal/util/config.go
+++ b/internal/util/config.go
@@ -25,7 +25,10 @@
"github.com/apache/skywalking-infra-e2e/internal/logger"
)
-var CfgFile string
+var (
+ CfgFile string
+ WorkDir string
+)
// ResolveAbs resolves the relative path (relative to CfgFile) to an absolute file path.
func ResolveAbs(p string) string {
diff --git a/internal/util/hook.sh b/internal/util/hook.sh
new file mode 100644
index 0000000..0a09eda
--- /dev/null
+++ b/internal/util/hook.sh
@@ -0,0 +1,21 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+function finish {
+ printenv > {{ .EnvFile }}
+}
+trap finish EXIT
diff --git a/internal/util/io.go b/internal/util/io.go
new file mode 100644
index 0000000..733f552
--- /dev/null
+++ b/internal/util/io.go
@@ -0,0 +1,45 @@
+// Licensed to 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. Apache Software Foundation (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 util
+
+import (
+ "os/user"
+ "strings"
+
+ "github.com/apache/skywalking-infra-e2e/internal/logger"
+)
+
+// 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
+ }
+ return ""
+}
+
+// ExpandFilePath expands the leading `~` to absolute path
+func ExpandFilePath(path string) string {
+ if strings.HasPrefix(path, "~") {
+ return strings.Replace(path, "~", UserHomeDir(), 1)
+ }
+ return path
+}
diff --git a/internal/util/utils.go b/internal/util/utils.go
index 1e84ea1..5dc7a55 100644
--- a/internal/util/utils.go
+++ b/internal/util/utils.go
@@ -20,10 +20,16 @@
import (
"bytes"
+ _ "embed"
"errors"
"io/ioutil"
"os"
"os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+
+ "github.com/apache/skywalking-infra-e2e/internal/logger"
)
// PathExist checks if a file/directory is exist.
@@ -48,16 +54,72 @@
}
// ExecuteCommand executes the given command and returns the result.
-func ExecuteCommand(cmd string) (string, error) {
+func ExecuteCommand(cmd string) (stdout, stderr string, err error) {
+ hookScript, err := hookScript()
+ if err != nil {
+ return "", "", err
+ }
+
+ // Propagate the env vars from sub-process back to parent process
+ defer exportEnvVars()
+
+ cmd = hookScript + "\n" + cmd
+
command := exec.Command("bash", "-ec", cmd)
- outinfo := bytes.Buffer{}
- command.Stdout = &outinfo
+ sout, serr := bytes.Buffer{}, bytes.Buffer{}
+ command.Stdout, command.Stderr = &sout, &serr
if err := command.Start(); err != nil {
- return outinfo.String(), err
+ return sout.String(), serr.String(), err
}
if err := command.Wait(); err != nil {
- return outinfo.String(), err
+ return sout.String(), serr.String(), err
}
- return outinfo.String(), nil
+ return sout.String(), serr.String(), nil
+}
+
+//go:embed hook.sh
+var hookScriptTemplate string
+
+type HookScriptTemplate struct {
+ EnvFile string
+}
+
+func hookScript() (string, error) {
+ hookScript := bytes.Buffer{}
+
+ parse, err := template.New("hookScriptTemplate").Parse(hookScriptTemplate)
+ if err != nil {
+ return "", err
+ }
+
+ envFile := filepath.Join(WorkDir, ".env")
+ scriptData := HookScriptTemplate{EnvFile: envFile}
+ if err := parse.Execute(&hookScript, scriptData); err != nil {
+ return "", err
+ }
+ return hookScript.String(), nil
+}
+
+func exportEnvVars() {
+ envFile := filepath.Join(WorkDir, ".env")
+ b, err := ioutil.ReadFile(envFile)
+ if err != nil {
+ logger.Log.Warnf("failed to export environment variables, %v", err)
+ return
+ }
+ s := string(b)
+
+ lines := strings.Split(s, "\n")
+ for _, line := range lines {
+ kv := strings.SplitN(line, "=", 2)
+ if len(kv) != 2 {
+ continue
+ }
+ key, val := kv[0], kv[1]
+ // should only export env vars that are not already exist in parent process (Go process)
+ if err := os.Setenv(key, val); err != nil {
+ logger.Log.Warnf("failed to export environment variable %v=%v, %v", key, val, err)
+ }
+ }
}