Browse code

prevent the build controller from escalating users

deads2k authored on 2016/05/28 03:25:32
Showing 13 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+package client
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"k8s.io/kubernetes/pkg/auth/user"
6
+
7
+	authenticationapi "github.com/openshift/origin/pkg/auth/api"
8
+	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
9
+)
10
+
11
+type impersonatingRoundTripper struct {
12
+	user     user.Info
13
+	delegate http.RoundTripper
14
+}
15
+
16
+// NewImpersonatingRoundTripper will add headers to impersonate a user, including user, groups, and scopes
17
+func NewImpersonatingRoundTripper(user user.Info, delegate http.RoundTripper) http.RoundTripper {
18
+	return &impersonatingRoundTripper{user, delegate}
19
+}
20
+
21
+func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
22
+	req = cloneRequest(req)
23
+	req.Header.Del(authenticationapi.ImpersonateUserHeader)
24
+	req.Header.Del(authenticationapi.ImpersonateGroupHeader)
25
+	req.Header.Del(authenticationapi.ImpersonateUserScopeHeader)
26
+
27
+	req.Header.Set(authenticationapi.ImpersonateUserHeader, rt.user.GetName())
28
+	for _, group := range rt.user.GetGroups() {
29
+		req.Header.Add(authenticationapi.ImpersonateGroupHeader, group)
30
+	}
31
+	for _, scope := range rt.user.GetExtra()[authorizationapi.ScopesKey] {
32
+		req.Header.Add(authenticationapi.ImpersonateUserScopeHeader, scope)
33
+	}
34
+	return rt.delegate.RoundTrip(req)
35
+}
36
+
37
+// cloneRequest returns a clone of the provided *http.Request.
38
+// The clone is a shallow copy of the struct and its Header map.
39
+func cloneRequest(r *http.Request) *http.Request {
40
+	// shallow copy of the struct
41
+	r2 := new(http.Request)
42
+	*r2 = *r
43
+	// deep copy of the Header
44
+	r2.Header = make(http.Header)
45
+	for k, s := range r.Header {
46
+		r2.Header[k] = s
47
+	}
48
+	return r2
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,156 @@
0
+package jenkinsbootstrapper
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"net/http"
6
+
7
+	"github.com/golang/glog"
8
+	"k8s.io/kubernetes/pkg/admission"
9
+	kapi "k8s.io/kubernetes/pkg/api"
10
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
11
+	"k8s.io/kubernetes/pkg/api/meta"
12
+	"k8s.io/kubernetes/pkg/apimachinery/registered"
13
+	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
14
+	coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
15
+	"k8s.io/kubernetes/pkg/client/restclient"
16
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
17
+	"k8s.io/kubernetes/pkg/kubectl/resource"
18
+	"k8s.io/kubernetes/pkg/runtime"
19
+	kutilerrors "k8s.io/kubernetes/pkg/util/errors"
20
+
21
+	"github.com/openshift/origin/pkg/api/latest"
22
+	authenticationclient "github.com/openshift/origin/pkg/auth/client"
23
+	buildapi "github.com/openshift/origin/pkg/build/api"
24
+	jenkinscontroller "github.com/openshift/origin/pkg/build/controller/jenkins"
25
+	"github.com/openshift/origin/pkg/client"
26
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
27
+	"github.com/openshift/origin/pkg/config/cmd"
28
+)
29
+
30
+func init() {
31
+	admission.RegisterPlugin("JenkinsBootstrapper", func(c clientset.Interface, config io.Reader) (admission.Interface, error) {
32
+		return NewJenkingsBootstrapper(c.Core()), nil
33
+	})
34
+}
35
+
36
+type jenkingsBootstrapper struct {
37
+	*admission.Handler
38
+
39
+	privilegedRESTClientConfig restclient.Config
40
+	serviceClient              coreclient.ServicesGetter
41
+	openshiftClient            client.Interface
42
+
43
+	jenkinsConfig configapi.JenkinsPipelineConfig
44
+}
45
+
46
+// NewJenkingsBootstrapper returns an admission plugin that will create required jenkins resources as the user if they are needed.
47
+func NewJenkingsBootstrapper(serviceClient coreclient.ServicesGetter) admission.Interface {
48
+	return &jenkingsBootstrapper{
49
+		Handler:       admission.NewHandler(admission.Create),
50
+		serviceClient: serviceClient,
51
+	}
52
+}
53
+
54
+func (a *jenkingsBootstrapper) Admit(attributes admission.Attributes) error {
55
+	if a.jenkinsConfig.Enabled != nil && !*a.jenkinsConfig.Enabled {
56
+		return nil
57
+	}
58
+	if len(attributes.GetSubresource()) != 0 {
59
+		return nil
60
+	}
61
+	if attributes.GetResource().GroupResource() != buildapi.Resource("buildconfigs") && attributes.GetResource().GroupResource() != buildapi.Resource("builds") {
62
+		return nil
63
+	}
64
+	if !needsJenkinsTemplate(attributes.GetObject()) {
65
+		return nil
66
+	}
67
+
68
+	namespace := attributes.GetNamespace()
69
+
70
+	svcName := a.jenkinsConfig.ServiceName
71
+	if len(svcName) == 0 {
72
+		return nil
73
+	}
74
+
75
+	// TODO pull this from a cache.
76
+	if _, err := a.serviceClient.Services(namespace).Get(svcName); !kapierrors.IsNotFound(err) {
77
+		// if it isn't a "not found" error, return the error.  Either its nil and there's nothing to do or something went really wrong
78
+		return err
79
+	}
80
+
81
+	glog.V(3).Infof("Adding new jenkins service %q to the project %q", svcName, namespace)
82
+	jenkinsTemplate := jenkinscontroller.NewPipelineTemplate(namespace, a.jenkinsConfig, a.openshiftClient)
83
+	objects, errs := jenkinsTemplate.Process()
84
+	if len(errs) > 0 {
85
+		return kutilerrors.NewAggregate(errs)
86
+	}
87
+	if !jenkinsTemplate.HasJenkinsService(objects) {
88
+		return fmt.Errorf("template %s/%s does not contain required service %q", a.jenkinsConfig.TemplateNamespace, a.jenkinsConfig.TemplateName, a.jenkinsConfig.ServiceName)
89
+	}
90
+
91
+	impersonatingConfig := a.privilegedRESTClientConfig
92
+	oldWrapTransport := impersonatingConfig.WrapTransport
93
+	impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
94
+		return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt))
95
+	}
96
+
97
+	var bulkErr error
98
+
99
+	bulk := &cmd.Bulk{
100
+		Mapper: &resource.Mapper{
101
+			RESTMapper:  registered.RESTMapper(),
102
+			ObjectTyper: kapi.Scheme,
103
+			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
104
+				if latest.OriginKind(mapping.GroupVersionKind) {
105
+					return client.New(&impersonatingConfig)
106
+				}
107
+				return kclient.New(&impersonatingConfig)
108
+			}),
109
+		},
110
+		Op: cmd.Create,
111
+		After: func(info *resource.Info, err error) bool {
112
+			if kapierrors.IsAlreadyExists(err) {
113
+				return false
114
+			}
115
+			if err != nil {
116
+				bulkErr = err
117
+				return true
118
+			}
119
+			return false
120
+		},
121
+	}
122
+	// we're intercepting the error we care about using After
123
+	bulk.Run(objects, namespace)
124
+	if bulkErr != nil {
125
+		return bulkErr
126
+	}
127
+
128
+	glog.V(1).Infof("Jenkins Pipeline service %q created", svcName)
129
+
130
+	return nil
131
+
132
+}
133
+
134
+func needsJenkinsTemplate(obj runtime.Object) bool {
135
+	switch t := obj.(type) {
136
+	case *buildapi.Build:
137
+		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
138
+	case *buildapi.BuildConfig:
139
+		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
140
+	default:
141
+		return false
142
+	}
143
+}
144
+
145
+func (a *jenkingsBootstrapper) SetJenkinsPipelineConfig(jenkinsConfig configapi.JenkinsPipelineConfig) {
146
+	a.jenkinsConfig = jenkinsConfig
147
+}
148
+
149
+func (a *jenkingsBootstrapper) SetRESTClientConfig(restClientConfig restclient.Config) {
150
+	a.privilegedRESTClientConfig = restClientConfig
151
+}
152
+
153
+func (a *jenkingsBootstrapper) SetOpenshiftClient(oclient client.Interface) {
154
+	a.openshiftClient = oclient
155
+}
0 156
new file mode 100644
... ...
@@ -0,0 +1,149 @@
0
+package jenkinsbootstrapper
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+	"testing"
6
+
7
+	"k8s.io/kubernetes/pkg/admission"
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	"k8s.io/kubernetes/pkg/api/unversioned"
10
+	"k8s.io/kubernetes/pkg/auth/user"
11
+	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
12
+	"k8s.io/kubernetes/pkg/runtime"
13
+
14
+	buildapi "github.com/openshift/origin/pkg/build/api"
15
+	"github.com/openshift/origin/pkg/client/testclient"
16
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
17
+)
18
+
19
+func TestAdmission(t *testing.T) {
20
+	enableBuild := &buildapi.Build{Spec: buildapi.BuildSpec{CommonSpec: buildapi.CommonSpec{Strategy: buildapi.BuildStrategy{JenkinsPipelineStrategy: &buildapi.JenkinsPipelineBuildStrategy{}}}}}
21
+	testCases := []struct {
22
+		name           string
23
+		objects        []runtime.Object
24
+		jenkinsEnabled *bool
25
+
26
+		attributes  admission.Attributes
27
+		expectedErr string
28
+
29
+		validateClients func(kubeClient *fake.Clientset, originClient *testclient.Fake) string
30
+	}{
31
+		{
32
+			name:            "disabled",
33
+			attributes:      admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
34
+			jenkinsEnabled:  boolptr(false),
35
+			validateClients: noAction,
36
+		},
37
+		{
38
+			name:            "not a jenkins build",
39
+			attributes:      admission.NewAttributesRecord(&buildapi.Build{Spec: buildapi.BuildSpec{CommonSpec: buildapi.CommonSpec{Strategy: buildapi.BuildStrategy{}}}}, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
40
+			validateClients: noAction,
41
+		},
42
+		{
43
+			name:            "not a build kind",
44
+			attributes:      admission.NewAttributesRecord(&kapi.Service{}, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
45
+			validateClients: noAction,
46
+		},
47
+		{
48
+			name:            "not a build resource",
49
+			attributes:      admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("notbuilds"), "", admission.Create, &user.DefaultInfo{}),
50
+			validateClients: noAction,
51
+		},
52
+		{
53
+			name:            "subresource",
54
+			attributes:      admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "subresource", admission.Create, &user.DefaultInfo{}),
55
+			validateClients: noAction,
56
+		},
57
+		{
58
+			name:       "service present",
59
+			attributes: admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
60
+			objects: []runtime.Object{
61
+				&kapi.Service{ObjectMeta: kapi.ObjectMeta{Namespace: "namespace", Name: "jenkins"}},
62
+			},
63
+			validateClients: func(kubeClient *fake.Clientset, originClient *testclient.Fake) string {
64
+				if len(kubeClient.Actions()) == 1 && kubeClient.Actions()[0].Matches("get", "services") {
65
+					return ""
66
+				}
67
+				return fmt.Sprintf("missing get service in: %v", kubeClient.Actions())
68
+			},
69
+		},
70
+		{
71
+			name:       "works on true",
72
+			attributes: admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
73
+			objects: []runtime.Object{
74
+				&kapi.Service{ObjectMeta: kapi.ObjectMeta{Namespace: "namespace", Name: "jenkins"}},
75
+			},
76
+			jenkinsEnabled: boolptr(true),
77
+			validateClients: func(kubeClient *fake.Clientset, originClient *testclient.Fake) string {
78
+				if len(kubeClient.Actions()) == 1 && kubeClient.Actions()[0].Matches("get", "services") {
79
+					return ""
80
+				}
81
+				return fmt.Sprintf("missing get service in: %v", kubeClient.Actions())
82
+			},
83
+		},
84
+		{
85
+			name:       "service missing",
86
+			attributes: admission.NewAttributesRecord(enableBuild, unversioned.GroupVersionKind{}, "namespace", "name", buildapi.SchemeGroupVersion.WithResource("builds"), "", admission.Create, &user.DefaultInfo{}),
87
+			objects:    []runtime.Object{},
88
+			validateClients: func(kubeClient *fake.Clientset, originClient *testclient.Fake) string {
89
+				if len(kubeClient.Actions()) == 0 {
90
+					return fmt.Sprintf("missing get service in: %v", kubeClient.Actions())
91
+				}
92
+				if !kubeClient.Actions()[0].Matches("get", "services") {
93
+					return fmt.Sprintf("missing get service in: %v", kubeClient.Actions())
94
+				}
95
+				if len(originClient.Actions()) == 0 {
96
+					return fmt.Sprintf("missing get template in: %v", originClient.Actions())
97
+				}
98
+				if !originClient.Actions()[0].Matches("get", "templates") {
99
+					return fmt.Sprintf("missing get template in: %v", originClient.Actions())
100
+				}
101
+				return ""
102
+			},
103
+			expectedErr: "Jenkins pipeline template / not found",
104
+		},
105
+	}
106
+
107
+	for _, tc := range testCases {
108
+		kubeClient := fake.NewSimpleClientset(tc.objects...)
109
+		originClient := testclient.NewSimpleFake(tc.objects...)
110
+
111
+		admission := NewJenkingsBootstrapper(kubeClient.Core()).(*jenkingsBootstrapper)
112
+		admission.openshiftClient = originClient
113
+		admission.jenkinsConfig = configapi.JenkinsPipelineConfig{
114
+			Enabled:     tc.jenkinsEnabled,
115
+			ServiceName: "jenkins",
116
+		}
117
+
118
+		err := admission.Admit(tc.attributes)
119
+		switch {
120
+		case len(tc.expectedErr) == 0 && err == nil:
121
+		case len(tc.expectedErr) == 0 && err != nil:
122
+			t.Errorf("%s: unexpected error: %v", tc.name, err)
123
+		case len(tc.expectedErr) != 0 && err == nil:
124
+			t.Errorf("%s: missing error: %v", tc.name, tc.expectedErr)
125
+		case len(tc.expectedErr) != 0 && err != nil && !strings.Contains(err.Error(), tc.expectedErr):
126
+			t.Errorf("%s: missing error: expected %v, got %v", tc.name, tc.expectedErr, err)
127
+		}
128
+
129
+		if tc.validateClients != nil {
130
+			if err := tc.validateClients(kubeClient, originClient); len(err) != 0 {
131
+				t.Errorf("%s: unexpected error: %v", tc.name, err)
132
+			}
133
+		}
134
+	}
135
+}
136
+func noAction(kubeClient *fake.Clientset, originClient *testclient.Fake) string {
137
+	if len(kubeClient.Actions()) != 0 {
138
+		return fmt.Sprintf("unexpected actions: %v", kubeClient.Actions())
139
+	}
140
+	if len(originClient.Actions()) != 0 {
141
+		return fmt.Sprintf("unexpected actions: %v", originClient.Actions())
142
+	}
143
+	return ""
144
+}
145
+
146
+func boolptr(in bool) *bool {
147
+	return &in
148
+}
... ...
@@ -7,16 +7,11 @@ import (
7 7
 	kapi "k8s.io/kubernetes/pkg/api"
8 8
 	kerrors "k8s.io/kubernetes/pkg/api/errors"
9 9
 	"k8s.io/kubernetes/pkg/client/record"
10
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
11 10
 	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
12 11
 
13 12
 	buildapi "github.com/openshift/origin/pkg/build/api"
14 13
 	buildclient "github.com/openshift/origin/pkg/build/client"
15
-	"github.com/openshift/origin/pkg/build/controller/jenkins"
16 14
 	buildgenerator "github.com/openshift/origin/pkg/build/generator"
17
-	"github.com/openshift/origin/pkg/client"
18
-	osclient "github.com/openshift/origin/pkg/client"
19
-	serverapi "github.com/openshift/origin/pkg/cmd/server/api"
20 15
 )
21 16
 
22 17
 // ConfigControllerFatalError represents a fatal error while generating a build.
... ...
@@ -40,11 +35,6 @@ func IsFatal(err error) bool {
40 40
 type BuildConfigController struct {
41 41
 	BuildConfigInstantiator buildclient.BuildConfigInstantiator
42 42
 
43
-	KubeClient kclient.Interface
44
-	Client     osclient.Interface
45
-
46
-	JenkinsConfig serverapi.JenkinsPipelineConfig
47
-
48 43
 	// recorder is used to record events.
49 44
 	Recorder record.EventRecorder
50 45
 }
... ...
@@ -52,53 +42,6 @@ type BuildConfigController struct {
52 52
 func (c *BuildConfigController) HandleBuildConfig(bc *buildapi.BuildConfig) error {
53 53
 	glog.V(4).Infof("Handling BuildConfig %s/%s", bc.Namespace, bc.Name)
54 54
 
55
-	if strategy := bc.Spec.Strategy.JenkinsPipelineStrategy; strategy != nil {
56
-		svcName := c.JenkinsConfig.ServiceName
57
-		if len(svcName) == 0 {
58
-			return fmt.Errorf("the Jenkins Pipeline ServiceName must be set in master configuration")
59
-		}
60
-
61
-		glog.V(4).Infof("Detected Jenkins pipeline strategy in %s/%s build configuration", bc.Namespace, bc.Name)
62
-		if _, err := c.KubeClient.Services(bc.Namespace).Get(svcName); err == nil {
63
-			glog.V(4).Infof("The Jenkins Pipeline service %q already exists in project %q", svcName, bc.Namespace)
64
-			return nil
65
-		}
66
-
67
-		if b := c.JenkinsConfig.Enabled; b == nil || !*b {
68
-			glog.V(4).Infof("Provisioning Jenkins Pipeline from a template is disabled in master configuration")
69
-			return nil
70
-		}
71
-
72
-		glog.V(3).Infof("Adding new Jenkins service %q to the project %q", svcName, bc.Namespace)
73
-		kc, ok := c.KubeClient.(*kclient.Client)
74
-		if !ok {
75
-			return fmt.Errorf("unable to get kubernetes client from %v", c.KubeClient)
76
-		}
77
-		oc, ok := c.Client.(*client.Client)
78
-		if !ok {
79
-			return fmt.Errorf("unable to get openshift client from %v", c.KubeClient)
80
-		}
81
-
82
-		jenkinsTemplate := jenkins.NewPipelineTemplate(bc.Namespace, c.JenkinsConfig, kc, oc)
83
-		objects, errs := jenkinsTemplate.Process()
84
-		if len(errs) > 0 {
85
-			for _, err := range errs {
86
-				c.Recorder.Eventf(bc, kapi.EventTypeWarning, "Failed", "Processing %s/%s error: %v", c.JenkinsConfig.TemplateNamespace, c.JenkinsConfig.TemplateName, err)
87
-			}
88
-			return fmt.Errorf("processing Jenkins pipeline template failed")
89
-		}
90
-
91
-		if errs := jenkinsTemplate.Instantiate(objects); len(errs) > 0 {
92
-			for _, err := range errs {
93
-				c.Recorder.Eventf(bc, kapi.EventTypeWarning, "Failed", "Instantiating %s/%s error: %v", c.JenkinsConfig.TemplateNamespace, c.JenkinsConfig.TemplateName, err)
94
-			}
95
-			return fmt.Errorf("instantiating Jenkins pipeline template failed")
96
-		}
97
-
98
-		c.Recorder.Eventf(bc, kapi.EventTypeNormal, "Started", "Jenkins Pipeline service %q created", svcName)
99
-		return nil
100
-	}
101
-
102 55
 	hasChangeTrigger := false
103 56
 	for _, trigger := range bc.Spec.Triggers {
104 57
 		if trigger.Type == buildapi.ConfigChangeBuildTriggerType {
... ...
@@ -25,7 +25,6 @@ import (
25 25
 	strategy "github.com/openshift/origin/pkg/build/controller/strategy"
26 26
 	buildutil "github.com/openshift/origin/pkg/build/util"
27 27
 	osclient "github.com/openshift/origin/pkg/client"
28
-	serverapi "github.com/openshift/origin/pkg/cmd/server/api"
29 28
 	controller "github.com/openshift/origin/pkg/controller"
30 29
 	imageapi "github.com/openshift/origin/pkg/image/api"
31 30
 	errors "github.com/openshift/origin/pkg/util/errors"
... ...
@@ -314,7 +313,6 @@ type BuildConfigControllerFactory struct {
314 314
 	Client                  osclient.Interface
315 315
 	KubeClient              kclient.Interface
316 316
 	BuildConfigInstantiator buildclient.BuildConfigInstantiator
317
-	JenkinsConfig           serverapi.JenkinsPipelineConfig
318 317
 	// Stop may be set to allow controllers created by this factory to be terminated.
319 318
 	Stop <-chan struct{}
320 319
 }
... ...
@@ -329,9 +327,6 @@ func (factory *BuildConfigControllerFactory) Create() controller.RunnableControl
329 329
 
330 330
 	bcController := &buildcontroller.BuildConfigController{
331 331
 		BuildConfigInstantiator: factory.BuildConfigInstantiator,
332
-		KubeClient:              factory.KubeClient,
333
-		Client:                  factory.Client,
334
-		JenkinsConfig:           factory.JenkinsConfig,
335 332
 		Recorder:                eventBroadcaster.NewRecorder(kapi.EventSource{Component: "build-config-controller"}),
336 333
 	}
337 334
 
... ...
@@ -9,14 +9,10 @@ import (
9 9
 	kerrs "k8s.io/kubernetes/pkg/api/errors"
10 10
 	"k8s.io/kubernetes/pkg/api/meta"
11 11
 	"k8s.io/kubernetes/pkg/api/unversioned"
12
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
13
-	"k8s.io/kubernetes/pkg/kubectl/resource"
14 12
 	"k8s.io/kubernetes/pkg/runtime"
15 13
 
16
-	"github.com/openshift/origin/pkg/api/latest"
17 14
 	"github.com/openshift/origin/pkg/client"
18 15
 	serverapi "github.com/openshift/origin/pkg/cmd/server/api"
19
-	"github.com/openshift/origin/pkg/config/cmd"
20 16
 	"github.com/openshift/origin/pkg/template"
21 17
 	templateapi "github.com/openshift/origin/pkg/template/api"
22 18
 )
... ...
@@ -25,19 +21,17 @@ import (
25 25
 // PipelineStrategy template, used to instantiate the Jenkins service in
26 26
 // given namespace.
27 27
 type PipelineTemplate struct {
28
-	Config     serverapi.JenkinsPipelineConfig
29
-	Namespace  string
30
-	kubeClient *kclient.Client
31
-	osClient   *client.Client
28
+	Config    serverapi.JenkinsPipelineConfig
29
+	Namespace string
30
+	osClient  client.Interface
32 31
 }
33 32
 
34 33
 // NewPipelineTemplate returns a new PipelineTemplate.
35
-func NewPipelineTemplate(ns string, conf serverapi.JenkinsPipelineConfig, kubeClient *kclient.Client, osClient *client.Client) *PipelineTemplate {
34
+func NewPipelineTemplate(ns string, conf serverapi.JenkinsPipelineConfig, osClient client.Interface) *PipelineTemplate {
36 35
 	return &PipelineTemplate{
37
-		Config:     conf,
38
-		Namespace:  ns,
39
-		kubeClient: kubeClient,
40
-		osClient:   osClient,
36
+		Config:    conf,
37
+		Namespace: ns,
38
+		osClient:  osClient,
41 39
 	}
42 40
 }
43 41
 
... ...
@@ -73,32 +67,9 @@ func (t *PipelineTemplate) Process() (*kapi.List, []error) {
73 73
 	return &kapi.List{ListMeta: unversioned.ListMeta{}, Items: items}, errors
74 74
 }
75 75
 
76
-// Instantiate instantiates the Jenkins template in the target namespace.
77
-func (t *PipelineTemplate) Instantiate(list *kapi.List) []error {
78
-	var errors []error
79
-	if !t.hasJenkinsService(list) {
80
-		err := fmt.Errorf("template %s/%s does not contain required service %q", t.Config.TemplateNamespace, t.Config.TemplateName, t.Config.ServiceName)
81
-		return append(errors, err)
82
-	}
83
-	bulk := &cmd.Bulk{
84
-		Mapper: &resource.Mapper{
85
-			RESTMapper:  client.DefaultMultiRESTMapper(),
86
-			ObjectTyper: kapi.Scheme,
87
-			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
88
-				if latest.OriginKind(mapping.GroupVersionKind) {
89
-					return t.osClient, nil
90
-				}
91
-				return t.kubeClient, nil
92
-			}),
93
-		},
94
-		Op: cmd.Create,
95
-	}
96
-	return bulk.Run(list, t.Namespace)
97
-}
98
-
99
-// hasJenkinsService searches the template items and return true if the expected
76
+// HasJenkinsService searches the template items and return true if the expected
100 77
 // Jenkins service is contained in template.
101
-func (t *PipelineTemplate) hasJenkinsService(items *kapi.List) bool {
78
+func (t *PipelineTemplate) HasJenkinsService(items *kapi.List) bool {
102 79
 	accessor := meta.NewAccessor()
103 80
 	for _, item := range items.Items {
104 81
 		kind, err := kapi.Scheme.ObjectKind(item)
... ...
@@ -2,18 +2,22 @@ package admission
2 2
 
3 3
 import (
4 4
 	"k8s.io/kubernetes/pkg/admission"
5
+	"k8s.io/kubernetes/pkg/client/restclient"
5 6
 	"k8s.io/kubernetes/pkg/quota"
6 7
 
7 8
 	"github.com/openshift/origin/pkg/authorization/authorizer"
8 9
 	"github.com/openshift/origin/pkg/client"
10
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
9 11
 	"github.com/openshift/origin/pkg/project/cache"
10 12
 )
11 13
 
12 14
 type PluginInitializer struct {
13
-	OpenshiftClient     client.Interface
14
-	ProjectCache        *cache.ProjectCache
15
-	OriginQuotaRegistry quota.Registry
16
-	Authorizer          authorizer.Authorizer
15
+	OpenshiftClient       client.Interface
16
+	ProjectCache          *cache.ProjectCache
17
+	OriginQuotaRegistry   quota.Registry
18
+	Authorizer            authorizer.Authorizer
19
+	JenkinsPipelineConfig configapi.JenkinsPipelineConfig
20
+	RESTClientConfig      restclient.Config
17 21
 }
18 22
 
19 23
 // Initialize will check the initialization interfaces implemented by each plugin
... ...
@@ -32,6 +36,12 @@ func (i *PluginInitializer) Initialize(plugins []admission.Interface) {
32 32
 		if wantsAuthorizer, ok := plugin.(WantsAuthorizer); ok {
33 33
 			wantsAuthorizer.SetAuthorizer(i.Authorizer)
34 34
 		}
35
+		if wantsJenkinsPipelineConfig, ok := plugin.(WantsJenkinsPipelineConfig); ok {
36
+			wantsJenkinsPipelineConfig.SetJenkinsPipelineConfig(i.JenkinsPipelineConfig)
37
+		}
38
+		if wantsRESTClientConfig, ok := plugin.(WantsRESTClientConfig); ok {
39
+			wantsRESTClientConfig.SetRESTClientConfig(i.RESTClientConfig)
40
+		}
35 41
 	}
36 42
 }
37 43
 
... ...
@@ -1,10 +1,12 @@
1 1
 package admission
2 2
 
3 3
 import (
4
+	"k8s.io/kubernetes/pkg/client/restclient"
4 5
 	"k8s.io/kubernetes/pkg/quota"
5 6
 
6 7
 	"github.com/openshift/origin/pkg/authorization/authorizer"
7 8
 	"github.com/openshift/origin/pkg/client"
9
+	configapi "github.com/openshift/origin/pkg/cmd/server/api"
8 10
 	"github.com/openshift/origin/pkg/project/cache"
9 11
 )
10 12
 
... ...
@@ -36,3 +38,14 @@ type Validator interface {
36 36
 type WantsAuthorizer interface {
37 37
 	SetAuthorizer(authorizer.Authorizer)
38 38
 }
39
+
40
+// WantsJenkinsPipelineConfig gives access to the JenkinsPipelineConfig.  This is a historical oddity.
41
+// It's likely that what we really wanted was this as an admission plugin config
42
+type WantsJenkinsPipelineConfig interface {
43
+	SetJenkinsPipelineConfig(jenkinsConfig configapi.JenkinsPipelineConfig)
44
+}
45
+
46
+// WantsRESTClientConfig gives access to a RESTClientConfig.  It's useful for doing unusual things with transports.
47
+type WantsRESTClientConfig interface {
48
+	SetRESTClientConfig(restclient.Config)
49
+}
... ...
@@ -191,7 +191,15 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
191 191
 	kubeletClientConfig := configapi.GetKubeletClientConfig(options)
192 192
 
193 193
 	// in-order list of plug-ins that should intercept admission decisions (origin only intercepts)
194
-	admissionControlPluginNames := []string{"ProjectRequestLimit", "OriginNamespaceLifecycle", "PodNodeConstraints", "BuildByStrategy", imageadmission.PluginName, quotaadmission.PluginName}
194
+	admissionControlPluginNames := []string{
195
+		"ProjectRequestLimit",
196
+		"OriginNamespaceLifecycle",
197
+		"PodNodeConstraints",
198
+		"JenkinsBootstrapper",
199
+		"BuildByStrategy",
200
+		imageadmission.PluginName,
201
+		quotaadmission.PluginName,
202
+	}
195 203
 	if len(options.AdmissionConfig.PluginOrderOverride) > 0 {
196 204
 		admissionControlPluginNames = options.AdmissionConfig.PluginOrderOverride
197 205
 	}
... ...
@@ -206,10 +214,12 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
206 206
 	authorizer := newAuthorizer(ruleResolver, policyClient, options.ProjectConfig.ProjectRequestMessage)
207 207
 
208 208
 	pluginInitializer := oadmission.PluginInitializer{
209
-		OpenshiftClient:     privilegedLoopbackOpenShiftClient,
210
-		ProjectCache:        projectCache,
211
-		OriginQuotaRegistry: quotaRegistry,
212
-		Authorizer:          authorizer,
209
+		OpenshiftClient:       privilegedLoopbackOpenShiftClient,
210
+		ProjectCache:          projectCache,
211
+		OriginQuotaRegistry:   quotaRegistry,
212
+		Authorizer:            authorizer,
213
+		JenkinsPipelineConfig: options.JenkinsPipelineConfig,
214
+		RESTClientConfig:      *privilegedLoopbackClientConfig,
213 215
 	}
214 216
 
215 217
 	plugins := []admission.Interface{}
... ...
@@ -296,7 +296,6 @@ func (c *MasterConfig) RunBuildConfigChangeController() {
296 296
 		Client:                  bcClient,
297 297
 		KubeClient:              kClient,
298 298
 		BuildConfigInstantiator: bcInstantiator,
299
-		JenkinsConfig:           c.Options.JenkinsPipelineConfig,
300 299
 	}
301 300
 	factory.Create().Run()
302 301
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 
9 9
 	// Admission control plug-ins used by OpenShift
10 10
 	_ "github.com/openshift/origin/pkg/build/admission/defaults"
11
+	_ "github.com/openshift/origin/pkg/build/admission/jenkinsbootstrapper"
11 12
 	_ "github.com/openshift/origin/pkg/build/admission/overrides"
12 13
 	_ "github.com/openshift/origin/pkg/build/admission/strategyrestrictions"
13 14
 	_ "github.com/openshift/origin/pkg/image/admission"
... ...
@@ -28,6 +28,7 @@ var admissionPluginsNotUsedByKube = sets.NewString(
28 28
 	"BuildDefaults",            // from origin, only needed for managing builds, not kubernetes resources
29 29
 	"BuildOverrides",           // from origin, only needed for managing builds, not kubernetes resources
30 30
 	imageadmission.PluginName,  // from origin, used for limiting image sizes, not kubernetes resources
31
+	"JenkinsBootstrapper",      // from origin, only needed for managing builds, not kubernetes resources
31 32
 	"OriginNamespaceLifecycle", // from origin, only needed for rejecting openshift resources, so not needed by kube
32 33
 	"ProjectRequestLimit",      // from origin, used for limiting project requests by user (online use case)
33 34
 	"RunOnceDuration",          // from origin, used for overriding the ActiveDeadlineSeconds for run-once pods
... ...
@@ -197,6 +197,18 @@ func setupBuildStrategyTest(t *testing.T, includeControllers bool) (clusterAdmin
197 197
 		t.Fatalf("Couldn't create ImageStreamMapping: %v", err)
198 198
 	}
199 199
 
200
+	template, err := testutil.GetTemplateFixture("../../examples/jenkins/jenkins-ephemeral-template.json")
201
+	if err != nil {
202
+		t.Fatalf("unexpected error: %v", err)
203
+	}
204
+	template.Name = "jenkins"
205
+	template.Namespace = "openshift"
206
+
207
+	_, err = clusterAdminClient.Templates("openshift").Create(template)
208
+	if err != nil {
209
+		t.Fatalf("Couldn't create jenkins template: %v", err)
210
+	}
211
+
200 212
 	return
201 213
 }
202 214