/
util.go
341 lines (304 loc) · 11.3 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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package util
import (
"fmt"
"net/url"
"strings"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ktypedclient "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/credentialprovider"
credentialprovidersecrets "k8s.io/kubernetes/pkg/credentialprovider/secrets"
buildv1 "github.com/openshift/api/build/v1"
buildlister "github.com/openshift/client-go/build/listers/build/v1"
"github.com/openshift/origin/pkg/api/apihelpers"
"github.com/openshift/origin/pkg/build/buildapihelpers"
)
const (
// NoBuildLogsMessage reports that no build logs are available
NoBuildLogsMessage = "No logs are available."
// BuildWorkDirMount is the working directory within the build pod, mounted as a volume.
BuildWorkDirMount = "/tmp/build"
// BuilderServiceAccountName is the name of the account used to run build pods by default.
BuilderServiceAccountName = "builder"
// buildPodSuffix is the suffix used to append to a build pod name given a build name
buildPodSuffix = "build"
// BuildBlobsMetaCache is the directory used to store a cache for the blobs metadata to be
// reused across builds.
BuildBlobsMetaCache = "/var/lib/containers/cache"
// BuildBlobsContentCache is the directory used to store a cache for the blobs content to be
// reused within a build pod.
BuildBlobsContentCache = "/var/cache/blobs"
)
// GeneratorFatalError represents a fatal error while generating a build.
// An operation that fails because of a fatal error should not be retried.
type GeneratorFatalError struct {
// Reason the fatal error occurred
Reason string
}
// Error returns the error string for this fatal error
func (e *GeneratorFatalError) Error() string {
return fmt.Sprintf("fatal error generating Build from BuildConfig: %s", e.Reason)
}
// IsFatal returns true if err is a fatal error
func IsFatalGeneratorError(err error) bool {
_, isFatal := err.(*GeneratorFatalError)
return isFatal
}
// GetBuildPodName returns name of the build pod.
func GetBuildPodName(build *buildv1.Build) string {
return apihelpers.GetPodName(build.Name, buildPodSuffix)
}
// IsBuildComplete returns whether the provided build is complete or not
func IsBuildComplete(build *buildv1.Build) bool {
return IsTerminalPhase(build.Status.Phase)
}
// IsTerminalPhase returns true if the provided phase is terminal
func IsTerminalPhase(phase buildv1.BuildPhase) bool {
switch phase {
case buildv1.BuildPhaseNew,
buildv1.BuildPhasePending,
buildv1.BuildPhaseRunning:
return false
}
return true
}
// BuildNameForConfigVersion returns the name of the version-th build
// for the config that has the provided name.
func BuildNameForConfigVersion(name string, version int) string {
return fmt.Sprintf("%s-%d", name, version)
}
// BuildConfigSelector returns a label Selector which can be used to find all
// builds for a BuildConfig.
func BuildConfigSelector(name string) labels.Selector {
return labels.Set{BuildConfigLabel: buildapihelpers.LabelValue(name)}.AsSelector()
}
type buildFilter func(*buildv1.Build) bool
// BuildConfigBuilds return a list of builds for the given build config.
// Optionally you can specify a filter function to select only builds that
// matches your criteria.
func BuildConfigBuilds(c buildlister.BuildLister, namespace, name string, filterFunc buildFilter) ([]*buildv1.Build, error) {
result, err := c.Builds(namespace).List(BuildConfigSelector(name))
if err != nil {
return nil, err
}
if filterFunc == nil {
return result, nil
}
var filteredList []*buildv1.Build
for _, b := range result {
if filterFunc(b) {
filteredList = append(filteredList, b)
}
}
return filteredList, nil
}
// ConfigNameForBuild returns the name of the build config from a
// build name.
func ConfigNameForBuild(build *buildv1.Build) string {
if build == nil {
return ""
}
if build.Annotations != nil {
if _, exists := build.Annotations[BuildConfigAnnotation]; exists {
return build.Annotations[BuildConfigAnnotation]
}
}
if _, exists := build.Labels[BuildConfigLabel]; exists {
return build.Labels[BuildConfigLabel]
}
return build.Labels[BuildConfigLabelDeprecated]
}
// MergeTrustedEnvWithoutDuplicates merges two environment lists without having
// duplicate items in the output list. The source list will be filtered
// such that only whitelisted environment variables are merged into the
// output list. If sourcePrecedence is true, keys in the source list
// will override keys in the output list.
func MergeTrustedEnvWithoutDuplicates(source []corev1.EnvVar, output *[]corev1.EnvVar, sourcePrecedence bool) {
MergeEnvWithoutDuplicates(source, output, sourcePrecedence, WhitelistEnvVarNames)
}
// MergeEnvWithoutDuplicates merges two environment lists without having
// duplicate items in the output list. If sourcePrecedence is true, keys in the source list
// will override keys in the output list.
func MergeEnvWithoutDuplicates(source []corev1.EnvVar, output *[]corev1.EnvVar, sourcePrecedence bool, whitelist []string) {
// filter out all environment variables except trusted/well known
// values, because we do not want random environment variables being
// fed into the privileged STI container via the BuildConfig definition.
filteredSourceMap := make(map[string]corev1.EnvVar)
for _, env := range source {
allowed := false
if len(whitelist) == 0 {
allowed = true
} else {
for _, acceptable := range WhitelistEnvVarNames {
if env.Name == acceptable {
allowed = true
break
}
}
}
if allowed {
filteredSourceMap[env.Name] = env
}
}
result := *output
for i, env := range result {
// If the value exists in output, optionally override it and remove it
// from the source list
if v, found := filteredSourceMap[env.Name]; found {
if sourcePrecedence {
result[i].Value = v.Value
}
delete(filteredSourceMap, env.Name)
}
}
// iterate the original list so we retain the order of the inputs
// when we append them to the output.
for _, v := range source {
if v, ok := filteredSourceMap[v.Name]; ok {
result = append(result, v)
}
}
*output = result
}
// GetBuildEnv gets the build strategy environment
func GetBuildEnv(build *buildv1.Build) []corev1.EnvVar {
switch {
case build.Spec.Strategy.SourceStrategy != nil:
return build.Spec.Strategy.SourceStrategy.Env
case build.Spec.Strategy.DockerStrategy != nil:
return build.Spec.Strategy.DockerStrategy.Env
case build.Spec.Strategy.CustomStrategy != nil:
return build.Spec.Strategy.CustomStrategy.Env
case build.Spec.Strategy.JenkinsPipelineStrategy != nil:
return build.Spec.Strategy.JenkinsPipelineStrategy.Env
default:
return nil
}
}
// SetBuildEnv replaces the current build environment
func SetBuildEnv(build *buildv1.Build, env []corev1.EnvVar) {
var oldEnv *[]corev1.EnvVar
switch {
case build.Spec.Strategy.SourceStrategy != nil:
oldEnv = &build.Spec.Strategy.SourceStrategy.Env
case build.Spec.Strategy.DockerStrategy != nil:
oldEnv = &build.Spec.Strategy.DockerStrategy.Env
case build.Spec.Strategy.CustomStrategy != nil:
oldEnv = &build.Spec.Strategy.CustomStrategy.Env
case build.Spec.Strategy.JenkinsPipelineStrategy != nil:
oldEnv = &build.Spec.Strategy.JenkinsPipelineStrategy.Env
default:
return
}
*oldEnv = env
}
// UpdateBuildEnv updates the strategy environment
// This will replace the existing variable definitions with provided env
func UpdateBuildEnv(build *buildv1.Build, env []corev1.EnvVar) {
buildEnv := GetBuildEnv(build)
newEnv := []corev1.EnvVar{}
for _, e := range buildEnv {
exists := false
for _, n := range env {
if e.Name == n.Name {
exists = true
break
}
}
if !exists {
newEnv = append(newEnv, e)
}
}
newEnv = append(newEnv, env...)
SetBuildEnv(build, newEnv)
}
// FindDockerSecretAsReference looks through a set of k8s Secrets to find one that represents Docker credentials
// and which contains credentials that are associated with the registry identified by the image. It returns
// a LocalObjectReference to the Secret, or nil if no match was found.
func FindDockerSecretAsReference(secrets []corev1.Secret, image string) *corev1.LocalObjectReference {
emptyKeyring := credentialprovider.BasicDockerKeyring{}
for _, secret := range secrets {
secretList := []corev1.Secret{secret}
keyring, err := credentialprovidersecrets.MakeDockerKeyring(secretList, &emptyKeyring)
if err != nil {
klog.V(2).Infof("Unable to make the Docker keyring for %s/%s secret: %v", secret.Name, secret.Namespace, err)
continue
}
if _, found := keyring.Lookup(image); found {
return &corev1.LocalObjectReference{Name: secret.Name}
}
}
return nil
}
// FetchServiceAccountSecrets retrieves the Secrets used for pushing and pulling
// images from private Docker registries.
func FetchServiceAccountSecrets(client ktypedclient.CoreV1Interface, namespace, serviceAccount string) ([]corev1.Secret, error) {
var result []corev1.Secret
sa, err := client.ServiceAccounts(namespace).Get(serviceAccount, metav1.GetOptions{})
if err != nil {
return result, fmt.Errorf("Error getting push/pull secrets for service account %s/%s: %v", namespace, serviceAccount, err)
}
for _, ref := range sa.Secrets {
secret, err := client.Secrets(namespace).Get(ref.Name, metav1.GetOptions{})
if err != nil {
continue
}
result = append(result, *secret)
}
return result, nil
}
// UpdateCustomImageEnv updates base image env variable reference with the new image for a custom build strategy.
// If no env variable reference exists, create a new env variable.
func UpdateCustomImageEnv(strategy *buildv1.CustomBuildStrategy, newImage string) {
if strategy.Env == nil {
strategy.Env = make([]corev1.EnvVar, 1)
strategy.Env[0] = corev1.EnvVar{Name: CustomBuildStrategyBaseImageKey, Value: newImage}
} else {
found := false
for i := range strategy.Env {
klog.V(4).Infof("Checking env variable %s %s", strategy.Env[i].Name, strategy.Env[i].Value)
if strategy.Env[i].Name == CustomBuildStrategyBaseImageKey {
found = true
strategy.Env[i].Value = newImage
klog.V(4).Infof("Updated env variable %s to %s", strategy.Env[i].Name, strategy.Env[i].Value)
break
}
}
if !found {
strategy.Env = append(strategy.Env, corev1.EnvVar{Name: CustomBuildStrategyBaseImageKey, Value: newImage})
}
}
}
// ParseProxyURL parses a proxy URL and allows fallback to non-URLs like
// myproxy:80 (for example) which url.Parse no longer accepts in Go 1.8. The
// logic is copied from net/http.ProxyFromEnvironment to try to maintain
// backwards compatibility.
func ParseProxyURL(proxy string) (*url.URL, error) {
proxyURL, err := url.Parse(proxy)
// logic copied from net/http.ProxyFromEnvironment
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
// proxy was bogus. Try prepending "http://" to it and see if that
// parses correctly. If not, we fall through and complain about the
// original one.
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
return proxyURL, nil
}
}
return proxyURL, err
}
// GetInputReference returns the From ObjectReference associated with the
// BuildStrategy.
func GetInputReference(strategy buildv1.BuildStrategy) *corev1.ObjectReference {
switch {
case strategy.SourceStrategy != nil:
return &strategy.SourceStrategy.From
case strategy.DockerStrategy != nil:
return strategy.DockerStrategy.From
case strategy.CustomStrategy != nil:
return &strategy.CustomStrategy.From
default:
return nil
}
}