Browse code

Build defaults and build overrides admission plugins

The BuildDefaults plugin allows setting default values for proxy
environment variables and git HTTP/S proxy settings.
The BuildOverrides plugin overrides the ForcePull setting on all
builds going through the build pod strategy.

Cesar Wong authored on 2016/01/28 05:45:49
Showing 40 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,133 @@
0
+package admission
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+
6
+	"k8s.io/kubernetes/pkg/admission"
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	"k8s.io/kubernetes/pkg/api/unversioned"
9
+
10
+	buildapi "github.com/openshift/origin/pkg/build/api"
11
+)
12
+
13
+// IsBuildPod returns true if a pod is a pod generated for a Build
14
+func IsBuildPod(a admission.Attributes) bool {
15
+	if a.GetResource() != kapi.Resource("pods") {
16
+		return false
17
+	}
18
+	if len(a.GetSubresource()) != 0 {
19
+		return false
20
+	}
21
+	pod, err := GetPod(a)
22
+	if err != nil {
23
+		return false
24
+	}
25
+	return hasBuildAnnotation(pod) && hasBuildEnvVar(pod)
26
+}
27
+
28
+// GetBuild returns a build object encoded in a pod's BUILD environment variable along with
29
+// its encoding version
30
+func GetBuild(a admission.Attributes) (*buildapi.Build, *unversioned.GroupVersion, error) {
31
+	pod, err := GetPod(a)
32
+	if err != nil {
33
+		return nil, nil, err
34
+	}
35
+	build, version, err := getBuildFromPod(pod)
36
+	if err != nil {
37
+		return nil, nil, admission.NewForbidden(a, fmt.Errorf("unable to get build from pod: %v", err))
38
+	}
39
+	return build, version, nil
40
+}
41
+
42
+// GetPod returns a pod from an admission attributes object
43
+func GetPod(a admission.Attributes) (*kapi.Pod, error) {
44
+	pod, isPod := a.GetObject().(*kapi.Pod)
45
+	if !isPod {
46
+		return nil, admission.NewForbidden(a, fmt.Errorf("unrecognized request object: %#v", a.GetObject()))
47
+	}
48
+	return pod, nil
49
+}
50
+
51
+// SetBuild encodes a build object and sets it in a pod's BUILD environment variable
52
+func SetBuild(a admission.Attributes, build *buildapi.Build, groupVersion *unversioned.GroupVersion) error {
53
+	pod, err := GetPod(a)
54
+	if err != nil {
55
+		return err
56
+	}
57
+	err = setBuildInPod(build, pod, groupVersion)
58
+	if err != nil {
59
+		return admission.NewForbidden(a, fmt.Errorf("unable to set build in pod: %v", err))
60
+	}
61
+	return nil
62
+}
63
+
64
+// getBuildFromPod detects the encoding of a Build in a pod and returns the Build
65
+// object along with its detected version.
66
+func getBuildFromPod(pod *kapi.Pod) (*buildapi.Build, *unversioned.GroupVersion, error) {
67
+	envVar, err := buildEnvVar(pod)
68
+	if err != nil {
69
+		return nil, nil, err
70
+	}
71
+	kind, err := kapi.Scheme.DataKind([]byte(envVar.Value))
72
+	if err != nil {
73
+		return nil, nil, err
74
+	}
75
+	groupVersion, err := unversioned.ParseGroupVersion(kind.Version)
76
+	if err != nil {
77
+		return nil, nil, err
78
+	}
79
+	obj, err := kapi.Scheme.Decode([]byte(envVar.Value))
80
+	if err != nil {
81
+		return nil, nil, err
82
+	}
83
+	build, ok := obj.(*buildapi.Build)
84
+	if !ok {
85
+		return nil, nil, errors.New("decoded object is not of type Build")
86
+	}
87
+	return build, &groupVersion, nil
88
+}
89
+
90
+// setBuildInPod encodes a build with the given version and sets it in the BUILD environment variable
91
+// of the pod.
92
+func setBuildInPod(build *buildapi.Build, pod *kapi.Pod, groupVersion *unversioned.GroupVersion) error {
93
+	envVar, err := buildEnvVar(pod)
94
+	if err != nil {
95
+		return err
96
+	}
97
+	encodedBuild, err := kapi.Scheme.EncodeToVersion(build, groupVersion.Version)
98
+	if err != nil {
99
+		return err
100
+	}
101
+	envVar.Value = string(encodedBuild)
102
+	return nil
103
+}
104
+
105
+func buildEnvVar(pod *kapi.Pod) (*kapi.EnvVar, error) {
106
+	if len(pod.Spec.Containers) == 0 {
107
+		return nil, errors.New("pod has no containers")
108
+	}
109
+	env := pod.Spec.Containers[0].Env
110
+	for i := range env {
111
+		if env[i].Name == "BUILD" {
112
+			if len(env[i].Value) == 0 {
113
+				return nil, errors.New("BUILD environment variable is empty")
114
+			}
115
+			return &env[i], nil
116
+		}
117
+	}
118
+	return nil, errors.New("pod does not have a BUILD environment variable")
119
+}
120
+
121
+func hasBuildEnvVar(pod *kapi.Pod) bool {
122
+	_, err := buildEnvVar(pod)
123
+	return err == nil
124
+}
125
+
126
+func hasBuildAnnotation(pod *kapi.Pod) bool {
127
+	if pod.Annotations == nil {
128
+		return false
129
+	}
130
+	_, hasAnnotation := pod.Annotations[buildapi.BuildAnnotation]
131
+	return hasAnnotation
132
+}
0 133
new file mode 100644
... ...
@@ -0,0 +1,80 @@
0
+package admission
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+
6
+	"k8s.io/kubernetes/pkg/api/unversioned"
7
+
8
+	u "github.com/openshift/origin/pkg/build/admission/testutil"
9
+	buildapi "github.com/openshift/origin/pkg/build/api"
10
+)
11
+
12
+func TestIsBuildPod(t *testing.T) {
13
+	tests := []struct {
14
+		pod      *u.TestPod
15
+		expected bool
16
+	}{
17
+		{
18
+			pod:      u.Pod().WithAnnotation("foo", "bar"),
19
+			expected: false,
20
+		},
21
+		{
22
+			pod:      u.Pod().WithEnvVar("BUILD", "blah"),
23
+			expected: false,
24
+		},
25
+		{
26
+			pod:      u.Pod().WithAnnotation(buildapi.BuildAnnotation, "build"),
27
+			expected: false,
28
+		},
29
+		{
30
+			pod: u.Pod().
31
+				WithAnnotation(buildapi.BuildAnnotation, "build").
32
+				WithEnvVar("BUILD", "true"),
33
+			expected: true,
34
+		},
35
+	}
36
+
37
+	for _, tc := range tests {
38
+		actual := IsBuildPod(tc.pod.ToAttributes())
39
+		if actual != tc.expected {
40
+			t.Errorf("unexpected result (%v) for pod %#v", actual, tc.pod)
41
+		}
42
+	}
43
+}
44
+
45
+func TestGetBuild(t *testing.T) {
46
+	build := u.Build().WithDockerStrategy()
47
+	for _, version := range []string{"v1", "v1beta3"} {
48
+		pod := u.Pod().WithBuild(t, build.AsBuild(), version)
49
+		resultBuild, resultVersion, err := GetBuild(pod.ToAttributes())
50
+		if err != nil {
51
+			t.Fatalf("unexpected error: %v", err)
52
+		}
53
+		if resultVersion.Version != version {
54
+			t.Errorf("unexpected version: %s", resultVersion)
55
+		}
56
+		if !reflect.DeepEqual(build.AsBuild(), resultBuild) {
57
+			t.Errorf("%s: did not get expected build: %#v", version, resultBuild)
58
+		}
59
+	}
60
+}
61
+
62
+func TestSetBuild(t *testing.T) {
63
+	build := u.Build().WithSourceStrategy()
64
+	for _, version := range []string{"v1", "v1beta3"} {
65
+		pod := u.Pod().WithEnvVar("BUILD", "foo")
66
+		groupVersion, err := unversioned.ParseGroupVersion(version)
67
+		if err != nil {
68
+			t.Fatalf("unexpected error: %v", err)
69
+		}
70
+		err = SetBuild(pod.ToAttributes(), build.AsBuild(), &groupVersion)
71
+		if err != nil {
72
+			t.Fatalf("unexpected error: %v", err)
73
+		}
74
+		resultBuild := pod.GetBuild(t)
75
+		if !reflect.DeepEqual(build.AsBuild(), resultBuild) {
76
+			t.Errorf("%s: did not get expected build: %#v", version, resultBuild)
77
+		}
78
+	}
79
+}
0 80
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package admission
1
+
2
+import (
3
+	"io"
4
+	"io/ioutil"
5
+	"reflect"
6
+
7
+	"k8s.io/kubernetes/pkg/runtime"
8
+
9
+	configlatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
10
+)
11
+
12
+// ReadPluginConfig will read a plugin configuration object from a reader stream
13
+func ReadPluginConfig(reader io.Reader, config runtime.Object) error {
14
+	if reader == nil || reflect.ValueOf(reader).IsNil() {
15
+		return nil
16
+	}
17
+
18
+	configBytes, err := ioutil.ReadAll(reader)
19
+	if err != nil {
20
+		return err
21
+	}
22
+	err = configlatest.ReadYAML(configBytes, config)
23
+	if err != nil {
24
+		return err
25
+	}
26
+	return nil
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package admission
1
+
2
+import (
3
+	"bytes"
4
+	"reflect"
5
+	"testing"
6
+
7
+	"k8s.io/kubernetes/pkg/api/unversioned"
8
+
9
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
10
+)
11
+
12
+type TestConfig struct {
13
+	unversioned.TypeMeta
14
+
15
+	Item1 string   `json:"item1"`
16
+	Item2 []string `json:"item2"`
17
+}
18
+
19
+func (*TestConfig) IsAnAPIObject() {}
20
+
21
+type TestConfig2 struct {
22
+	unversioned.TypeMeta
23
+	Item1 string `json:"item1"`
24
+}
25
+
26
+func (*TestConfig2) IsAnAPIObject() {}
27
+
28
+func TestReadPluginConfig(t *testing.T) {
29
+	groupVersion := unversioned.GroupVersion{Group: "", Version: ""}
30
+	v1GroupVersion := unversioned.GroupVersion{Group: "", Version: "v1"}
31
+	configapi.Scheme.AddKnownTypes(groupVersion, &TestConfig{})
32
+	configapi.Scheme.AddKnownTypes(v1GroupVersion, &TestConfig{})
33
+	configapi.Scheme.AddKnownTypes(groupVersion, &TestConfig2{})
34
+	configapi.Scheme.AddKnownTypes(v1GroupVersion, &TestConfig2{})
35
+
36
+	configString := `apiVersion: v1
37
+kind: TestConfig
38
+item1: hello
39
+item2:
40
+- foo
41
+- bar
42
+`
43
+	config := &TestConfig{}
44
+
45
+	expected := &TestConfig{
46
+		Item1: "hello",
47
+		Item2: []string{"foo", "bar"},
48
+	}
49
+	// The config should match the expected config object
50
+	err := ReadPluginConfig(bytes.NewBufferString(configString), config)
51
+	if err != nil {
52
+		t.Fatalf("unexpected: %v", err)
53
+	}
54
+	if !reflect.DeepEqual(config, expected) {
55
+		t.Errorf("config does not equal expected: %#v", config)
56
+	}
57
+
58
+	// Passing a nil reader, should not get an error
59
+	var nilBuffer *bytes.Buffer
60
+	err = ReadPluginConfig(nilBuffer, &TestConfig{})
61
+	if err != nil {
62
+		t.Fatalf("unexpected: %v", err)
63
+	}
64
+
65
+	// Passing the wrong type of destination object should result in an error
66
+	config2 := &TestConfig2{}
67
+	err = ReadPluginConfig(bytes.NewBufferString(configString), config2)
68
+	if err == nil {
69
+		t.Fatalf("expected error")
70
+	}
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,127 @@
0
+package defaults
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/golang/glog"
6
+	"k8s.io/kubernetes/pkg/admission"
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
9
+
10
+	buildadmission "github.com/openshift/origin/pkg/build/admission"
11
+	buildapi "github.com/openshift/origin/pkg/build/api"
12
+)
13
+
14
+func init() {
15
+	admission.RegisterPlugin("BuildDefaults", func(c kclient.Interface, config io.Reader) (admission.Interface, error) {
16
+
17
+		defaultsConfig, err := getConfig(config)
18
+		if err != nil {
19
+			return nil, err
20
+		}
21
+
22
+		glog.V(4).Infof("Initializing BuildDefaults plugin with config: %#v", defaultsConfig)
23
+		return NewBuildDefaults(defaultsConfig), nil
24
+	})
25
+}
26
+
27
+func getConfig(in io.Reader) (*BuildDefaultsConfig, error) {
28
+	defaultsConfig := &BuildDefaultsConfig{}
29
+	err := buildadmission.ReadPluginConfig(in, defaultsConfig)
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+	errs := ValidateBuildDefaultsConfig(defaultsConfig)
34
+	if len(errs) > 0 {
35
+		return nil, errs.ToAggregate()
36
+	}
37
+	return defaultsConfig, nil
38
+}
39
+
40
+type buildDefaults struct {
41
+	*admission.Handler
42
+	defaultsConfig *BuildDefaultsConfig
43
+}
44
+
45
+// NewBuildDefaults returns an admission control for builds that sets build defaults
46
+// based on the plugin configuration
47
+func NewBuildDefaults(defaultsConfig *BuildDefaultsConfig) admission.Interface {
48
+	return &buildDefaults{
49
+		Handler:        admission.NewHandler(admission.Create),
50
+		defaultsConfig: defaultsConfig,
51
+	}
52
+}
53
+
54
+// Admit applies configured build defaults to a pod that is identified
55
+// as a build pod.
56
+func (a *buildDefaults) Admit(attributes admission.Attributes) error {
57
+	if a.defaultsConfig == nil {
58
+		return nil
59
+	}
60
+	if !buildadmission.IsBuildPod(attributes) {
61
+		return nil
62
+	}
63
+	build, version, err := buildadmission.GetBuild(attributes)
64
+	if err != nil {
65
+		return nil
66
+	}
67
+
68
+	glog.V(4).Infof("Handling build %s/%s", build.Namespace, build.Name)
69
+
70
+	a.applyBuildDefaults(build)
71
+
72
+	return buildadmission.SetBuild(attributes, build, version)
73
+}
74
+
75
+func (a *buildDefaults) applyBuildDefaults(build *buildapi.Build) {
76
+	// Apply default env
77
+	buildEnv := getBuildEnv(build)
78
+	for _, envVar := range a.defaultsConfig.Env {
79
+		glog.V(5).Infof("Adding default environment variable %s=%s to build %s/%s", envVar.Name, envVar.Value, build.Namespace, build.Name)
80
+		addDefaultEnvVar(envVar, buildEnv)
81
+	}
82
+
83
+	// Apply git proxy defaults
84
+	if build.Spec.Source.Git == nil {
85
+		return
86
+	}
87
+	if len(a.defaultsConfig.GitHTTPProxy) != 0 {
88
+		if build.Spec.Source.Git.HTTPProxy == nil {
89
+			t := a.defaultsConfig.GitHTTPProxy
90
+			glog.V(5).Infof("Setting default Git HTTP proxy of build %s/%s to %s", build.Namespace, build.Name, t)
91
+			build.Spec.Source.Git.HTTPProxy = &t
92
+		}
93
+	}
94
+
95
+	if len(a.defaultsConfig.GitHTTPSProxy) != 0 {
96
+		if build.Spec.Source.Git.HTTPSProxy == nil {
97
+			t := a.defaultsConfig.GitHTTPSProxy
98
+			glog.V(5).Infof("Setting default Git HTTPS proxy of build %s/%s to %s", build.Namespace, build.Name, t)
99
+			build.Spec.Source.Git.HTTPSProxy = &t
100
+		}
101
+	}
102
+}
103
+
104
+func getBuildEnv(build *buildapi.Build) *[]kapi.EnvVar {
105
+	switch {
106
+	case build.Spec.Strategy.DockerStrategy != nil:
107
+		return &build.Spec.Strategy.DockerStrategy.Env
108
+	case build.Spec.Strategy.SourceStrategy != nil:
109
+		return &build.Spec.Strategy.SourceStrategy.Env
110
+	case build.Spec.Strategy.CustomStrategy != nil:
111
+		return &build.Spec.Strategy.CustomStrategy.Env
112
+	}
113
+	return nil
114
+}
115
+
116
+func addDefaultEnvVar(v kapi.EnvVar, envVars *[]kapi.EnvVar) {
117
+	found := false
118
+	for i := range *envVars {
119
+		if (*envVars)[i].Name == v.Name {
120
+			found = true
121
+		}
122
+	}
123
+	if !found {
124
+		*envVars = append(*envVars, v)
125
+	}
126
+}
0 127
new file mode 100644
... ...
@@ -0,0 +1,84 @@
0
+package defaults
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+
7
+	buildadmission "github.com/openshift/origin/pkg/build/admission"
8
+	u "github.com/openshift/origin/pkg/build/admission/testutil"
9
+)
10
+
11
+func TestProxyDefaults(t *testing.T) {
12
+	defaultsConfig := &BuildDefaultsConfig{
13
+		GitHTTPProxy:  "http",
14
+		GitHTTPSProxy: "https",
15
+	}
16
+
17
+	admitter := NewBuildDefaults(defaultsConfig)
18
+	pod := u.Pod().WithBuild(t, u.Build().WithDockerStrategy().AsBuild(), "v1")
19
+	err := admitter.Admit(pod.ToAttributes())
20
+	if err != nil {
21
+		t.Fatalf("unexpected error: %v", err)
22
+	}
23
+	build, _, err := buildadmission.GetBuild(pod.ToAttributes())
24
+	if err != nil {
25
+		t.Fatalf("unexpected error: %v", err)
26
+	}
27
+
28
+	if build.Spec.Source.Git.HTTPProxy == nil || len(*build.Spec.Source.Git.HTTPProxy) == 0 || *build.Spec.Source.Git.HTTPProxy != "http" {
29
+		t.Errorf("failed to find http proxy in git source")
30
+	}
31
+	if build.Spec.Source.Git.HTTPSProxy == nil || len(*build.Spec.Source.Git.HTTPSProxy) == 0 || *build.Spec.Source.Git.HTTPSProxy != "https" {
32
+		t.Errorf("failed to find http proxy in git source")
33
+	}
34
+}
35
+
36
+func TestEnvDefaults(t *testing.T) {
37
+	defaultsConfig := &BuildDefaultsConfig{
38
+		Env: []kapi.EnvVar{
39
+			{
40
+				Name:  "VAR1",
41
+				Value: "VALUE1",
42
+			},
43
+			{
44
+				Name:  "VAR2",
45
+				Value: "VALUE2",
46
+			},
47
+		},
48
+	}
49
+
50
+	admitter := NewBuildDefaults(defaultsConfig)
51
+	pod := u.Pod().WithBuild(t, u.Build().WithSourceStrategy().AsBuild(), "v1")
52
+	err := admitter.Admit(pod.ToAttributes())
53
+	if err != nil {
54
+		t.Fatalf("unexpected error: %v", err)
55
+	}
56
+	build, _, err := buildadmission.GetBuild(pod.ToAttributes())
57
+	if err != nil {
58
+		t.Fatalf("unexpected error: %v", err)
59
+	}
60
+
61
+	env := getBuildEnv(build)
62
+	var1found, var2found := false, false
63
+	for _, ev := range *env {
64
+		if ev.Name == "VAR1" {
65
+			if ev.Value != "VALUE1" {
66
+				t.Errorf("unexpected value %s", ev.Value)
67
+			}
68
+			var1found = true
69
+		}
70
+		if ev.Name == "VAR2" {
71
+			if ev.Value != "VALUE2" {
72
+				t.Errorf("unexpected value %s", ev.Value)
73
+			}
74
+			var2found = true
75
+		}
76
+	}
77
+	if !var1found {
78
+		t.Errorf("VAR1 not found")
79
+	}
80
+	if !var2found {
81
+		t.Errorf("VAR2 not found")
82
+	}
83
+}
0 84
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+/*
1
+Package defaults contains the BuildDefaults admission control plugin.
2
+
3
+The plugin allows setting default values for build setings like the git HTTP
4
+and HTTPS proxy URLs and additional environment variables for the build
5
+strategy
6
+
7
+Configuration
8
+
9
+Configuration is done via a BuildDefaultsConfig object:
10
+
11
+ apiVersion: v1
12
+ kind: BuildDefaultsConfiguration
13
+ gitHTTPProxy: http://my.proxy.server:12345
14
+ gitHTTPSProxy: https://my.proxy.server:7890
15
+ env:
16
+ - name: ENV_VAR1
17
+   value: VALUE1
18
+ - name: ENV_VAR2
19
+   value: VALUE2
20
+*/
21
+package defaults
0 22
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+package latest
1
+
2
+import (
3
+	_ "github.com/openshift/origin/pkg/build/admission/defaults/v1"
4
+)
0 5
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package defaults
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+
5
+	_ "github.com/openshift/origin/pkg/build/admission/defaults/latest"
6
+	"github.com/openshift/origin/pkg/cmd/server/api"
7
+)
8
+
9
+// SchemeGroupVersion is group version used to register these objects
10
+var SchemeGroupVersion = unversioned.GroupVersion{Group: "", Version: ""}
11
+
12
+// Kind takes an unqualified kind and returns back a Group qualified GroupKind
13
+func Kind(kind string) unversioned.GroupKind {
14
+	return SchemeGroupVersion.WithKind(kind).GroupKind()
15
+}
16
+
17
+// Resource takes an unqualified resource and returns back a Group qualified GroupResource
18
+func Resource(resource string) unversioned.GroupResource {
19
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
20
+}
21
+
22
+func init() {
23
+	api.Scheme.AddKnownTypes(SchemeGroupVersion,
24
+		&BuildDefaultsConfig{},
25
+	)
26
+}
27
+
28
+func (*BuildDefaultsConfig) IsAnAPIObject() {}
0 29
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package defaults
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+	"k8s.io/kubernetes/pkg/api/unversioned"
5
+)
6
+
7
+// BuildDefaultsConfig controls the default information for Builds
8
+type BuildDefaultsConfig struct {
9
+	unversioned.TypeMeta
10
+
11
+	// GitHTTPProxy is the location of the HTTPProxy for Git source
12
+	GitHTTPProxy string
13
+
14
+	// GitHTTPSProxy is the location of the HTTPSProxy for Git source
15
+	GitHTTPSProxy string
16
+
17
+	// Env is a set of default environment variables that will be applied to the
18
+	// build if the specified variables do not exist on the build
19
+	Env []kapi.EnvVar
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package v1
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+
5
+	"github.com/openshift/origin/pkg/cmd/server/api"
6
+)
7
+
8
+// SchemeGroupVersion is group version used to register these objects
9
+var SchemeGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"}
10
+
11
+func init() {
12
+	api.Scheme.AddKnownTypes(SchemeGroupVersion,
13
+		&BuildDefaultsConfig{},
14
+	)
15
+}
16
+
17
+func (*BuildDefaultsConfig) IsAnAPIObject() {}
0 18
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package v1
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+	"k8s.io/kubernetes/pkg/api/unversioned"
5
+)
6
+
7
+// BuildDefaultsConfig controls the default information for Builds
8
+type BuildDefaultsConfig struct {
9
+	unversioned.TypeMeta
10
+
11
+	// GitHTTPProxy is the location of the HTTPProxy for Git source
12
+	GitHTTPProxy string `json:"gitHTTPProxy,omitempty",description:"location of the git http proxy"`
13
+
14
+	// GitHTTPSProxy is the location of the HTTPSProxy for Git source
15
+	GitHTTPSProxy string `json:"gitHTTPSProxy,omitempty",description:"location of the git https proxy"`
16
+
17
+	// Env is a set of default environment variables that will be applied to the
18
+	// build if the specified variables do not exist on the build
19
+	Env []kapi.EnvVar `json:"env,omitempty",description:"default environment variable values to add to builds"`
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package defaults
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/util/validation/field"
4
+
5
+	buildvalidation "github.com/openshift/origin/pkg/build/api/validation"
6
+)
7
+
8
+// ValidateBuild tests required fields for a Build.
9
+func ValidateBuildDefaultsConfig(config *BuildDefaultsConfig) field.ErrorList {
10
+	allErrs := field.ErrorList{}
11
+	allErrs = append(allErrs, validateURL(config.GitHTTPProxy, field.NewPath("gitHTTPProxy"))...)
12
+	allErrs = append(allErrs, validateURL(config.GitHTTPSProxy, field.NewPath("gitHTTPSProxy"))...)
13
+	allErrs = append(allErrs, buildvalidation.ValidateStrategyEnv(config.Env, field.NewPath("env"))...)
14
+	return allErrs
15
+}
16
+
17
+//
18
+func validateURL(u string, path *field.Path) field.ErrorList {
19
+	allErrs := field.ErrorList{}
20
+	if !buildvalidation.IsValidURL(u) {
21
+		allErrs = append(allErrs, field.Invalid(path, u, "invalid URL"))
22
+	}
23
+	return allErrs
24
+}
0 25
new file mode 100644
... ...
@@ -0,0 +1,120 @@
0
+package defaults
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	"k8s.io/kubernetes/pkg/util/validation/field"
7
+)
8
+
9
+func TestValidateBuildDefaultsConfig(t *testing.T) {
10
+	tests := []struct {
11
+		config      *BuildDefaultsConfig
12
+		errExpected bool
13
+		errField    string
14
+		errType     field.ErrorType
15
+	}{
16
+		// 0: Valid config
17
+		{
18
+			config: &BuildDefaultsConfig{
19
+				GitHTTPProxy:  "http://valid.url",
20
+				GitHTTPSProxy: "https://valid.url",
21
+				Env: []kapi.EnvVar{
22
+					{
23
+						Name:  "VAR1",
24
+						Value: "VALUE1",
25
+					},
26
+					{
27
+						Name:  "VAR2",
28
+						Value: "VALUE2",
29
+					},
30
+				},
31
+			},
32
+			errExpected: false,
33
+		},
34
+		// 1:  invalid HTTP proxy
35
+		{
36
+			config: &BuildDefaultsConfig{
37
+				GitHTTPProxy:  "some!@#$%^&*()url",
38
+				GitHTTPSProxy: "https://valid.url",
39
+			},
40
+			errExpected: true,
41
+			errField:    "gitHTTPProxy",
42
+			errType:     field.ErrorTypeInvalid,
43
+		},
44
+		// 2:  invalid HTTPS proxy
45
+		{
46
+			config: &BuildDefaultsConfig{
47
+				GitHTTPProxy:  "https://valid.url",
48
+				GitHTTPSProxy: "some!@#$%^&*()url",
49
+			},
50
+			errExpected: true,
51
+			errField:    "gitHTTPSProxy",
52
+			errType:     field.ErrorTypeInvalid,
53
+		},
54
+		// 3: missing Env variable name
55
+		{
56
+			config: &BuildDefaultsConfig{
57
+				Env: []kapi.EnvVar{
58
+					{
59
+						Name:  "",
60
+						Value: "test",
61
+					},
62
+				},
63
+			},
64
+			errExpected: true,
65
+			errField:    "env[0].name",
66
+			errType:     field.ErrorTypeRequired,
67
+		},
68
+		// 4: invalid Env variable name
69
+		{
70
+			config: &BuildDefaultsConfig{
71
+				Env: []kapi.EnvVar{
72
+					{
73
+						Name:  " invalid,name",
74
+						Value: "test",
75
+					},
76
+				},
77
+			},
78
+			errExpected: true,
79
+			errField:    "env[0].name",
80
+			errType:     field.ErrorTypeInvalid,
81
+		},
82
+		// 5: valueFrom present in env var
83
+		{
84
+			config: &BuildDefaultsConfig{
85
+				Env: []kapi.EnvVar{
86
+					{
87
+						Name:      "name",
88
+						Value:     "test",
89
+						ValueFrom: &kapi.EnvVarSource{},
90
+					},
91
+				},
92
+			},
93
+			errExpected: true,
94
+			errField:    "env[0].valueFrom",
95
+			errType:     field.ErrorTypeInvalid,
96
+		},
97
+	}
98
+
99
+	for i, tc := range tests {
100
+		result := ValidateBuildDefaultsConfig(tc.config)
101
+		if !tc.errExpected {
102
+			if len(result) > 0 {
103
+				t.Errorf("%d: unexpected error: %v", i, result.ToAggregate())
104
+			}
105
+			continue
106
+		}
107
+		if tc.errExpected && len(result) == 0 {
108
+			t.Errorf("%d: did not get expected error", i)
109
+			continue
110
+		}
111
+		err := result[0]
112
+		if err.Type != tc.errType {
113
+			t.Errorf("%d: unexpected error type: %v", i, err.Type)
114
+		}
115
+		if err.Field != tc.errField {
116
+			t.Errorf("%d: unexpected error field: %v", i, err.Field)
117
+		}
118
+	}
119
+}
0 120
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+/*
1
+Package admission contains admission control plugins that work with builds or
2
+pods that are running builds.
3
+
4
+Each admission control plugin has its own sub package. Common utilities live directly
5
+in this package.
6
+*/
7
+package admission
0 8
deleted file mode 100644
... ...
@@ -1,134 +0,0 @@
1
-package httpproxy
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"io/ioutil"
7
-	"reflect"
8
-
9
-	"k8s.io/kubernetes/pkg/admission"
10
-	kapi "k8s.io/kubernetes/pkg/api"
11
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
12
-
13
-	buildapi "github.com/openshift/origin/pkg/build/api"
14
-	configlatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
15
-)
16
-
17
-func init() {
18
-	admission.RegisterPlugin("BuildHTTPProxy", func(c kclient.Interface, config io.Reader) (admission.Interface, error) {
19
-		proxyConfig, err := readConfig(config)
20
-		if err != nil {
21
-			return nil, err
22
-		}
23
-
24
-		return NewBuildHTTPProxy(proxyConfig), nil
25
-	})
26
-}
27
-
28
-func readConfig(reader io.Reader) (*ProxyConfig, error) {
29
-	if reader == nil || reflect.ValueOf(reader).IsNil() {
30
-		return nil, nil
31
-	}
32
-
33
-	configBytes, err := ioutil.ReadAll(reader)
34
-	if err != nil {
35
-		return nil, err
36
-	}
37
-	config := &ProxyConfig{}
38
-	err = configlatest.ReadYAML(configBytes, config)
39
-	if err != nil {
40
-		return nil, err
41
-	}
42
-	return config, nil
43
-}
44
-
45
-type buildHTTPProxy struct {
46
-	*admission.Handler
47
-	proxyConfig *ProxyConfig
48
-}
49
-
50
-// NewBuildHTTPProxy returns an admission control for builds that checks
51
-// on policy based on the build strategy type
52
-func NewBuildHTTPProxy(proxyConfig *ProxyConfig) admission.Interface {
53
-	return &buildHTTPProxy{
54
-		Handler:     admission.NewHandler(admission.Create, admission.Update),
55
-		proxyConfig: proxyConfig,
56
-	}
57
-}
58
-
59
-func (a *buildHTTPProxy) Admit(attributes admission.Attributes) error {
60
-	if a.proxyConfig == nil {
61
-		return nil
62
-	}
63
-	if attributes.GetResource() != "buildconfigs" {
64
-		return nil
65
-	}
66
-	if len(attributes.GetSubresource()) != 0 {
67
-		return nil
68
-	}
69
-
70
-	buildConfig, ok := attributes.GetObject().(*buildapi.BuildConfig)
71
-	if !ok {
72
-		return admission.NewForbidden(attributes, fmt.Errorf("unrecognized request object %#v", attributes.GetObject()))
73
-	}
74
-
75
-	if len(a.proxyConfig.HTTPProxy) != 0 {
76
-		var envVars *[]kapi.EnvVar
77
-		switch {
78
-		case buildConfig.Spec.Strategy.DockerStrategy != nil:
79
-			envVars = &buildConfig.Spec.Strategy.DockerStrategy.Env
80
-		case buildConfig.Spec.Strategy.SourceStrategy != nil:
81
-			envVars = &buildConfig.Spec.Strategy.SourceStrategy.Env
82
-		case buildConfig.Spec.Strategy.CustomStrategy != nil:
83
-			envVars = &buildConfig.Spec.Strategy.CustomStrategy.Env
84
-		}
85
-
86
-		found := false
87
-		for i := range *envVars {
88
-			if (*envVars)[i].Name == "HTTP_PROXY" {
89
-				found = true
90
-			}
91
-		}
92
-		if !found {
93
-			*envVars = append(*envVars, kapi.EnvVar{Name: "HTTP_PROXY", Value: a.proxyConfig.HTTPProxy})
94
-		}
95
-
96
-		if buildConfig.Spec.Source.Git != nil {
97
-			if buildConfig.Spec.Source.Git.HTTPProxy == nil {
98
-				t := a.proxyConfig.HTTPProxy
99
-				buildConfig.Spec.Source.Git.HTTPProxy = &t
100
-			}
101
-		}
102
-	}
103
-
104
-	if len(a.proxyConfig.HTTPSProxy) != 0 {
105
-		var envVars *[]kapi.EnvVar
106
-		switch {
107
-		case buildConfig.Spec.Strategy.DockerStrategy != nil:
108
-			envVars = &buildConfig.Spec.Strategy.DockerStrategy.Env
109
-		case buildConfig.Spec.Strategy.SourceStrategy != nil:
110
-			envVars = &buildConfig.Spec.Strategy.SourceStrategy.Env
111
-		case buildConfig.Spec.Strategy.CustomStrategy != nil:
112
-			envVars = &buildConfig.Spec.Strategy.CustomStrategy.Env
113
-		}
114
-
115
-		found := false
116
-		for i := range *envVars {
117
-			if (*envVars)[i].Name == "HTTPS_PROXY" {
118
-				found = true
119
-			}
120
-		}
121
-		if !found {
122
-			*envVars = append(*envVars, kapi.EnvVar{Name: "HTTPS_PROXY", Value: a.proxyConfig.HTTPSProxy})
123
-		}
124
-
125
-		if buildConfig.Spec.Source.Git != nil {
126
-			if buildConfig.Spec.Source.Git.HTTPSProxy == nil {
127
-				t := a.proxyConfig.HTTPSProxy
128
-				buildConfig.Spec.Source.Git.HTTPSProxy = &t
129
-			}
130
-		}
131
-	}
132
-
133
-	return nil
134
-}
135 1
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-package httpproxy
2
-
3
-import (
4
-	"testing"
5
-
6
-	"k8s.io/kubernetes/pkg/admission"
7
-
8
-	buildapi "github.com/openshift/origin/pkg/build/api"
9
-)
10
-
11
-func TestSubstitution(t *testing.T) {
12
-	proxyConfig := &ProxyConfig{
13
-		HTTPProxy:  "http",
14
-		HTTPSProxy: "https",
15
-	}
16
-
17
-	admitter := NewBuildHTTPProxy(proxyConfig)
18
-
19
-	bc := &buildapi.BuildConfig{
20
-		Spec: buildapi.BuildConfigSpec{
21
-			BuildSpec: buildapi.BuildSpec{
22
-				Source: buildapi.BuildSource{
23
-					Git: &buildapi.GitBuildSource{},
24
-				},
25
-				Strategy: buildapi.BuildStrategy{
26
-					DockerStrategy: &buildapi.DockerBuildStrategy{},
27
-				},
28
-			},
29
-		},
30
-	}
31
-
32
-	attributes := admission.NewAttributesRecord(bc, "BuildConfig", "default", "name", "buildconfigs", "", admission.Create, nil)
33
-	err := admitter.Admit(attributes)
34
-	if err != nil {
35
-		t.Fatalf("unexpected error: %v", err)
36
-	}
37
-
38
-	foundHTTP := false
39
-	for _, envVar := range bc.Spec.Strategy.DockerStrategy.Env {
40
-		if envVar.Name == "HTTP_PROXY" && envVar.Value == "http" {
41
-			foundHTTP = true
42
-		}
43
-	}
44
-	if !foundHTTP {
45
-		t.Errorf("failed to find http proxy in %v", bc.Spec.Strategy.DockerStrategy.Env)
46
-	}
47
-
48
-	foundHTTPS := false
49
-	for _, envVar := range bc.Spec.Strategy.DockerStrategy.Env {
50
-		if envVar.Name == "HTTPS_PROXY" && envVar.Value == "https" {
51
-			foundHTTPS = true
52
-		}
53
-	}
54
-	if !foundHTTPS {
55
-		t.Errorf("failed to find https proxy in %v", bc.Spec.Strategy.DockerStrategy.Env)
56
-	}
57
-
58
-	if bc.Spec.Source.Git.HTTPProxy == nil || len(*bc.Spec.Source.Git.HTTPProxy) == 0 || *bc.Spec.Source.Git.HTTPProxy != "http" {
59
-		t.Errorf("failed to find http proxy in git source")
60
-	}
61
-	if bc.Spec.Source.Git.HTTPSProxy == nil || len(*bc.Spec.Source.Git.HTTPSProxy) == 0 || *bc.Spec.Source.Git.HTTPSProxy != "https" {
62
-		t.Errorf("failed to find http proxy in git source")
63
-	}
64
-
65
-}
66 1
deleted file mode 100644
... ...
@@ -1,5 +0,0 @@
1
-package latest
2
-
3
-import (
4
-	_ "github.com/openshift/origin/pkg/build/admission/httpproxy/v1"
5
-)
6 1
deleted file mode 100644
... ...
@@ -1,14 +0,0 @@
1
-package httpproxy
2
-
3
-import (
4
-	_ "github.com/openshift/origin/pkg/build/admission/httpproxy/latest"
5
-	"github.com/openshift/origin/pkg/cmd/server/api"
6
-)
7
-
8
-func init() {
9
-	api.Scheme.AddKnownTypes("",
10
-		&ProxyConfig{},
11
-	)
12
-}
13
-
14
-func (*ProxyConfig) IsAnAPIObject() {}
15 1
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-package httpproxy
2
-
3
-import (
4
-	"k8s.io/kubernetes/pkg/api/unversioned"
5
-)
6
-
7
-// ProxyConfig controls the proxy information for BuildConfigs
8
-type ProxyConfig struct {
9
-	unversioned.TypeMeta
10
-
11
-	// HTTPProxy is the location of the HTTPProxy
12
-	HTTPProxy string
13
-
14
-	// HTTPSProxy is the location of the HTTPSProxy
15
-	HTTPSProxy string
16
-}
17 1
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-package v1
2
-
3
-import (
4
-	"github.com/openshift/origin/pkg/cmd/server/api"
5
-)
6
-
7
-func init() {
8
-	api.Scheme.AddKnownTypes("v1",
9
-		&ProxyConfig{},
10
-	)
11
-}
12
-
13
-func (*ProxyConfig) IsAnAPIObject() {}
14 1
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-package v1
2
-
3
-import (
4
-	"k8s.io/kubernetes/pkg/api/unversioned"
5
-)
6
-
7
-// ProxyConfig controls the proxy information for BuildConfigs
8
-type ProxyConfig struct {
9
-	unversioned.TypeMeta
10
-
11
-	// HTTPProxy is the location of the HTTPProxy
12
-	HTTPProxy string `json:"httpProxy",description:"location of the global http proxy"`
13
-
14
-	// HTTPSProxy is the location of the HTTPSProxy
15
-	HTTPSProxy string `json:"httpsProxy",description:"location of the global https proxy"`
16
-}
17 1
new file mode 100644
... ...
@@ -0,0 +1,98 @@
0
+package overrides
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/golang/glog"
6
+	"k8s.io/kubernetes/pkg/admission"
7
+	kapi "k8s.io/kubernetes/pkg/api"
8
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
9
+
10
+	buildadmission "github.com/openshift/origin/pkg/build/admission"
11
+)
12
+
13
+func init() {
14
+	admission.RegisterPlugin("BuildOverrides", func(c kclient.Interface, config io.Reader) (admission.Interface, error) {
15
+		overridesConfig, err := getConfig(config)
16
+		if err != nil {
17
+			return nil, err
18
+		}
19
+
20
+		glog.V(4).Infof("Initializing BuildOverrides plugin with config: %#v", overridesConfig)
21
+		return NewBuildOverrides(overridesConfig), nil
22
+	})
23
+}
24
+
25
+func getConfig(in io.Reader) (*BuildOverridesConfig, error) {
26
+	overridesConfig := &BuildOverridesConfig{}
27
+	err := buildadmission.ReadPluginConfig(in, overridesConfig)
28
+	if err != nil {
29
+		return nil, err
30
+	}
31
+	return overridesConfig, nil
32
+}
33
+
34
+type buildOverrides struct {
35
+	*admission.Handler
36
+	overridesConfig *BuildOverridesConfig
37
+}
38
+
39
+// NewBuildOverrides returns an admission control for builds that overrides
40
+// settings on builds
41
+func NewBuildOverrides(overridesConfig *BuildOverridesConfig) admission.Interface {
42
+	return &buildOverrides{
43
+		Handler:         admission.NewHandler(admission.Create, admission.Update),
44
+		overridesConfig: overridesConfig,
45
+	}
46
+}
47
+
48
+// Admit appplies configured overrides to a build in a build pod
49
+func (a *buildOverrides) Admit(attributes admission.Attributes) error {
50
+	if a.overridesConfig == nil {
51
+		return nil
52
+	}
53
+	if !buildadmission.IsBuildPod(attributes) {
54
+		return nil
55
+	}
56
+	return a.applyOverrides(attributes)
57
+}
58
+
59
+func (a *buildOverrides) applyOverrides(attributes admission.Attributes) error {
60
+	if !a.overridesConfig.ForcePull {
61
+		return nil
62
+	}
63
+	build, version, err := buildadmission.GetBuild(attributes)
64
+	if err != nil {
65
+		return err
66
+	}
67
+	glog.V(4).Infof("Handling build %s/%s", build.Namespace, build.Name)
68
+	if build.Spec.Strategy.DockerStrategy != nil {
69
+		glog.V(5).Infof("Setting docker strategy ForcePull to true in build %s/%s", build.Namespace, build.Name)
70
+		build.Spec.Strategy.DockerStrategy.ForcePull = true
71
+	}
72
+	if build.Spec.Strategy.SourceStrategy != nil {
73
+		glog.V(5).Infof("Setting source strategy ForcePull to true in build %s/%s", build.Namespace, build.Name)
74
+		build.Spec.Strategy.SourceStrategy.ForcePull = true
75
+	}
76
+	if build.Spec.Strategy.CustomStrategy != nil {
77
+		err := applyForcePullToPod(attributes)
78
+		if err != nil {
79
+			return err
80
+		}
81
+		glog.V(5).Infof("Setting custom strategy ForcePull to true in build %s/%s", build.Namespace, build.Name)
82
+		build.Spec.Strategy.CustomStrategy.ForcePull = true
83
+	}
84
+	return buildadmission.SetBuild(attributes, build, version)
85
+}
86
+
87
+func applyForcePullToPod(attributes admission.Attributes) error {
88
+	pod, err := buildadmission.GetPod(attributes)
89
+	if err != nil {
90
+		return err
91
+	}
92
+	for i := range pod.Spec.Containers {
93
+		glog.V(5).Infof("Setting ImagePullPolicy to PullAlways on container %s of pod %s/%s", pod.Spec.Containers[i].Name, pod.Namespace, pod.Name)
94
+		pod.Spec.Containers[i].ImagePullPolicy = kapi.PullAlways
95
+	}
96
+	return nil
97
+}
0 98
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+package overrides
1
+
2
+import (
3
+	"testing"
4
+
5
+	"k8s.io/kubernetes/pkg/admission"
6
+	kapi "k8s.io/kubernetes/pkg/api"
7
+
8
+	u "github.com/openshift/origin/pkg/build/admission/testutil"
9
+	buildapi "github.com/openshift/origin/pkg/build/api"
10
+)
11
+
12
+func TestBuildOverrideForcePull(t *testing.T) {
13
+	tests := []struct {
14
+		name  string
15
+		build *buildapi.Build
16
+	}{
17
+		{
18
+			name:  "build - custom",
19
+			build: u.Build().WithCustomStrategy().AsBuild(),
20
+		},
21
+		{
22
+			name:  "build - docker",
23
+			build: u.Build().WithDockerStrategy().AsBuild(),
24
+		},
25
+		{
26
+			name:  "build - source",
27
+			build: u.Build().WithSourceStrategy().AsBuild(),
28
+		},
29
+	}
30
+
31
+	ops := []admission.Operation{admission.Create, admission.Update}
32
+	for _, test := range tests {
33
+		for _, op := range ops {
34
+			overrides := NewBuildOverrides(&BuildOverridesConfig{ForcePull: true})
35
+			pod := u.Pod().WithBuild(t, test.build, "v1")
36
+			err := overrides.Admit(pod.ToAttributes())
37
+			if err != nil {
38
+				t.Errorf("%s: unexpected error: %v", test.name, err)
39
+			}
40
+			build := pod.GetBuild(t)
41
+			strategy := build.Spec.Strategy
42
+			switch {
43
+			case strategy.CustomStrategy != nil:
44
+				if strategy.CustomStrategy.ForcePull == false {
45
+					t.Errorf("%s (%s): force pull was false", test.name, op)
46
+				}
47
+				if pod.Spec.Containers[0].ImagePullPolicy != kapi.PullAlways {
48
+					t.Errorf("%s (%s): image pull policy is not PullAlways", test.name, op)
49
+				}
50
+			case strategy.DockerStrategy != nil:
51
+				if strategy.DockerStrategy.ForcePull == false {
52
+					t.Errorf("%s (%s): force pull was false", test.name, op)
53
+				}
54
+			case strategy.SourceStrategy != nil:
55
+				if strategy.SourceStrategy.ForcePull == false {
56
+					t.Errorf("%s (%s): force pull was false", test.name, op)
57
+				}
58
+			}
59
+		}
60
+	}
61
+}
0 62
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+/*
1
+Package overrides contains the BuildOverrides admission control plugin.
2
+
3
+The plugin allows overriding settings on builds via the build pod.
4
+
5
+Configuration
6
+
7
+Configuration is done via a BuildOverridesConfig object:
8
+
9
+ apiVersion: v1
10
+ kind: BuildOverridesConfig
11
+ forcePull: true
12
+*/
13
+package overrides
0 14
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+package latest
1
+
2
+import (
3
+	_ "github.com/openshift/origin/pkg/build/admission/overrides/v1"
4
+)
0 5
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package overrides
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+
5
+	_ "github.com/openshift/origin/pkg/build/admission/overrides/latest"
6
+	"github.com/openshift/origin/pkg/cmd/server/api"
7
+)
8
+
9
+// SchemeGroupVersion is group version used to register these objects
10
+var SchemeGroupVersion = unversioned.GroupVersion{Group: "", Version: ""}
11
+
12
+// Kind takes an unqualified kind and returns back a Group qualified GroupKind
13
+func Kind(kind string) unversioned.GroupKind {
14
+	return SchemeGroupVersion.WithKind(kind).GroupKind()
15
+}
16
+
17
+// Resource takes an unqualified resource and returns back a Group qualified GroupResource
18
+func Resource(resource string) unversioned.GroupResource {
19
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
20
+}
21
+
22
+func init() {
23
+	api.Scheme.AddKnownTypes(SchemeGroupVersion,
24
+		&BuildOverridesConfig{},
25
+	)
26
+}
27
+
28
+func (*BuildOverridesConfig) IsAnAPIObject() {}
0 29
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package overrides
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+)
5
+
6
+// BuildOverridesConfig controls override settings for builds
7
+type BuildOverridesConfig struct {
8
+	unversioned.TypeMeta
9
+
10
+	// ForcePull indicates whether the build strategy should always be set to ForcePull=true
11
+	ForcePull bool
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package v1
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+
5
+	"github.com/openshift/origin/pkg/cmd/server/api"
6
+)
7
+
8
+// SchemeGroupVersion is group version used to register these objects
9
+var SchemeGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"}
10
+
11
+func init() {
12
+	api.Scheme.AddKnownTypes(SchemeGroupVersion,
13
+		&BuildOverridesConfig{},
14
+	)
15
+}
16
+
17
+func (*BuildOverridesConfig) IsAnAPIObject() {}
0 18
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package v1
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/unversioned"
4
+)
5
+
6
+// BuildOverridesConfig controls override settings for builds
7
+type BuildOverridesConfig struct {
8
+	unversioned.TypeMeta
9
+
10
+	// ForcePull indicates whether the build strategy should always be set to ForcePull=true
11
+	ForcePull bool `json:"forcePull",description:"if true, will always set ForcePull to true on builds"`
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package test
1
+
2
+import (
3
+	buildapi "github.com/openshift/origin/pkg/build/api"
4
+)
5
+
6
+type TestBuild buildapi.Build
7
+
8
+func Build() *TestBuild {
9
+	b := (*TestBuild)(&buildapi.Build{})
10
+	b.Name = "TestBuild"
11
+	b.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
12
+	b.Spec.Source.Git = &buildapi.GitBuildSource{
13
+		URI: "http://test.build/source",
14
+	}
15
+	return b
16
+}
17
+
18
+func (b *TestBuild) WithDockerStrategy() *TestBuild {
19
+	b.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
20
+	return b
21
+}
22
+
23
+func (b *TestBuild) WithSourceStrategy() *TestBuild {
24
+	strategy := &buildapi.SourceBuildStrategy{}
25
+	strategy.From.Name = "builder/image"
26
+	strategy.From.Kind = "DockerImage"
27
+	b.Spec.Strategy.SourceStrategy = strategy
28
+	return b
29
+}
30
+
31
+func (b *TestBuild) WithCustomStrategy() *TestBuild {
32
+	strategy := &buildapi.CustomBuildStrategy{}
33
+	strategy.From.Name = "builder/image"
34
+	strategy.From.Kind = "DockerImage"
35
+	b.Spec.Strategy.CustomStrategy = strategy
36
+	return b
37
+}
38
+
39
+func (b *TestBuild) AsBuild() *buildapi.Build {
40
+	return (*buildapi.Build)(b)
41
+}
0 42
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+/*
1
+Package testutil contains utilities to test admission control plugins.
2
+
3
+It contains a TestPod and TestBuild class that allow quickly building mock objects
4
+for tests
5
+*/
6
+
7
+package test
0 8
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+package test
1
+
2
+import (
3
+	"testing"
4
+
5
+	"k8s.io/kubernetes/pkg/admission"
6
+	kapi "k8s.io/kubernetes/pkg/api"
7
+
8
+	buildapi "github.com/openshift/origin/pkg/build/api"
9
+)
10
+
11
+type TestPod kapi.Pod
12
+
13
+func Pod() *TestPod {
14
+	return (*TestPod)(&kapi.Pod{})
15
+}
16
+
17
+func (p *TestPod) WithAnnotation(name, value string) *TestPod {
18
+	if p.Annotations == nil {
19
+		p.Annotations = map[string]string{}
20
+	}
21
+	p.Annotations[name] = value
22
+	return p
23
+}
24
+
25
+func (p *TestPod) WithEnvVar(name, value string) *TestPod {
26
+	if len(p.Spec.Containers) == 0 {
27
+		p.Spec.Containers = append(p.Spec.Containers, kapi.Container{})
28
+	}
29
+	p.Spec.Containers[0].Env = append(p.Spec.Containers[0].Env, kapi.EnvVar{Name: name, Value: value})
30
+	return p
31
+}
32
+
33
+func (p *TestPod) WithBuild(t *testing.T, build *buildapi.Build, version string) *TestPod {
34
+	encodedBuild, err := kapi.Scheme.EncodeToVersion(build, version)
35
+	if err != nil {
36
+		t.Fatalf("%v", err)
37
+	}
38
+	return p.WithAnnotation(buildapi.BuildAnnotation, build.Name).WithEnvVar("BUILD", string(encodedBuild))
39
+}
40
+
41
+func (p *TestPod) EnvValue(name string) string {
42
+	if len(p.Spec.Containers) == 0 {
43
+		return ""
44
+	}
45
+	for _, ev := range p.Spec.Containers[0].Env {
46
+		if ev.Name == name {
47
+			return ev.Value
48
+		}
49
+	}
50
+	return ""
51
+}
52
+
53
+func (p *TestPod) GetBuild(t *testing.T) *buildapi.Build {
54
+	obj, err := kapi.Scheme.Decode([]byte(p.EnvValue("BUILD")))
55
+	if err != nil {
56
+		t.Fatalf("Could not decode build: %v", err)
57
+	}
58
+	build, ok := obj.(*buildapi.Build)
59
+	if !ok {
60
+		t.Fatalf("Not a build object: %#v", obj)
61
+	}
62
+	return build
63
+}
64
+
65
+func (p *TestPod) ToAttributes() admission.Attributes {
66
+	return admission.NewAttributesRecord((*kapi.Pod)(p),
67
+		kapi.Kind("Pod"),
68
+		"default",
69
+		"TestPod",
70
+		kapi.Resource("pods"),
71
+		"",
72
+		admission.Create,
73
+		nil)
74
+}
75
+
76
+func (p *TestPod) AsPod() *kapi.Pod {
77
+	return (*kapi.Pod)(p)
78
+}
... ...
@@ -213,13 +213,13 @@ func validateGitSource(git *buildapi.GitBuildSource, fldPath *field.Path) field.
213 213
 	allErrs := field.ErrorList{}
214 214
 	if len(git.URI) == 0 {
215 215
 		allErrs = append(allErrs, field.Required(fldPath.Child("uri")))
216
-	} else if !isValidURL(git.URI) {
216
+	} else if !IsValidURL(git.URI) {
217 217
 		allErrs = append(allErrs, field.Invalid(fldPath.Child("uri"), git.URI, "uri is not a valid url"))
218 218
 	}
219
-	if git.HTTPProxy != nil && len(*git.HTTPProxy) != 0 && !isValidURL(*git.HTTPProxy) {
219
+	if git.HTTPProxy != nil && len(*git.HTTPProxy) != 0 && !IsValidURL(*git.HTTPProxy) {
220 220
 		allErrs = append(allErrs, field.Invalid(fldPath.Child("httpproxy"), *git.HTTPProxy, "proxy is not a valid url"))
221 221
 	}
222
-	if git.HTTPSProxy != nil && len(*git.HTTPSProxy) != 0 && !isValidURL(*git.HTTPSProxy) {
222
+	if git.HTTPSProxy != nil && len(*git.HTTPSProxy) != 0 && !IsValidURL(*git.HTTPSProxy) {
223 223
 		allErrs = append(allErrs, field.Invalid(fldPath.Child("httpsproxy"), *git.HTTPSProxy, "proxy is not a valid url"))
224 224
 	}
225 225
 	if hasProxy(git) && !isHTTPScheme(git.URI) {
... ...
@@ -432,6 +432,8 @@ func validateDockerStrategy(strategy *buildapi.DockerBuildStrategy, fldPath *fie
432 432
 		}
433 433
 	}
434 434
 
435
+	allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...)
436
+
435 437
 	return allErrs
436 438
 }
437 439
 
... ...
@@ -439,6 +441,7 @@ func validateSourceStrategy(strategy *buildapi.SourceBuildStrategy, fldPath *fie
439 439
 	allErrs := field.ErrorList{}
440 440
 	allErrs = append(allErrs, validateFromImageReference(&strategy.From, fldPath.Child("from"))...)
441 441
 	allErrs = append(allErrs, validateSecretRef(strategy.PullSecret, fldPath.Child("pullSecret"))...)
442
+	allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...)
442 443
 	return allErrs
443 444
 }
444 445
 
... ...
@@ -446,6 +449,7 @@ func validateCustomStrategy(strategy *buildapi.CustomBuildStrategy, fldPath *fie
446 446
 	allErrs := field.ErrorList{}
447 447
 	allErrs = append(allErrs, validateFromImageReference(&strategy.From, fldPath.Child("from"))...)
448 448
 	allErrs = append(allErrs, validateSecretRef(strategy.PullSecret, fldPath.Child("pullSecret"))...)
449
+	allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...)
449 450
 	return allErrs
450 451
 }
451 452
 
... ...
@@ -503,7 +507,7 @@ func validateWebHook(webHook *buildapi.WebHookTrigger, fldPath *field.Path) fiel
503 503
 	return allErrs
504 504
 }
505 505
 
506
-func isValidURL(uri string) bool {
506
+func IsValidURL(uri string) bool {
507 507
 	_, err := url.Parse(uri)
508 508
 	return err == nil
509 509
 }
... ...
@@ -525,3 +529,22 @@ func ValidateBuildLogOptions(opts *buildapi.BuildLogOptions) field.ErrorList {
525 525
 	}
526 526
 	return allErrs
527 527
 }
528
+
529
+const cIdentifierErrorMsg string = `must be a C identifier (matching regex ` + kvalidation.CIdentifierFmt + `): e.g. "my_name" or "MyName"`
530
+
531
+func ValidateStrategyEnv(vars []kapi.EnvVar, fldPath *field.Path) field.ErrorList {
532
+	allErrs := field.ErrorList{}
533
+
534
+	for i, ev := range vars {
535
+		idxPath := fldPath.Index(i)
536
+		if len(ev.Name) == 0 {
537
+			allErrs = append(allErrs, field.Required(idxPath.Child("name")))
538
+		} else if !kvalidation.IsCIdentifier(ev.Name) {
539
+			allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, cIdentifierErrorMsg))
540
+		}
541
+		if ev.ValueFrom != nil {
542
+			allErrs = append(allErrs, field.Invalid(idxPath.Child("valueFrom"), ev.ValueFrom, "valueFrom is not supported in build strategy environment variables"))
543
+		}
544
+	}
545
+	return allErrs
546
+}
... ...
@@ -207,6 +207,7 @@ func TestValidateBuildUpdate(t *testing.T) {
207 207
 }
208 208
 
209 209
 func TestBuildConfigGitSourceWithProxyFailure(t *testing.T) {
210
+	proxyAddress := "127.0.0.1:3128"
210 211
 	buildConfig := &buildapi.BuildConfig{
211 212
 		ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
212 213
 		Spec: buildapi.BuildConfigSpec{
... ...
@@ -214,8 +215,8 @@ func TestBuildConfigGitSourceWithProxyFailure(t *testing.T) {
214 214
 				Source: buildapi.BuildSource{
215 215
 					Git: &buildapi.GitBuildSource{
216 216
 						URI:        "git://github.com/my/repository",
217
-						HTTPProxy:  "127.0.0.1:3128",
218
-						HTTPSProxy: "127.0.0.1:3128",
217
+						HTTPProxy:  &proxyAddress,
218
+						HTTPSProxy: &proxyAddress,
219 219
 					},
220 220
 				},
221 221
 				Strategy: buildapi.BuildStrategy{
... ...
@@ -624,6 +625,7 @@ func TestValidateBuildRequest(t *testing.T) {
624 624
 
625 625
 func TestValidateSource(t *testing.T) {
626 626
 	dockerfile := "FROM something"
627
+	invalidProxyAddress := "some!@#$%^&*()url"
627 628
 	errorCases := []struct {
628 629
 		t        field.ErrorType
629 630
 		path     string
... ...
@@ -757,7 +759,7 @@ func TestValidateSource(t *testing.T) {
757 757
 			source: &buildapi.BuildSource{
758 758
 				Git: &buildapi.GitBuildSource{
759 759
 					URI:       "https://example.com/repo.git",
760
-					HTTPProxy: "some!@#$%^&*()url",
760
+					HTTPProxy: &invalidProxyAddress,
761 761
 				},
762 762
 				ContextDir: "contextDir",
763 763
 			},
... ...
@@ -769,7 +771,7 @@ func TestValidateSource(t *testing.T) {
769 769
 			source: &buildapi.BuildSource{
770 770
 				Git: &buildapi.GitBuildSource{
771 771
 					URI:        "https://example.com/repo.git",
772
-					HTTPSProxy: "some!@#$%^&*()url",
772
+					HTTPSProxy: &invalidProxyAddress,
773 773
 				},
774 774
 				ContextDir: "contextDir",
775 775
 			},
... ...
@@ -1727,3 +1729,84 @@ func TestValidateToImageReference(t *testing.T) {
1727 1727
 		t.Errorf("Error on wrong field, expected %s, got %s", "namespace", err.Field)
1728 1728
 	}
1729 1729
 }
1730
+
1731
+func TestValidateStrategyEnvVars(t *testing.T) {
1732
+	tests := []struct {
1733
+		env         []kapi.EnvVar
1734
+		errExpected bool
1735
+		errField    string
1736
+		errType     field.ErrorType
1737
+	}{
1738
+		// 0: missing Env variable name
1739
+		{
1740
+			env: []kapi.EnvVar{
1741
+				{
1742
+					Name:  "",
1743
+					Value: "test",
1744
+				},
1745
+			},
1746
+			errExpected: true,
1747
+			errField:    "env[0].name",
1748
+			errType:     field.ErrorTypeRequired,
1749
+		},
1750
+		// 1: invalid Env variable name
1751
+		{
1752
+			env: []kapi.EnvVar{
1753
+				{
1754
+					Name:  " invalid,name",
1755
+					Value: "test",
1756
+				},
1757
+			},
1758
+			errExpected: true,
1759
+			errField:    "env[0].name",
1760
+			errType:     field.ErrorTypeInvalid,
1761
+		},
1762
+		// 2: valueFrom present in env var
1763
+		{
1764
+			env: []kapi.EnvVar{
1765
+				{
1766
+					Name:      "name",
1767
+					Value:     "test",
1768
+					ValueFrom: &kapi.EnvVarSource{},
1769
+				},
1770
+			},
1771
+			errExpected: true,
1772
+			errField:    "env[0].valueFrom",
1773
+			errType:     field.ErrorTypeInvalid,
1774
+		},
1775
+		// 3: valid env
1776
+		{
1777
+			env: []kapi.EnvVar{
1778
+				{
1779
+					Name:  "VAR1",
1780
+					Value: "value1",
1781
+				},
1782
+				{
1783
+					Name:  "VAR2",
1784
+					Value: "value2",
1785
+				},
1786
+			},
1787
+		},
1788
+	}
1789
+
1790
+	for i, tc := range tests {
1791
+		errs := ValidateStrategyEnv(tc.env, field.NewPath("env"))
1792
+		if !tc.errExpected {
1793
+			if len(errs) > 0 {
1794
+				t.Errorf("%d: unexpected error: %v", i, errs.ToAggregate())
1795
+			}
1796
+			continue
1797
+		}
1798
+		if tc.errExpected && len(errs) == 0 {
1799
+			t.Errorf("%d: expected error. Got none.", i)
1800
+			continue
1801
+		}
1802
+		err := errs[0]
1803
+		if err.Field != tc.errField {
1804
+			t.Errorf("%d: unexpected error field: %s", i, err.Field)
1805
+		}
1806
+		if err.Type != tc.errType {
1807
+			t.Errorf("%d: unexpected error type: %s", i, err.Type)
1808
+		}
1809
+	}
1810
+}
... ...
@@ -41,7 +41,7 @@ import (
41 41
 )
42 42
 
43 43
 // AdmissionPlugins is the full list of admission control plugins to enable in the order they must run
44
-var AdmissionPlugins = []string{"NamespaceLifecycle", "OriginPodNodeEnvironment", "LimitRanger", "ServiceAccount", "SecurityContextConstraint", "ResourceQuota", "SCCExecRestrictions"}
44
+var AdmissionPlugins = []string{"NamespaceLifecycle", "OriginPodNodeEnvironment", "LimitRanger", "ServiceAccount", "SecurityContextConstraint", "BuildDefaults", "BuildOverrides", "ResourceQuota", "SCCExecRestrictions"}
45 45
 
46 46
 // MasterConfig defines the required values to start a Kubernetes master
47 47
 type MasterConfig struct {
... ...
@@ -169,7 +169,7 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
169 169
 	kubeletClientConfig := configapi.GetKubeletClientConfig(options)
170 170
 
171 171
 	// in-order list of plug-ins that should intercept admission decisions (origin only intercepts)
172
-	admissionControlPluginNames := []string{"OriginNamespaceLifecycle", "BuildByStrategy", "BuildHTTPProxy"}
172
+	admissionControlPluginNames := []string{"OriginNamespaceLifecycle", "BuildByStrategy"}
173 173
 	if len(options.AdmissionConfig.PluginOrderOverride) > 0 {
174 174
 		admissionControlPluginNames = options.AdmissionConfig.PluginOrderOverride
175 175
 	}
... ...
@@ -22,7 +22,8 @@ var admissionPluginsNotUsedByKube = sets.NewString(
22 22
 	"DenyEscalatingExec",     // from kube, it denies exec to pods that have certain privileges.  This is superceded in origin by SCCExecRestrictions that checks against SCC rules.
23 23
 
24 24
 	"BuildByStrategy",          // from origin, only needed for managing builds, not kubernetes resources
25
-	"BuildHTTPProxy",           // from origin, only needed for managing builds, not kubernetes resources
25
+	"BuildDefaults",            // from origin, only needed for managing builds, not kubernetes resources
26
+	"BuildOverrides",           // from origin, only needed for managing builds, not kubernetes resources
26 27
 	"OriginNamespaceLifecycle", // from origin, only needed for rejecting openshift resources, so not needed by kube
27 28
 	"ProjectRequestLimit",      // from origin, used for limiting project requests by user (online use case)
28 29
 
... ...
@@ -3,7 +3,8 @@ package start
3 3
 import (
4 4
 
5 5
 	// Admission control plug-ins used by OpenShift
6
-	_ "github.com/openshift/origin/pkg/build/admission/httpproxy"
6
+	_ "github.com/openshift/origin/pkg/build/admission/defaults"
7
+	_ "github.com/openshift/origin/pkg/build/admission/overrides"
7 8
 	_ "github.com/openshift/origin/pkg/build/admission/strategyrestrictions"
8 9
 	_ "github.com/openshift/origin/pkg/project/admission/lifecycle"
9 10
 	_ "github.com/openshift/origin/pkg/project/admission/nodeenv"
10 11
new file mode 100644
... ...
@@ -0,0 +1,214 @@
0
+// +build integration,etcd
1
+
2
+package integration
3
+
4
+import (
5
+	"reflect"
6
+	"testing"
7
+	"time"
8
+
9
+	kapi "k8s.io/kubernetes/pkg/api"
10
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
11
+	"k8s.io/kubernetes/pkg/fields"
12
+	"k8s.io/kubernetes/pkg/runtime"
13
+	watchapi "k8s.io/kubernetes/pkg/watch"
14
+
15
+	"github.com/openshift/origin/pkg/build/admission/defaults"
16
+	"github.com/openshift/origin/pkg/build/admission/overrides"
17
+	buildtestutil "github.com/openshift/origin/pkg/build/admission/testutil"
18
+	buildapi "github.com/openshift/origin/pkg/build/api"
19
+	buildutil "github.com/openshift/origin/pkg/build/util"
20
+	"github.com/openshift/origin/pkg/client"
21
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
22
+	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
23
+	testutil "github.com/openshift/origin/test/util"
24
+	testserver "github.com/openshift/origin/test/util/server"
25
+)
26
+
27
+var buildPodAdmissionTestTimeout time.Duration = 10 * time.Second
28
+
29
+func TestBuildDefaultGitHTTPProxy(t *testing.T) {
30
+	httpProxy := "http://my.test.proxy:12345"
31
+	oclient, kclient := setupBuildDefaultsAdmissionTest(t, &defaults.BuildDefaultsConfig{
32
+		GitHTTPProxy: httpProxy,
33
+	})
34
+	build, _ := runBuildPodAdmissionTest(t, oclient, kclient, buildPodAdmissionTestDockerBuild())
35
+	if actual := build.Spec.Source.Git.HTTPProxy; actual == nil || *actual != httpProxy {
36
+		t.Errorf("Resulting build did not get expected HTTP proxy: %v", actual)
37
+	}
38
+}
39
+
40
+func TestBuildDefaultGitHTTPSProxy(t *testing.T) {
41
+	httpsProxy := "https://my.test.proxy:12345"
42
+	oclient, kclient := setupBuildDefaultsAdmissionTest(t, &defaults.BuildDefaultsConfig{
43
+		GitHTTPSProxy: httpsProxy,
44
+	})
45
+	build, _ := runBuildPodAdmissionTest(t, oclient, kclient, buildPodAdmissionTestDockerBuild())
46
+	if actual := build.Spec.Source.Git.HTTPSProxy; actual == nil || *actual != httpsProxy {
47
+		t.Errorf("Resulting build did not get expected HTTPS proxy: %v", actual)
48
+	}
49
+}
50
+
51
+func TestBuildDefaultEnvironment(t *testing.T) {
52
+	env := []kapi.EnvVar{
53
+		{
54
+			Name:  "VAR1",
55
+			Value: "VALUE1",
56
+		},
57
+		{
58
+			Name:  "VAR2",
59
+			Value: "VALUE2",
60
+		},
61
+	}
62
+	oclient, kclient := setupBuildDefaultsAdmissionTest(t, &defaults.BuildDefaultsConfig{
63
+		Env: env,
64
+	})
65
+	build, _ := runBuildPodAdmissionTest(t, oclient, kclient, buildPodAdmissionTestDockerBuild())
66
+	if actual := build.Spec.Strategy.DockerStrategy.Env; !reflect.DeepEqual(env, actual) {
67
+		t.Errorf("Resulting build did not get expected environment: %v", actual)
68
+	}
69
+}
70
+
71
+func TestBuildOverrideForcePull(t *testing.T) {
72
+	oclient, kclient := setupBuildOverridesAdmissionTest(t, &overrides.BuildOverridesConfig{
73
+		ForcePull: true,
74
+	})
75
+	build, _ := runBuildPodAdmissionTest(t, oclient, kclient, buildPodAdmissionTestDockerBuild())
76
+	if !build.Spec.Strategy.DockerStrategy.ForcePull {
77
+		t.Errorf("ForcePull was not set on resulting build")
78
+	}
79
+}
80
+
81
+func TestBuildOverrideForcePullCustomStrategy(t *testing.T) {
82
+	oclient, kclient := setupBuildOverridesAdmissionTest(t, &overrides.BuildOverridesConfig{
83
+		ForcePull: true,
84
+	})
85
+	build, pod := runBuildPodAdmissionTest(t, oclient, kclient, buildPodAdmissionTestCustomBuild())
86
+	if pod.Spec.Containers[0].ImagePullPolicy != kapi.PullAlways {
87
+		t.Errorf("Pod ImagePullPolicy is not PullAlways")
88
+	}
89
+	if !build.Spec.Strategy.CustomStrategy.ForcePull {
90
+		t.Errorf("ForcePull was not set on resulting build")
91
+	}
92
+}
93
+
94
+func buildPodAdmissionTestCustomBuild() *buildapi.Build {
95
+	build := &buildapi.Build{}
96
+	build.Name = "test-custom-build"
97
+	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
98
+	build.Spec.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{}
99
+	build.Spec.Strategy.CustomStrategy.From.Kind = "DockerImage"
100
+	build.Spec.Strategy.CustomStrategy.From.Name = "test/image"
101
+	return build
102
+}
103
+
104
+func buildPodAdmissionTestDockerBuild() *buildapi.Build {
105
+	build := &buildapi.Build{}
106
+	build.Name = "test-build"
107
+	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
108
+	build.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
109
+	return build
110
+}
111
+
112
+func runBuildPodAdmissionTest(t *testing.T, client *client.Client, kclient *kclient.Client, build *buildapi.Build) (*buildapi.Build, *kapi.Pod) {
113
+
114
+	ns := testutil.Namespace()
115
+	_, err := client.Builds(ns).Create(build)
116
+	if err != nil {
117
+		t.Fatalf("%v", err)
118
+	}
119
+
120
+	watchOpt := kapi.ListOptions{
121
+		FieldSelector: fields.OneTermEqualSelector(
122
+			"metadata.name",
123
+			buildutil.GetBuildPodName(build),
124
+		),
125
+	}
126
+	podWatch, err := kclient.Pods(ns).Watch(watchOpt)
127
+	if err != nil {
128
+		t.Fatalf("%v", err)
129
+	}
130
+	type resultObjs struct {
131
+		build *buildapi.Build
132
+		pod   *kapi.Pod
133
+	}
134
+	result := make(chan resultObjs)
135
+	defer podWatch.Stop()
136
+	go func() {
137
+		for e := range podWatch.ResultChan() {
138
+			if e.Type == watchapi.Added {
139
+				pod, ok := e.Object.(*kapi.Pod)
140
+				if !ok {
141
+					t.Fatalf("unexpected object: %v", e.Object)
142
+				}
143
+				build := (*buildtestutil.TestPod)(pod).GetBuild(t)
144
+				result <- resultObjs{build: build, pod: pod}
145
+			}
146
+		}
147
+	}()
148
+
149
+	select {
150
+	case <-time.After(buildPodAdmissionTestTimeout):
151
+		t.Fatalf("timed out after %v", buildPodAdmissionTestTimeout)
152
+	case objs := <-result:
153
+		return objs.build, objs.pod
154
+	}
155
+	return nil, nil
156
+}
157
+
158
+func setupBuildDefaultsAdmissionTest(t *testing.T, defaultsConfig *defaults.BuildDefaultsConfig) (*client.Client, *kclient.Client) {
159
+	return setupBuildPodAdmissionTest(t, map[string]configapi.AdmissionPluginConfig{
160
+		"BuildDefaults": {
161
+			Configuration: runtime.EmbeddedObject{Object: defaultsConfig},
162
+		},
163
+	})
164
+}
165
+
166
+func setupBuildOverridesAdmissionTest(t *testing.T, overridesConfig *overrides.BuildOverridesConfig) (*client.Client, *kclient.Client) {
167
+	return setupBuildPodAdmissionTest(t, map[string]configapi.AdmissionPluginConfig{
168
+		"BuildOverrides": {
169
+			Configuration: runtime.EmbeddedObject{Object: overridesConfig},
170
+		},
171
+	})
172
+}
173
+
174
+func setupBuildPodAdmissionTest(t *testing.T, pluginConfig map[string]configapi.AdmissionPluginConfig) (*client.Client, *kclient.Client) {
175
+	master, err := testserver.DefaultMasterOptions()
176
+	if err != nil {
177
+		t.Fatalf("%v", err)
178
+	}
179
+	master.KubernetesMasterConfig.AdmissionConfig.PluginConfig = pluginConfig
180
+	clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(master)
181
+	if err != nil {
182
+		t.Fatalf("%v", err)
183
+	}
184
+	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
185
+	if err != nil {
186
+		t.Fatalf("%v", err)
187
+	}
188
+
189
+	clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
190
+	if err != nil {
191
+		t.Fatalf("%v", err)
192
+	}
193
+
194
+	_, err = clusterAdminKubeClient.Namespaces().Create(&kapi.Namespace{
195
+		ObjectMeta: kapi.ObjectMeta{Name: testutil.Namespace()},
196
+	})
197
+	if err != nil {
198
+		t.Fatalf("%v", err)
199
+	}
200
+
201
+	err = testserver.WaitForServiceAccounts(
202
+		clusterAdminKubeClient,
203
+		testutil.Namespace(),
204
+		[]string{
205
+			bootstrappolicy.BuilderServiceAccountName,
206
+			bootstrappolicy.DefaultServiceAccountName,
207
+		})
208
+	if err != nil {
209
+		t.Fatalf("%v", err)
210
+	}
211
+
212
+	return clusterAdminClient, clusterAdminKubeClient
213
+}