github.com/maxgio92/test-infra@v0.1.0/kubetest/util/util.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package util
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"go/build"
    23  	"log"
    24  	"net/url"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"strings"
    29  )
    30  
    31  // K8s returns $GOPATH/src/k8s.io/...
    32  func K8s(projectName string, parts ...string) string {
    33  	return getProjectDir("k8s.io", projectName, parts...)
    34  }
    35  
    36  // K8sSigs returns $GOPATH/src/sigs.k8s.io/...
    37  func K8sSigs(projectName string, parts ...string) string {
    38  	return getProjectDir("sigs.k8s.io", projectName, parts...)
    39  }
    40  
    41  // GetProjectDir returns $GOPATH/src/<project-namespace>/...
    42  func getProjectDir(namespace, projectName string, parts ...string) string {
    43  	gopathList := filepath.SplitList(build.Default.GOPATH)
    44  	var found bool
    45  	var projectDir string
    46  	for _, gopath := range gopathList {
    47  		projectDir = filepath.Join(gopath, "src", namespace, projectName)
    48  		if _, err := os.Stat(projectDir); !os.IsNotExist(err) {
    49  			found = true
    50  			break
    51  		}
    52  	}
    53  	if !found && len(gopathList) > 0 {
    54  		// Default to the first item in GOPATH list.
    55  		projectDir = filepath.Join(gopathList[0], "src", "k8s.io", projectName)
    56  		log.Printf(
    57  			"Warning: Couldn't find directory src/%s/%s under any of GOPATH %s, defaulting to %s",
    58  			namespace, projectName, build.Default.GOPATH, projectDir)
    59  	}
    60  	p := []string{projectDir}
    61  	p = append(p, parts...)
    62  	return filepath.Join(p...)
    63  }
    64  
    65  // AppendError does append(errs, err) if err != nil
    66  func AppendError(errs []error, err error) []error {
    67  	if err != nil {
    68  		return append(errs, err)
    69  	}
    70  	return errs
    71  }
    72  
    73  // Home returns $HOME/part/part/part
    74  func Home(parts ...string) string {
    75  	p := []string{os.Getenv("HOME")}
    76  	p = append(p, parts...)
    77  	return filepath.Join(p...)
    78  }
    79  
    80  // InsertPath does export PATH=path:$PATH
    81  func InsertPath(path string) error {
    82  	return os.Setenv("PATH", fmt.Sprintf("%v:%v", path, os.Getenv("PATH")))
    83  }
    84  
    85  // OptionalAbsPath returns an absolute path if the provided path wasn't empty, and otherwise
    86  // returns an empty string.
    87  func OptionalAbsPath(path string) (string, error) {
    88  	if path == "" {
    89  		return "", nil
    90  	}
    91  
    92  	return filepath.Abs(path)
    93  }
    94  
    95  // JoinURL converts input (gs://foo, "bar") to gs://foo/bar
    96  func JoinURL(urlPath, path string) (string, error) {
    97  	u, err := url.Parse(urlPath)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  	u.Path = filepath.Join(u.Path, path)
   102  	return u.String(), nil
   103  }
   104  
   105  // Pushd will Chdir() to dir and return a function to cd back to Getwd()
   106  func Pushd(dir string) (func() error, error) {
   107  	old, err := os.Getwd()
   108  	if err != nil {
   109  		return nil, fmt.Errorf("failed to os.Getwd(): %w", err)
   110  	}
   111  	if err = os.Chdir(dir); err != nil {
   112  		return nil, err
   113  	}
   114  	return func() error {
   115  		return os.Chdir(old)
   116  	}, nil
   117  }
   118  
   119  // PushEnv pushes env=value and return a function that resets env
   120  func PushEnv(env, value string) (func() error, error) {
   121  	prev, present := os.LookupEnv(env)
   122  	if err := os.Setenv(env, value); err != nil {
   123  		return nil, fmt.Errorf("could not set %s: %w", env, err)
   124  	}
   125  	return func() error {
   126  		if present {
   127  			return os.Setenv(env, prev)
   128  		}
   129  		return os.Unsetenv(env)
   130  	}, nil
   131  }
   132  
   133  // MigratedOption is an option that was an ENV that is now a --flag
   134  type MigratedOption struct {
   135  	Env      string  // env associated with --flag
   136  	Option   *string // Value of --flag
   137  	Name     string  // --flag name
   138  	SkipPush bool    // Push option to env if false
   139  }
   140  
   141  // MigrateOptions reads value from ENV if --flag unset, optionally pushing to ENV
   142  func MigrateOptions(m []MigratedOption) error {
   143  	for _, s := range m {
   144  		if *s.Option == "" {
   145  			// Jobs may not be using --foo instead of FOO just yet, so ease the transition
   146  			// TODO(fejta): require --foo instead of FOO
   147  			v := os.Getenv(s.Env) // expected Getenv
   148  			if v != "" {
   149  				// Tell people to use --foo=blah instead of FOO=blah
   150  				log.Printf("Please use kubetest %s=%s (instead of deprecated %s=%s)", s.Name, v, s.Env, v)
   151  				*s.Option = v
   152  			}
   153  		}
   154  		if s.SkipPush {
   155  			continue
   156  		}
   157  		// Script called by kubetest may expect these values to be set, so set them
   158  		// TODO(fejta): refactor the scripts below kubetest to use explicit config
   159  		if *s.Option == "" {
   160  			continue
   161  		}
   162  		if err := os.Setenv(s.Env, *s.Option); err != nil {
   163  			return fmt.Errorf("could not set %s=%s: %w", s.Env, *s.Option, err)
   164  		}
   165  	}
   166  	return nil
   167  }
   168  
   169  // AppendField will append prefix to the flag value.
   170  //
   171  // For example, AppendField(fields, "--foo", "bar") if fields is empty or does
   172  // not contain a "--foo" it will simply append a "--foo=bar" value.
   173  // Otherwise if fields contains "--foo=current" it will replace this value with
   174  // "--foo=current-bar
   175  func AppendField(fields []string, flag, prefix string) []string {
   176  	fields, cur, _ := ExtractField(fields, flag)
   177  	if len(cur) == 0 {
   178  		cur = prefix
   179  	} else {
   180  		cur += "-" + prefix
   181  	}
   182  	return append(fields, flag+"="+cur)
   183  }
   184  
   185  // SetFieldDefault sets the value of flag to val if flag is not present in fields.
   186  //
   187  // For example, SetFieldDefault(fields, "--foo", "bar") will append "--foo=bar" if
   188  // fields is empty or does not include a "--foo" flag.
   189  // It returns fields unchanged if "--foo" is present.
   190  func SetFieldDefault(fields []string, flag, val string) []string {
   191  	fields, cur, present := ExtractField(fields, flag)
   192  	if !present {
   193  		cur = val
   194  	}
   195  	return append(fields, flag+"="+cur)
   196  }
   197  
   198  // ExtractField input ("--a=this --b=that --c=other", "--b") returns [--a=this, --c=other"], "that", true
   199  //
   200  // In other words, it will remove "--b" from fields and return the previous value of "--b" if it was set.
   201  func ExtractField(fields []string, target string) ([]string, string, bool) {
   202  	f := []string{}
   203  	prefix := target + "="
   204  	consumeNext := false
   205  	done := false
   206  	r := ""
   207  	for _, field := range fields {
   208  		switch {
   209  		case done:
   210  			f = append(f, field)
   211  		case consumeNext:
   212  			r = field
   213  			done = true
   214  		case field == target:
   215  			consumeNext = true
   216  		case strings.HasPrefix(field, prefix):
   217  			r = strings.SplitN(field, "=", 2)[1]
   218  			done = true
   219  		default:
   220  			f = append(f, field)
   221  		}
   222  	}
   223  	return f, r, done
   224  }
   225  
   226  // ExecError returns a string format of err including stderr if the
   227  // err is an ExitError, useful for errors from e.g. exec.Cmd.Output().
   228  func ExecError(err error) string {
   229  	if ee, ok := err.(*exec.ExitError); ok {
   230  		return fmt.Sprintf("%v (output: %q)", err, string(ee.Stderr))
   231  	}
   232  	return err.Error()
   233  }
   234  
   235  // EnsureExecutable sets the executable file mode bits, for all users, to ensure that we can execute a file
   236  func EnsureExecutable(p string) error {
   237  	s, err := os.Stat(p)
   238  	if err != nil {
   239  		return fmt.Errorf("error doing stat on %q: %w", p, err)
   240  	}
   241  	if err := os.Chmod(p, s.Mode()|0111); err != nil {
   242  		return fmt.Errorf("error doing chmod on %q: %w", p, err)
   243  	}
   244  	return nil
   245  }
   246  
   247  // JSONForDebug returns a json representation of the value, or a string representation of an error
   248  // It is useful for an easy implementation of fmt.Stringer
   249  func JSONForDebug(o interface{}) string {
   250  	if o == nil {
   251  		return "nil"
   252  	}
   253  	v, err := json.Marshal(o)
   254  	if err != nil {
   255  		return fmt.Sprintf("error[%v]", err)
   256  	}
   257  	return string(v)
   258  }
   259  
   260  // FlushMem will try to reduce the memory usage of the container it is running in
   261  // run this after a build
   262  func FlushMem() {
   263  	log.Println("Flushing memory.")
   264  	// it's ok if these fail
   265  	// flush memory buffers
   266  	err := exec.Command("sync").Run()
   267  	if err != nil {
   268  		log.Printf("flushMem error (sync): %v", err)
   269  	}
   270  	// clear page cache
   271  	err = exec.Command("bash", "-c", "echo 1 > /proc/sys/vm/drop_caches").Run()
   272  	if err != nil {
   273  		log.Printf("flushMem error (page cache): %v", err)
   274  	}
   275  }