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