| 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{}
|
| ... | ... |
@@ -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 |
|