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 }