Browse code

Instantiate Jenkins when pipeline strategy is created

Jimmi Dyson authored on 2016/04/29 17:52:47
Showing 9 changed files
... ...
@@ -6,11 +6,17 @@ import (
6 6
 	"github.com/golang/glog"
7 7
 	kapi "k8s.io/kubernetes/pkg/api"
8 8
 	kerrors "k8s.io/kubernetes/pkg/api/errors"
9
+	"k8s.io/kubernetes/pkg/client/record"
10
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
9 11
 	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
10 12
 
11 13
 	buildapi "github.com/openshift/origin/pkg/build/api"
12 14
 	buildclient "github.com/openshift/origin/pkg/build/client"
13 15
 	buildgenerator "github.com/openshift/origin/pkg/build/generator"
16
+	buildutil "github.com/openshift/origin/pkg/build/util"
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"
14 20
 )
15 21
 
16 22
 // ConfigControllerFatalError represents a fatal error while generating a build.
... ...
@@ -33,11 +39,67 @@ func IsFatal(err error) bool {
33 33
 
34 34
 type BuildConfigController struct {
35 35
 	BuildConfigInstantiator buildclient.BuildConfigInstantiator
36
+
37
+	KubeClient kclient.Interface
38
+	Client     osclient.Interface
39
+
40
+	JenkinsConfig serverapi.JenkinsPipelineConfig
41
+
42
+	// recorder is used to record events.
43
+	Recorder record.EventRecorder
36 44
 }
37 45
 
38 46
 func (c *BuildConfigController) HandleBuildConfig(bc *buildapi.BuildConfig) error {
39 47
 	glog.V(4).Infof("Handling BuildConfig %s/%s", bc.Namespace, bc.Name)
40 48
 
49
+	if strategy := bc.Spec.Strategy.JenkinsPipelineStrategy; strategy != nil {
50
+		svcName := c.JenkinsConfig.ServiceName
51
+		if len(svcName) == 0 {
52
+			return fmt.Errorf("the Jenkins Pipeline ServiceName must be set in master configuration")
53
+		}
54
+
55
+		glog.V(4).Infof("Detected Jenkins pipeline strategy in %s/%s build configuration", bc.Namespace, bc.Name)
56
+		if _, err := c.KubeClient.Services(bc.Namespace).Get(svcName); err == nil {
57
+			glog.V(4).Infof("The Jenkins Pipeline service %q already exists in project %q", svcName, bc.Namespace)
58
+			return nil
59
+		}
60
+
61
+		isDisabled := c.JenkinsConfig.Disabled
62
+		if isDisabled != nil && *isDisabled {
63
+			glog.V(4).Infof("Provisioning Jenkins Pipeline from a template is disabled in master configuration")
64
+			return nil
65
+		}
66
+
67
+		glog.V(3).Infof("Adding new Jenkins service %q to the project %q", svcName, bc.Namespace)
68
+		kc, ok := c.KubeClient.(*kclient.Client)
69
+		if !ok {
70
+			return fmt.Errorf("unable to get kubernetes client from %v", c.KubeClient)
71
+		}
72
+		oc, ok := c.Client.(*client.Client)
73
+		if !ok {
74
+			return fmt.Errorf("unable to get openshift client from %v", c.KubeClient)
75
+		}
76
+
77
+		jenkinsTemplate := buildutil.NewJenkinsPipelineTemplate(bc.Namespace, c.JenkinsConfig, kc, oc)
78
+		objects, errs := jenkinsTemplate.Process()
79
+		if len(errs) > 0 {
80
+			for _, err := range errs {
81
+				c.Recorder.Eventf(bc, kapi.EventTypeWarning, "Failed", "Processing %s/%s error: %v", c.JenkinsConfig.Namespace, c.JenkinsConfig.TemplateName, err)
82
+			}
83
+			return fmt.Errorf("processing Jenkins pipeline template failed")
84
+		}
85
+
86
+		if errs := jenkinsTemplate.Instantiate(objects); len(errs) > 0 {
87
+			for _, err := range errs {
88
+				c.Recorder.Eventf(bc, kapi.EventTypeWarning, "Failed", "Instantiating %s/%s error: %v", c.JenkinsConfig.Namespace, c.JenkinsConfig.TemplateName, err)
89
+			}
90
+			return fmt.Errorf("instantiating Jenkins pipeline template failed")
91
+		}
92
+
93
+		c.Recorder.Eventf(bc, kapi.EventTypeNormal, "Started", "Jenkins Pipeline service %q created", svcName)
94
+		return nil
95
+	}
96
+
41 97
 	hasChangeTrigger := false
42 98
 	for _, trigger := range bc.Spec.Triggers {
43 99
 		if trigger.Type == buildapi.ConfigChangeBuildTriggerType {
44 100
new file mode 100644
... ...
@@ -0,0 +1,137 @@
0
+package util
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/golang/glog"
6
+	"github.com/openshift/origin/pkg/api/latest"
7
+	"github.com/openshift/origin/pkg/client"
8
+	serverapi "github.com/openshift/origin/pkg/cmd/server/api"
9
+	"github.com/openshift/origin/pkg/config/cmd"
10
+	"github.com/openshift/origin/pkg/template"
11
+	templateapi "github.com/openshift/origin/pkg/template/api"
12
+	kapi "k8s.io/kubernetes/pkg/api"
13
+	kerrs "k8s.io/kubernetes/pkg/api/errors"
14
+	"k8s.io/kubernetes/pkg/api/meta"
15
+	"k8s.io/kubernetes/pkg/api/unversioned"
16
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
17
+	"k8s.io/kubernetes/pkg/kubectl/resource"
18
+	"k8s.io/kubernetes/pkg/runtime"
19
+)
20
+
21
+// JenkinsPipelineTemplate stores the configuration of the
22
+// JenkinsPipelineStrategy template, used to instantiate the Jenkins service in
23
+// given namespace.
24
+type JenkinsPipelineTemplate struct {
25
+	Config     serverapi.JenkinsPipelineConfig
26
+	Namespace  string
27
+	kubeClient *kclient.Client
28
+	osClient   *client.Client
29
+}
30
+
31
+// NewJenkinsPipelineTemplate returns a new JenkinsPipelineTemplate.
32
+func NewJenkinsPipelineTemplate(ns string, conf serverapi.JenkinsPipelineConfig, kubeClient *kclient.Client, osClient *client.Client) *JenkinsPipelineTemplate {
33
+	return &JenkinsPipelineTemplate{
34
+		Config:     conf,
35
+		Namespace:  ns,
36
+		kubeClient: kubeClient,
37
+		osClient:   osClient,
38
+	}
39
+}
40
+
41
+// Process processes the Jenkins template. If an error occurs
42
+func (t *JenkinsPipelineTemplate) Process() (*kapi.List, []error) {
43
+	var errors []error
44
+	jenkinsTemplate, err := t.osClient.Templates(t.Config.Namespace).Get(t.Config.TemplateName)
45
+	if err != nil {
46
+		if kerrs.IsNotFound(err) {
47
+			errors = append(errors, fmt.Errorf("Jenkins pipeline template %s/%s not found", t.Config.Namespace, t.Config.TemplateName))
48
+		} else {
49
+			errors = append(errors, err)
50
+		}
51
+		return nil, errors
52
+	}
53
+	errors = append(errors, substituteTemplateParameters(t.Config.Parameters, jenkinsTemplate)...)
54
+	pTemplate, err := t.osClient.TemplateConfigs(t.Namespace).Create(jenkinsTemplate)
55
+	if err != nil {
56
+		errors = append(errors, fmt.Errorf("processing Jenkins template %s/%s failed: %v", t.Config.Namespace, t.Config.TemplateName, err))
57
+		return nil, errors
58
+	}
59
+	var items []runtime.Object
60
+	for _, obj := range pTemplate.Objects {
61
+		if unknownObj, ok := obj.(*runtime.Unknown); ok {
62
+			decodedObj, err := runtime.Decode(kapi.Codecs.UniversalDecoder(), unknownObj.Raw)
63
+			if err != nil {
64
+				errors = append(errors, err)
65
+			}
66
+			items = append(items, decodedObj)
67
+		}
68
+	}
69
+	glog.V(4).Infof("Processed Jenkins pipeline jenkinsTemplate %s/%s", pTemplate.Namespace, pTemplate.Namespace)
70
+	return &kapi.List{ListMeta: unversioned.ListMeta{}, Items: items}, errors
71
+}
72
+
73
+// Instantiate instantiates the Jenkins template in the target namespace.
74
+func (t *JenkinsPipelineTemplate) Instantiate(list *kapi.List) []error {
75
+	var errors []error
76
+	if !t.hasJenkinsService(list) {
77
+		err := fmt.Errorf("template %s/%s does not contain required service %q", t.Config.Namespace, t.Config.TemplateName, t.Config.ServiceName)
78
+		return append(errors, err)
79
+	}
80
+	bulk := &cmd.Bulk{
81
+		Mapper: &resource.Mapper{
82
+			RESTMapper:  client.DefaultMultiRESTMapper(),
83
+			ObjectTyper: kapi.Scheme,
84
+			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
85
+				if latest.OriginKind(mapping.GroupVersionKind) {
86
+					return t.osClient, nil
87
+				}
88
+				return t.kubeClient, nil
89
+			}),
90
+		},
91
+		Op: cmd.Create,
92
+	}
93
+	return bulk.Run(list, t.Namespace)
94
+}
95
+
96
+// hasJenkinsService searches the template items and return true if the expected
97
+// Jenkins service is contained in template.
98
+func (t *JenkinsPipelineTemplate) hasJenkinsService(items *kapi.List) bool {
99
+	accessor := meta.NewAccessor()
100
+	for _, item := range items.Items {
101
+		kind, err := kapi.Scheme.ObjectKind(item)
102
+		if err != nil {
103
+			glog.Infof("Error checking Jenkins service kind: %v", err)
104
+			return false
105
+		}
106
+		name, err := accessor.Name(item)
107
+		if err != nil {
108
+			glog.Infof("Error checking Jenkins service name: %v", err)
109
+			return false
110
+		}
111
+		glog.Infof("Jenkins Pipeline template object %q with name %q", name, kind.Kind)
112
+		if name == t.Config.ServiceName && kind.Kind == "Service" {
113
+			return true
114
+		}
115
+	}
116
+	return false
117
+}
118
+
119
+// substituteTemplateParameters injects user specified parameter values into the Template
120
+func substituteTemplateParameters(params map[string]string, t *templateapi.Template) []error {
121
+	var errors []error
122
+	for name, value := range params {
123
+		if len(name) == 0 {
124
+			errors = append(errors, fmt.Errorf("template parameter name cannot be empty (%q)", value))
125
+			continue
126
+		}
127
+		if v := template.GetParameterByName(t, name); v != nil {
128
+			v.Value = value
129
+			v.Generate = ""
130
+			template.AddParameter(t, *v)
131
+		} else {
132
+			errors = append(errors, fmt.Errorf("unknown parameter %q specified for template", name))
133
+		}
134
+	}
135
+	return errors
136
+}
0 137
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"k8s.io/kubernetes/pkg/api/meta"
4
+	"k8s.io/kubernetes/pkg/apimachinery/registered"
5
+	"k8s.io/kubernetes/pkg/util/sets"
6
+)
7
+
8
+// DefaultMultiRESTMapper returns the multi REST mapper with all OpenShift and
9
+// Kubernetes objects already registered.
10
+func DefaultMultiRESTMapper() meta.MultiRESTMapper {
11
+	var restMapper meta.MultiRESTMapper
12
+	seenGroups := sets.String{}
13
+	for _, gv := range registered.EnabledVersions() {
14
+		if seenGroups.Has(gv.Group) {
15
+			continue
16
+		}
17
+		seenGroups.Insert(gv.Group)
18
+		groupMeta, err := registered.Group(gv.Group)
19
+		if err != nil {
20
+			continue
21
+		}
22
+		restMapper = meta.MultiRESTMapper(append(restMapper, groupMeta.RESTMapper))
23
+	}
24
+	return restMapper
25
+}
... ...
@@ -288,6 +288,28 @@ type MasterConfig struct {
288 288
 
289 289
 	// VolumeConfig contains options for configuring volumes on the node.
290 290
 	VolumeConfig MasterVolumeConfig
291
+
292
+	// JenkinsPipelineConfig holds information about the default Jenkins template
293
+	// used for JenkinsPipeline build strategy.
294
+	JenkinsPipelineConfig JenkinsPipelineConfig
295
+}
296
+
297
+// JenkinsPipelineConfig holds configuration for the Jenkins pipeline strategy
298
+type JenkinsPipelineConfig struct {
299
+	// Disabled disables the Jenkins Pipeline auto-instantiation of Jenkins
300
+	// template. The ServiceName is still used to verify the project already have
301
+	// the Jenkins available. When not specified (default), this option defaults
302
+	// to false
303
+	Disabled *bool `json:"disabled"`
304
+	// Namespace contains the namespace name where the Jenkins template is stored
305
+	Namespace string
306
+	// TemplateName is the name of the default Jenkins template
307
+	TemplateName string
308
+	// ServiceName is the name of the Jenkins service OpenShift use for Jenkins
309
+	// pipeline
310
+	ServiceName string
311
+	// Parameters specifies a set of optional parameters to the Jenkins template
312
+	Parameters map[string]string
291 313
 }
292 314
 
293 315
 type ImagePolicyConfig struct {
... ...
@@ -32,6 +32,19 @@ func addDefaultingFuncs(scheme *runtime.Scheme) {
32 32
 			if len(obj.RoutingConfig.Subdomain) == 0 {
33 33
 				obj.RoutingConfig.Subdomain = "router.default.svc.cluster.local"
34 34
 			}
35
+			if len(obj.JenkinsPipelineConfig.Namespace) == 0 {
36
+				obj.JenkinsPipelineConfig.Namespace = "openshift"
37
+			}
38
+			if len(obj.JenkinsPipelineConfig.TemplateName) == 0 {
39
+				obj.JenkinsPipelineConfig.TemplateName = "jenkins"
40
+			}
41
+			if len(obj.JenkinsPipelineConfig.ServiceName) == 0 {
42
+				obj.JenkinsPipelineConfig.ServiceName = "jenkins"
43
+			}
44
+			if obj.JenkinsPipelineConfig.Disabled == nil {
45
+				disabled := false
46
+				obj.JenkinsPipelineConfig.Disabled = &disabled
47
+			}
35 48
 
36 49
 			// Populate the new NetworkConfig.ServiceNetworkCIDR field from the KubernetesMasterConfig.ServicesSubnet field if needed
37 50
 			if len(obj.NetworkConfig.ServiceNetworkCIDR) == 0 {
... ...
@@ -265,6 +265,19 @@ func (ImagePolicyConfig) SwaggerDoc() map[string]string {
265 265
 	return map_ImagePolicyConfig
266 266
 }
267 267
 
268
+var map_JenkinsPipelineConfig = map[string]string{
269
+	"":             "JenkinsPipelineConfig holds configuration for the Jenkins pipeline strategy",
270
+	"disabled":     "Disabled disables the Jenkins Pipeline auto-instantiation of Jenkins template. The ServiceName is still used to verify the project already have the Jenkins available. When not specified (default), this option defaults to false",
271
+	"namespace":    "Namespace contains the namespace name where the Jenkins template is stored",
272
+	"templateName": "TemplateName is the name of the default Jenkins template",
273
+	"serviceName":  "ServiceName is the name of the Jenkins service OpenShift use for Jenkins pipeline",
274
+	"parameters":   "Parameters specifies a set of optional parameters to the Jenkins template",
275
+}
276
+
277
+func (JenkinsPipelineConfig) SwaggerDoc() map[string]string {
278
+	return map_JenkinsPipelineConfig
279
+}
280
+
268 281
 var map_KeystonePasswordIdentityProvider = map[string]string{
269 282
 	"":           "KeystonePasswordIdentityProvider provides identities for users authenticating using keystone password credentials",
270 283
 	"domainName": "Domain Name is required for keystone v3",
... ...
@@ -409,6 +422,7 @@ var map_MasterConfig = map[string]string{
409 409
 	"routingConfig":          "RoutingConfig holds information about routing and route generation",
410 410
 	"networkConfig":          "NetworkConfig to be passed to the compiled in network plugin",
411 411
 	"volumeConfig":           "MasterVolumeConfig contains options for configuring volume plugins in the master node.",
412
+	"jenkinsPipelineConfig":  "JenkinsPipelineConfig holds information about the default Jenkins template used for JenkinsPipeline build strategy.",
412 413
 }
413 414
 
414 415
 func (MasterConfig) SwaggerDoc() map[string]string {
... ...
@@ -228,6 +228,28 @@ type MasterConfig struct {
228 228
 
229 229
 	// MasterVolumeConfig contains options for configuring volume plugins in the master node.
230 230
 	VolumeConfig MasterVolumeConfig `json:"volumeConfig"`
231
+
232
+	// JenkinsPipelineConfig holds information about the default Jenkins template
233
+	// used for JenkinsPipeline build strategy.
234
+	JenkinsPipelineConfig JenkinsPipelineConfig `json:"jenkinsPipelineConfig"`
235
+}
236
+
237
+// JenkinsPipelineConfig holds configuration for the Jenkins pipeline strategy
238
+type JenkinsPipelineConfig struct {
239
+	// Disabled disables the Jenkins Pipeline auto-instantiation of Jenkins
240
+	// template. The ServiceName is still used to verify the project already have
241
+	// the Jenkins available. When not specified (default), this option defaults
242
+	// to false
243
+	Disabled *bool `json:"disabled"`
244
+	// Namespace contains the namespace name where the Jenkins template is stored
245
+	Namespace string `json:"namespace"`
246
+	// TemplateName is the name of the default Jenkins template
247
+	TemplateName string `json:"templateName"`
248
+	// ServiceName is the name of the Jenkins service OpenShift use for Jenkins
249
+	// pipeline
250
+	ServiceName string `json:"serviceName"`
251
+	// Parameters specifies a set of optional parameters to the Jenkins template
252
+	Parameters map[string]string `json:"parameters"`
231 253
 }
232 254
 
233 255
 // ImagePolicyConfig holds the necessary configuration options for limits and behavior for importing images
... ...
@@ -270,9 +270,14 @@ func (c *MasterConfig) RunBuildImageChangeTriggerController() {
270 270
 
271 271
 // RunBuildConfigChangeController starts the build config change trigger controller process.
272 272
 func (c *MasterConfig) RunBuildConfigChangeController() {
273
-	bcClient, _ := c.BuildConfigChangeControllerClients()
273
+	bcClient, kClient := c.BuildConfigChangeControllerClients()
274 274
 	bcInstantiator := buildclient.NewOSClientBuildConfigInstantiatorClient(bcClient)
275
-	factory := buildcontrollerfactory.BuildConfigControllerFactory{Client: bcClient, BuildConfigInstantiator: bcInstantiator}
275
+	factory := buildcontrollerfactory.BuildConfigControllerFactory{
276
+		Client:                  bcClient,
277
+		KubeClient:              kClient,
278
+		BuildConfigInstantiator: bcInstantiator,
279
+		JenkinsConfig:           c.Options.JenkinsPipelineConfig,
280
+	}
276 281
 	factory.Create().Run()
277 282
 }
278 283
 
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"k8s.io/kubernetes/pkg/api/meta"
12 12
 	"k8s.io/kubernetes/pkg/api/rest"
13 13
 	"k8s.io/kubernetes/pkg/api/unversioned"
14
-	"k8s.io/kubernetes/pkg/apimachinery/registered"
15 14
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
16 15
 	"k8s.io/kubernetes/pkg/kubectl/resource"
17 16
 	"k8s.io/kubernetes/pkg/runtime"
... ...
@@ -138,24 +137,9 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
138 138
 		return nil, err
139 139
 	}
140 140
 
141
-	var restMapper meta.MultiRESTMapper
142
-	seenGroups := sets.String{}
143
-	for _, gv := range registered.EnabledVersions() {
144
-		if seenGroups.Has(gv.Group) {
145
-			continue
146
-		}
147
-		seenGroups.Insert(gv.Group)
148
-
149
-		groupMeta, err := registered.Group(gv.Group)
150
-		if err != nil {
151
-			continue
152
-		}
153
-		restMapper = meta.MultiRESTMapper(append(restMapper, groupMeta.RESTMapper))
154
-	}
155
-
156 141
 	bulk := configcmd.Bulk{
157 142
 		Mapper: &resource.Mapper{
158
-			RESTMapper:  restMapper,
143
+			RESTMapper:  client.DefaultMultiRESTMapper(),
159 144
 			ObjectTyper: kapi.Scheme,
160 145
 			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
161 146
 				if latest.OriginKind(mapping.GroupVersionKind) {