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