You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2021/08/12 08:29:32 UTC

[skywalking-infra-e2e] 01/01: Propagate environment variables from user command to parent process

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

kezhenxu94 pushed a commit to branch enhancement/env-var
in repository https://gitbox.apache.org/repos/asf/skywalking-infra-e2e.git

commit 33088ee7c4f8c15ff0c3a735eff87b204a32eefb
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Thu Aug 12 16:28:59 2021 +0800

    Propagate environment variables from user command to parent process
    
    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.
---
 .golangci.yml                       |  5 +--
 Makefile                            |  2 +-
 commands/root.go                    | 30 ++++++++++++++-
 commands/verify/verify.go           | 12 +++---
 go.mod                              |  2 +-
 internal/components/cleanup/kind.go |  6 +--
 internal/components/setup/common.go |  9 +++--
 internal/components/trigger/http.go |  4 +-
 internal/logger/log.go              |  2 +-
 internal/util/config.go             |  5 ++-
 internal/util/hook.sh               | 21 +++++++++++
 internal/util/{config.go => io.go}  | 34 ++++++++---------
 internal/util/utils.go              | 74 ++++++++++++++++++++++++++++++++++---
 13 files changed, 156 insertions(+), 50 deletions(-)

diff --git a/.golangci.yml b/.golangci.yml
index 617deaf..f3d33b5 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -24,7 +24,7 @@ run:
 linters-settings:
   govet:
     check-shadowing: true
-  golint:
+  revive:
     min-confidence: 0
   gocyclo:
     min-complexity: 15
@@ -108,12 +108,11 @@ linters:
     - 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 @@ all: clean lint test build
 
 .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 @@ import (
 	"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 @@ var Root = &cobra.Command{
 	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 @@ func Execute() error {
 	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 @@ func verifySingleCase(expectedFile, actualFile, query string) error {
 		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 @@ func verifySingleCase(expectedFile, actualFile, query string) error {
 		}
 	} 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 @@ func cleanKindCluster(kindConfigFilePath string) error {
 	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 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"strings"
 	"time"
 
 	"k8s.io/client-go/dynamic"
@@ -170,13 +171,13 @@ func executeCommandsAndWait(commands string, waits []config.Wait, waitSet *util.
 	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 @@ func (h *httpAction) executeOnce(client *http.Client, req *http.Request) error {
 		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 @@ func init() {
 	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 @@ import (
 	"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/config.go b/internal/util/io.go
similarity index 62%
copy from internal/util/config.go
copy to internal/util/io.go
index 15e38e1..733f552 100644
--- a/internal/util/config.go
+++ b/internal/util/io.go
@@ -19,29 +19,27 @@
 package util
 
 import (
-	"path"
-	"path/filepath"
+	"os/user"
+	"strings"
 
 	"github.com/apache/skywalking-infra-e2e/internal/logger"
 )
 
-var CfgFile string
-
-// ResolveAbs resolves the relative path (relative to CfgFile) to an absolute file path.
-func ResolveAbs(p string) string {
-	if p == "" {
-		return p
-	}
-
-	if path.IsAbs(p) {
-		return p
+// 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 ""
+}
 
-	abs, err := filepath.Abs(CfgFile)
-	if err != nil {
-		logger.Log.Warnf("failed to resolve the absolute file path of %v\n", CfgFile)
-		return p
+// ExpandFilePath expands the leading `~` to absolute path
+func ExpandFilePath(path string) string {
+	if strings.HasPrefix(path, "~") {
+		return strings.Replace(path, "~", UserHomeDir(), 1)
 	}
-
-	return filepath.Join(filepath.Dir(abs), p)
+	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 @@ package util
 
 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 @@ func ReadFileContent(filename string) (string, error) {
 }
 
 // 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 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)
+		}
 	}
-	return outinfo.String(), nil
 }