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