forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathutil.go
357 lines (309 loc) · 12.2 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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
package strategy
import (
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/apis/policy"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kvalidation "k8s.io/apimachinery/pkg/util/validation"
buildv1 "github.com/openshift/api/build/v1"
"github.com/openshift/origin/pkg/api/apihelpers"
"github.com/openshift/origin/pkg/build/buildapihelpers"
buildutil "github.com/openshift/origin/pkg/build/util"
"github.com/openshift/origin/pkg/image/apis/image/reference"
)
const (
// dockerSocketPath is the default path for the Docker socket inside the builder container
dockerSocketPath = "/var/run/docker.sock"
sourceSecretMountPath = "/var/run/secrets/openshift.io/source"
DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push"
DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull"
ConfigMapBuildSourceBaseMountPath = "/var/run/configs/openshift.io/build"
SecretBuildSourceBaseMountPath = "/var/run/secrets/openshift.io/build"
SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image"
// ExtractImageContentContainer is the name of the container that will
// pull down input images and extract their content for input to the build.
ExtractImageContentContainer = "extract-image-content"
// GitCloneContainer is the name of the container that will clone the
// build source repository and also handle binary input content.
GitCloneContainer = "git-clone"
)
const (
CustomBuild = "custom-build"
DockerBuild = "docker-build"
StiBuild = "sti-build"
)
var BuildContainerNames = []string{CustomBuild, StiBuild, DockerBuild}
var (
// BuildControllerRefKind contains the schema.GroupVersionKind for builds.
// This is used in the ownerRef of builder pods.
BuildControllerRefKind = buildv1.GroupVersion.WithKind("Build")
)
// FatalError is an error which can't be retried.
type FatalError struct {
// Reason the fatal error occurred
Reason string
}
// Error implements the error interface.
func (e *FatalError) Error() string {
return fmt.Sprintf("fatal error: %s", e.Reason)
}
// IsFatal returns true if the error is fatal
func IsFatal(err error) bool {
_, isFatal := err.(*FatalError)
return isFatal
}
// setupDockerSocket configures the pod to support the host's Docker socket
func setupDockerSocket(pod *corev1.Pod) {
dockerSocketVolume := corev1.Volume{
Name: "docker-socket",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: dockerSocketPath,
},
},
}
dockerSocketVolumeMount := corev1.VolumeMount{
Name: "docker-socket",
MountPath: dockerSocketPath,
}
pod.Spec.Volumes = append(pod.Spec.Volumes,
dockerSocketVolume)
pod.Spec.Containers[0].VolumeMounts =
append(pod.Spec.Containers[0].VolumeMounts,
dockerSocketVolumeMount)
for i, initContainer := range pod.Spec.InitContainers {
if initContainer.Name == ExtractImageContentContainer {
pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, dockerSocketVolumeMount)
break
}
}
}
// setupCrioSocket configures the pod to support the host's Crio socket
func setupCrioSocket(pod *corev1.Pod) {
crioSocketVolume := corev1.Volume{
Name: "crio-socket",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/var/run/crio/crio.sock",
},
},
}
crioSocketVolumeMount := corev1.VolumeMount{
Name: "crio-socket",
MountPath: "/var/run/crio/crio.sock",
}
pod.Spec.Volumes = append(pod.Spec.Volumes,
crioSocketVolume)
pod.Spec.Containers[0].VolumeMounts =
append(pod.Spec.Containers[0].VolumeMounts,
crioSocketVolumeMount)
}
// mountConfigMapVolume is a helper method responsible for actual mounting configMap
// volumes into a pod.
func mountConfigMapVolume(pod *corev1.Pod, container *corev1.Container, configMapName, mountPath, volumeSuffix string) {
mountVolume(pod, container, configMapName, mountPath, volumeSuffix, policy.ConfigMap)
}
// mountSecretVolume is a helper method responsible for actual mounting secret
// volumes into a pod.
func mountSecretVolume(pod *corev1.Pod, container *corev1.Container, secretName, mountPath, volumeSuffix string) {
mountVolume(pod, container, secretName, mountPath, volumeSuffix, policy.Secret)
}
// mountVolume is a helper method responsible for mounting volumes into a pod.
// The following file system types for the volume are supported:
//
// 1. ConfigMap
// 2. EmptyDir
// 3. Secret
func mountVolume(pod *corev1.Pod, container *corev1.Container, objName, mountPath, volumeSuffix string, fsType policy.FSType) {
volumeName := apihelpers.GetName(objName, volumeSuffix, kvalidation.DNS1123LabelMaxLength)
// coerce from RFC1123 subdomain to RFC1123 label.
volumeName = strings.Replace(volumeName, ".", "-", -1)
volumeExists := false
for _, v := range pod.Spec.Volumes {
if v.Name == volumeName {
volumeExists = true
break
}
}
mode := int32(0600)
if !volumeExists {
volume := makeVolume(volumeName, objName, mode, fsType)
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
}
volumeMount := corev1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
ReadOnly: true,
}
container.VolumeMounts = append(container.VolumeMounts, volumeMount)
}
func makeVolume(volumeName, refName string, mode int32, fsType policy.FSType) corev1.Volume {
// TODO: Add support for key-based paths for secrets and configMaps?
vol := corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{},
}
switch fsType {
case policy.ConfigMap:
vol.VolumeSource.ConfigMap = &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: refName,
},
DefaultMode: &mode,
}
case policy.EmptyDir:
vol.VolumeSource.EmptyDir = &corev1.EmptyDirVolumeSource{}
case policy.Secret:
vol.VolumeSource.Secret = &corev1.SecretVolumeSource{
SecretName: refName,
DefaultMode: &mode,
}
default:
glog.V(3).Infof("File system %s is not supported for volumes. Using empty directory instead.", fsType)
vol.VolumeSource.EmptyDir = &corev1.EmptyDirVolumeSource{}
}
return vol
}
// setupDockerSecrets mounts Docker Registry secrets into Pod running the build,
// allowing Docker to authenticate against private registries or Docker Hub.
func setupDockerSecrets(pod *corev1.Pod, container *corev1.Container, pushSecret, pullSecret *corev1.LocalObjectReference, imageSources []buildv1.ImageSource) {
if pushSecret != nil {
mountSecretVolume(pod, container, pushSecret.Name, DockerPushSecretMountPath, "push")
container.Env = append(container.Env, []corev1.EnvVar{
{Name: "PUSH_DOCKERCFG_PATH", Value: DockerPushSecretMountPath},
}...)
glog.V(3).Infof("%s will be used for docker push in %s", DockerPushSecretMountPath, pod.Name)
}
if pullSecret != nil {
mountSecretVolume(pod, container, pullSecret.Name, DockerPullSecretMountPath, "pull")
container.Env = append(container.Env, []corev1.EnvVar{
{Name: "PULL_DOCKERCFG_PATH", Value: DockerPullSecretMountPath},
}...)
glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name)
}
for i, imageSource := range imageSources {
if imageSource.PullSecret == nil {
continue
}
mountPath := filepath.Join(SourceImagePullSecretMountPath, strconv.Itoa(i))
mountSecretVolume(pod, container, imageSource.PullSecret.Name, mountPath, fmt.Sprintf("%s%d", "source-image", i))
container.Env = append(container.Env, []corev1.EnvVar{
{Name: fmt.Sprintf("%s%d", "PULL_SOURCE_DOCKERCFG_PATH_", i), Value: mountPath},
}...)
glog.V(3).Infof("%s will be used for docker pull in %s", mountPath, pod.Name)
}
}
// setupSourceSecrets mounts SSH key used for accessing private SCM to clone
// application source code during build.
func setupSourceSecrets(pod *corev1.Pod, container *corev1.Container, sourceSecret *corev1.LocalObjectReference) {
if sourceSecret == nil {
return
}
mountSecretVolume(pod, container, sourceSecret.Name, sourceSecretMountPath, "source")
glog.V(3).Infof("Installed source secrets in %s, in Pod %s/%s", sourceSecretMountPath, pod.Namespace, pod.Name)
container.Env = append(container.Env, []corev1.EnvVar{
{Name: "SOURCE_SECRET_PATH", Value: sourceSecretMountPath},
}...)
}
// setupInputConfigMaps mounts the configMaps referenced by the ConfigMapBuildSource
// into a builder container.
func setupInputConfigMaps(pod *corev1.Pod, container *corev1.Container, configs []buildv1.ConfigMapBuildSource) {
for _, c := range configs {
mountConfigMapVolume(pod, container, c.ConfigMap.Name, filepath.Join(ConfigMapBuildSourceBaseMountPath, c.ConfigMap.Name), "build")
glog.V(3).Infof("%s will be used as a build config in %s", c.ConfigMap.Name, ConfigMapBuildSourceBaseMountPath)
}
}
// setupInputSecrets mounts the secrets referenced by the SecretBuildSource
// into a builder container.
func setupInputSecrets(pod *corev1.Pod, container *corev1.Container, secrets []buildv1.SecretBuildSource) {
for _, s := range secrets {
mountSecretVolume(pod, container, s.Secret.Name, filepath.Join(SecretBuildSourceBaseMountPath, s.Secret.Name), "build")
glog.V(3).Infof("%s will be used as a build secret in %s", s.Secret.Name, SecretBuildSourceBaseMountPath)
}
}
// addSourceEnvVars adds environment variables related to the source code
// repository to builder container
func addSourceEnvVars(source buildv1.BuildSource, output *[]corev1.EnvVar) {
sourceVars := []corev1.EnvVar{}
if source.Git != nil {
sourceVars = append(sourceVars, corev1.EnvVar{Name: "SOURCE_REPOSITORY", Value: source.Git.URI})
sourceVars = append(sourceVars, corev1.EnvVar{Name: "SOURCE_URI", Value: source.Git.URI})
}
if len(source.ContextDir) > 0 {
sourceVars = append(sourceVars, corev1.EnvVar{Name: "SOURCE_CONTEXT_DIR", Value: source.ContextDir})
}
if source.Git != nil && len(source.Git.Ref) > 0 {
sourceVars = append(sourceVars, corev1.EnvVar{Name: "SOURCE_REF", Value: source.Git.Ref})
}
*output = append(*output, sourceVars...)
}
// addOutputEnvVars adds env variables that provide information about the output
// target for the build
func addOutputEnvVars(buildOutput *corev1.ObjectReference, output *[]corev1.EnvVar) error {
if buildOutput == nil {
return nil
}
// output must always be a DockerImage type reference at this point.
if buildOutput.Kind != "DockerImage" {
return fmt.Errorf("invalid build output kind %s, must be DockerImage", buildOutput.Kind)
}
ref, err := reference.Parse(buildOutput.Name)
if err != nil {
return err
}
registry := ref.Registry
ref.Registry = ""
image := ref.String()
outputVars := []corev1.EnvVar{
{Name: "OUTPUT_REGISTRY", Value: registry},
{Name: "OUTPUT_IMAGE", Value: image},
}
*output = append(*output, outputVars...)
return nil
}
// setupAdditionalSecrets creates secret volume mounts in the given pod for the given list of secrets
func setupAdditionalSecrets(pod *corev1.Pod, container *corev1.Container, secrets []buildv1.SecretSpec) {
for _, secretSpec := range secrets {
mountSecretVolume(pod, container, secretSpec.SecretSource.Name, secretSpec.MountPath, "secret")
glog.V(3).Infof("Installed additional secret in %s, in Pod %s/%s", secretSpec.MountPath, pod.Namespace, pod.Name)
}
}
// getPodLabels creates labels for the Build Pod
func getPodLabels(build *buildv1.Build) map[string]string {
return map[string]string{buildutil.BuildLabel: buildapihelpers.LabelValue(build.Name)}
}
func makeOwnerReference(build *buildv1.Build) metav1.OwnerReference {
t := true
return metav1.OwnerReference{
APIVersion: BuildControllerRefKind.GroupVersion().String(),
Kind: BuildControllerRefKind.Kind,
Name: build.Name,
UID: build.UID,
Controller: &t,
}
}
func setOwnerReference(pod *corev1.Pod, build *buildv1.Build) {
pod.OwnerReferences = []metav1.OwnerReference{makeOwnerReference(build)}
}
// HasOwnerReference returns true if the build pod has an OwnerReference to the
// build.
func HasOwnerReference(pod *corev1.Pod, build *buildv1.Build) bool {
ref := makeOwnerReference(build)
for _, r := range pod.OwnerReferences {
if reflect.DeepEqual(r, ref) {
return true
}
}
return false
}
// copyEnvVarSlice returns a copy of an []corev1.EnvVar
func copyEnvVarSlice(in []corev1.EnvVar) []corev1.EnvVar {
out := make([]corev1.EnvVar, len(in))
copy(out, in)
return out
}