Browse code

Merge pull request #2082 from smarterclayton/webhooks_v1beta3

Merged by openshift-bot

OpenShift Bot authored on 2015/05/08 05:01:55
Showing 14 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,110 @@
0
+/*
1
+Copyright 2015 The Kubernetes Authors All rights reserved.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package rest
17
+
18
+import (
19
+	"net/http"
20
+
21
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
22
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
23
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
24
+)
25
+
26
+// HookHandler is a Kubernetes API compatible webhook that is able to get access to the raw request
27
+// and response. Used when adapting existing webhook code to the Kubernetes patterns.
28
+type HookHandler interface {
29
+	ServeHTTP(w http.ResponseWriter, r *http.Request, ctx api.Context, name, subpath string) error
30
+}
31
+
32
+type httpHookHandler struct {
33
+	http.Handler
34
+}
35
+
36
+func (h httpHookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, ctx api.Context, name, subpath string) error {
37
+	h.Handler.ServeHTTP(w, r)
38
+	return nil
39
+}
40
+
41
+// WebHook provides a reusable rest.Storage implementation for linking a generic WebHook handler
42
+// into the Kube API pattern. It is intended to be used with GET or POST against a resource's
43
+// named path, possibly as a subresource. The handler has access to the extracted information
44
+// from the Kube apiserver including the context, the name, and the subpath.
45
+type WebHook struct {
46
+	h        HookHandler
47
+	allowGet bool
48
+}
49
+
50
+var _ rest.Connecter = &WebHook{}
51
+
52
+func NewWebHook(handler HookHandler, allowGet bool) *WebHook {
53
+	return &WebHook{
54
+		h:        handler,
55
+		allowGet: allowGet,
56
+	}
57
+}
58
+
59
+func NewHTTPWebHook(handler http.Handler, allowGet bool) *WebHook {
60
+	return &WebHook{
61
+		h:        httpHookHandler{handler},
62
+		allowGet: allowGet,
63
+	}
64
+}
65
+
66
+func (h *WebHook) New() runtime.Object {
67
+	return &api.Status{}
68
+}
69
+
70
+func (h *WebHook) Connect(ctx api.Context, name string, options runtime.Object) (rest.ConnectHandler, error) {
71
+	return &WebHookHandler{
72
+		handler: h.h,
73
+		ctx:     ctx,
74
+		name:    name,
75
+		options: options.(*api.PodProxyOptions),
76
+	}, nil
77
+}
78
+
79
+func (h *WebHook) NewConnectOptions() (runtime.Object, bool, string) {
80
+	return &api.PodProxyOptions{}, true, "path"
81
+}
82
+
83
+func (h *WebHook) ConnectMethods() []string {
84
+	if h.allowGet {
85
+		return []string{"GET", "POST"}
86
+	}
87
+	return []string{"POST"}
88
+}
89
+
90
+type WebHookHandler struct {
91
+	handler HookHandler
92
+	ctx     api.Context
93
+	name    string
94
+	options *api.PodProxyOptions
95
+	err     error
96
+}
97
+
98
+var _ rest.ConnectHandler = &WebHookHandler{}
99
+
100
+func (h *WebHookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
101
+	h.err = h.handler.ServeHTTP(w, r, h.ctx, h.name, h.options.Path)
102
+	if h.err == nil {
103
+		w.WriteHeader(http.StatusOK)
104
+	}
105
+}
106
+
107
+func (h *WebHookHandler) RequestError() error {
108
+	return h.err
109
+}
... ...
@@ -431,6 +431,12 @@ osc start-build --list-webhooks='all' ruby-sample-build
431 431
 [[ $(osc start-build --list-webhooks='all' ruby-sample-build | grep --text "github") ]]
432 432
 [[ $(osc start-build --list-webhooks='github' ruby-sample-build | grep --text "secret101") ]]
433 433
 [ ! "$(osc start-build --list-webhooks='blah')" ]
434
+
435
+webhook=$(osc start-build --list-webhooks='generic' ruby-sample-build --api-version=v1beta1 | head -n 1)
436
+osc start-build --from-webhook="${webhook}"
437
+webhook=$(osc start-build --list-webhooks='generic' ruby-sample-build --api-version=v1beta3 | head -n 1)
438
+osc start-build --from-webhook="${webhook}"
439
+osc get builds
434 440
 echo "buildConfig: ok"
435 441
 
436 442
 osc create -f test/integration/fixtures/test-buildcli.json
437 443
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package buildconfig
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+	"strings"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/rest"
10
+
11
+	buildapi "github.com/openshift/origin/pkg/build/api"
12
+	"github.com/openshift/origin/pkg/build/client"
13
+	"github.com/openshift/origin/pkg/build/webhook"
14
+)
15
+
16
+func NewWebHookREST(registry Registry, instantiator client.BuildConfigInstantiator, plugins map[string]webhook.Plugin) *rest.WebHook {
17
+	controller := &controller{
18
+		registry:     registry,
19
+		instantiator: instantiator,
20
+		plugins:      plugins,
21
+	}
22
+	return rest.NewWebHook(controller, false)
23
+}
24
+
25
+type controller struct {
26
+	registry     Registry
27
+	instantiator client.BuildConfigInstantiator
28
+	plugins      map[string]webhook.Plugin
29
+}
30
+
31
+// ServeHTTP implements rest.HookHandler
32
+func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request, ctx kapi.Context, name, subpath string) error {
33
+	parts := strings.Split(subpath, "/")
34
+	if len(parts) < 2 {
35
+		return errors.NewBadRequest(fmt.Sprintf("unexpected hook subpath %s", subpath))
36
+	}
37
+	secret, hookType := parts[0], parts[1]
38
+
39
+	plugin, ok := c.plugins[hookType]
40
+	if !ok {
41
+		return errors.NewNotFound("BuildConfigHook", hookType)
42
+	}
43
+
44
+	config, err := c.registry.GetBuildConfig(ctx, name)
45
+	if err != nil {
46
+		// clients should not be able to find information about build configs in the system unless the config exists
47
+		// and the secret matches
48
+		return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name))
49
+	}
50
+
51
+	revision, proceed, err := plugin.Extract(config, secret, "", req)
52
+	switch err {
53
+	case webhook.ErrSecretMismatch, webhook.ErrHookNotEnabled:
54
+		return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", plugin, name))
55
+	case nil:
56
+	default:
57
+		return errors.NewInternalError(fmt.Errorf("hook failed: %v", err))
58
+	}
59
+
60
+	if !proceed {
61
+		return nil
62
+	}
63
+
64
+	request := &buildapi.BuildRequest{
65
+		ObjectMeta: kapi.ObjectMeta{Name: name},
66
+		Revision:   revision,
67
+	}
68
+	if _, err := c.instantiator.Instantiate(config.Namespace, request); err != nil {
69
+		return errors.NewInternalError(fmt.Errorf("could not generate a build: %v", err))
70
+	}
71
+	return nil
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,142 @@
0
+package buildconfig
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+	"net/http/httptest"
6
+	"strings"
7
+	"testing"
8
+
9
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
11
+	_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
12
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/rest"
13
+
14
+	//"github.com/openshift/origin/pkg/api/latest"
15
+	"github.com/openshift/origin/pkg/build/api"
16
+	"github.com/openshift/origin/pkg/build/registry/test"
17
+	"github.com/openshift/origin/pkg/build/webhook"
18
+)
19
+
20
+type buildConfigInstantiator struct {
21
+	Build   *api.Build
22
+	Err     error
23
+	Request *api.BuildRequest
24
+}
25
+
26
+func (i *buildConfigInstantiator) Instantiate(namespace string, request *api.BuildRequest) (*api.Build, error) {
27
+	i.Request = request
28
+	return i.Build, i.Err
29
+}
30
+
31
+type plugin struct {
32
+	Secret, Path string
33
+	Err          error
34
+}
35
+
36
+func (p *plugin) Extract(buildCfg *api.BuildConfig, secret, path string, req *http.Request) (*api.SourceRevision, bool, error) {
37
+	p.Secret, p.Path = secret, path
38
+	return nil, true, p.Err
39
+}
40
+
41
+func newStorage() (*rest.WebHook, *buildConfigInstantiator, *test.BuildConfigRegistry) {
42
+	mockRegistry := &test.BuildConfigRegistry{}
43
+	bci := &buildConfigInstantiator{}
44
+	hook := NewWebHookREST(mockRegistry, bci, map[string]webhook.Plugin{
45
+		"ok":        &plugin{},
46
+		"errsecret": &plugin{Err: webhook.ErrSecretMismatch},
47
+		"errhook":   &plugin{Err: webhook.ErrHookNotEnabled},
48
+		"err":       &plugin{Err: fmt.Errorf("test error")},
49
+	})
50
+	return hook, bci, mockRegistry
51
+}
52
+
53
+func TestNewWebHook(t *testing.T) {
54
+	hook, _, _ := newStorage()
55
+	if out, ok := hook.New().(*kapi.Status); !ok {
56
+		t.Errorf("unexpected new: %#v", out)
57
+	}
58
+}
59
+
60
+func TestConnectWebHook(t *testing.T) {
61
+	testCases := map[string]struct {
62
+		Name   string
63
+		Path   string
64
+		Obj    *api.BuildConfig
65
+		RegErr error
66
+		ErrFn  func(error) bool
67
+		WFn    func(*httptest.ResponseRecorder) bool
68
+	}{
69
+		"hook returns generic error": {
70
+			Name: "test",
71
+			Path: "secret/err/extra",
72
+			ErrFn: func(err error) bool {
73
+				return strings.Contains(err.Error(), "Internal error occurred: hook failed: test error")
74
+			},
75
+		},
76
+		"hook returns unauthorized for bad secret": {
77
+			Name:  "test",
78
+			Path:  "secret/errsecret/extra",
79
+			ErrFn: errors.IsUnauthorized,
80
+		},
81
+		"hook returns unauthorized for bad hook": {
82
+			Name:  "test",
83
+			Path:  "secret/errhook/extra",
84
+			ErrFn: errors.IsUnauthorized,
85
+		},
86
+		"hook returns unauthorized for missing build config": {
87
+			Name:   "test",
88
+			Path:   "secret/errhook/extra",
89
+			RegErr: fmt.Errorf("any old error"),
90
+			ErrFn:  errors.IsUnauthorized,
91
+		},
92
+		"hook returns 200 for ok hook": {
93
+			Name:  "test",
94
+			Path:  "secret/ok/extra",
95
+			Obj:   &api.BuildConfig{ObjectMeta: kapi.ObjectMeta{Name: "test", Namespace: "default"}},
96
+			ErrFn: func(err error) bool { return err == nil },
97
+			WFn: func(w *httptest.ResponseRecorder) bool {
98
+				return w.Code == http.StatusOK
99
+			},
100
+		},
101
+	}
102
+	for k, testCase := range testCases {
103
+		hook, bci, registry := newStorage()
104
+		if testCase.Obj != nil {
105
+			registry.BuildConfig = testCase.Obj
106
+		}
107
+		if testCase.RegErr != nil {
108
+			registry.Err = testCase.RegErr
109
+		}
110
+		handler, err := hook.Connect(kapi.NewDefaultContext(), testCase.Name, &kapi.PodProxyOptions{Path: testCase.Path})
111
+		if err != nil {
112
+			t.Errorf("%s: %v", k, err)
113
+			continue
114
+		}
115
+		w := httptest.NewRecorder()
116
+		handler.ServeHTTP(w, &http.Request{})
117
+		if err := handler.RequestError(); !testCase.ErrFn(err) {
118
+			t.Errorf("%s: unexpected error: %v", k, err)
119
+			continue
120
+		}
121
+		if testCase.WFn != nil && !testCase.WFn(w) {
122
+			t.Errorf("%s: unexpected response: %#v", k, w)
123
+			continue
124
+		}
125
+		if testCase.Obj != nil {
126
+			if bci.Request == nil {
127
+				t.Errorf("%s: instantiator not invoked", k)
128
+				continue
129
+			}
130
+			if bci.Request.Name != testCase.Obj.Name {
131
+				t.Errorf("%s: instantiator incorrect: %#v", k, bci)
132
+				continue
133
+			}
134
+		} else {
135
+			if bci.Request != nil {
136
+				t.Errorf("%s: instantiator should not be invoked: %#v", k, bci)
137
+				continue
138
+			}
139
+		}
140
+	}
141
+}
... ...
@@ -8,12 +8,12 @@
8 8
         "ref": "refs/heads/master",
9 9
         "commit": "2602ace61490de0513dfbd7c7de949356cf9bd17",
10 10
         "author": {
11
-          "name": "Martin Nagy",
12
-          "email": "nagy.martin@gmail.com"
11
+          "name": "Joe Smith",
12
+          "email": "joe.smith@gmail.com"
13 13
         },
14 14
         "committer": {
15
-          "name": "Martin Nagy",
16
-          "email": "nagy.martin@gmail.com"
15
+          "name": "Joe Smith",
16
+          "email": "joe.smith@gmail.com"
17 17
         },
18 18
         "message": "Merge pull request #31 from mnagy/prepare_for_new_mysql_image\n\nPrepare for new openshift/mysql-55-centos7 image"
19 19
       }
... ...
@@ -24,11 +24,11 @@ func New() *WebHookPlugin {
24 24
 func (p *WebHookPlugin) Extract(buildCfg *api.BuildConfig, secret, path string, req *http.Request) (revision *api.SourceRevision, proceed bool, err error) {
25 25
 	trigger, ok := webhook.FindTriggerPolicy(api.GenericWebHookBuildTriggerType, buildCfg)
26 26
 	if !ok {
27
-		err = fmt.Errorf("BuildConfig %s does not support the Generic webhook trigger type", buildCfg.Name)
27
+		err = webhook.ErrHookNotEnabled
28 28
 		return
29 29
 	}
30 30
 	if trigger.GenericWebHook.Secret != secret {
31
-		err = fmt.Errorf("Secret does not match for BuildConfig %s", buildCfg.Name)
31
+		err = webhook.ErrSecretMismatch
32 32
 		return
33 33
 	}
34 34
 	if err = verifyRequest(req); err != nil {
... ...
@@ -38,11 +38,11 @@ type pushEvent struct {
38 38
 func (p *WebHook) Extract(buildCfg *api.BuildConfig, secret, path string, req *http.Request) (revision *api.SourceRevision, proceed bool, err error) {
39 39
 	trigger, ok := webhook.FindTriggerPolicy(api.GithubWebHookBuildTriggerType, buildCfg)
40 40
 	if !ok {
41
-		err = fmt.Errorf("BuildConfig %s does not support the Github webhook trigger type", buildCfg.Name)
41
+		err = webhook.ErrHookNotEnabled
42 42
 		return
43 43
 	}
44 44
 	if trigger.GithubWebHook.Secret != secret {
45
-		err = fmt.Errorf("Secret does not match for BuildConfig %s", buildCfg.Name)
45
+		err = webhook.ErrSecretMismatch
46 46
 		return
47 47
 	}
48 48
 	if err = verifyRequest(req); err != nil {
... ...
@@ -1,11 +1,17 @@
1 1
 package webhook
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"strings"
5 6
 
6 7
 	"github.com/openshift/origin/pkg/build/api"
7 8
 )
8 9
 
10
+var (
11
+	ErrSecretMismatch = fmt.Errorf("the provided secret does not match")
12
+	ErrHookNotEnabled = fmt.Errorf("the specified hook is not enabled")
13
+)
14
+
9 15
 // GitRefMatches determines if the ref from a webhook event matches a build configuration
10 16
 func GitRefMatches(eventRef, configRef string) bool {
11 17
 	const RefPrefix = "refs/heads/"
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"net/url"
6 6
 
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
8 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
... ...
@@ -69,11 +70,21 @@ func (c *buildConfigs) Get(name string) (result *buildapi.BuildConfig, err error
69 69
 // WebHookURL returns the URL for the provided build config name and trigger policy, or ErrTriggerIsNotAWebHook
70 70
 // if the trigger is not a webhook type.
71 71
 func (c *buildConfigs) WebHookURL(name string, trigger *buildapi.BuildTriggerPolicy) (*url.URL, error) {
72
+	if kapi.PreV1Beta3(c.r.APIVersion()) {
73
+		switch {
74
+		case trigger.GenericWebHook != nil:
75
+			return c.r.Get().Namespace(c.ns).Resource("buildConfigHooks").Name(name).Suffix(trigger.GenericWebHook.Secret, "generic").URL(), nil
76
+		case trigger.GithubWebHook != nil:
77
+			return c.r.Get().Namespace(c.ns).Resource("buildConfigHooks").Name(name).Suffix(trigger.GithubWebHook.Secret, "github").URL(), nil
78
+		default:
79
+			return nil, ErrTriggerIsNotAWebHook
80
+		}
81
+	}
72 82
 	switch {
73 83
 	case trigger.GenericWebHook != nil:
74
-		return c.r.Get().Namespace(c.ns).Resource("buildConfigHooks").Name(name).Suffix(trigger.GenericWebHook.Secret, "generic").URL(), nil
84
+		return c.r.Get().Namespace(c.ns).Resource("buildConfigs").Name(name).SubResource("webhooks").Suffix(trigger.GenericWebHook.Secret, "generic").URL(), nil
75 85
 	case trigger.GithubWebHook != nil:
76
-		return c.r.Get().Namespace(c.ns).Resource("buildConfigHooks").Name(name).Suffix(trigger.GithubWebHook.Secret, "github").URL(), nil
86
+		return c.r.Get().Namespace(c.ns).Resource("buildConfigs").Name(name).SubResource("webhooks").Suffix(trigger.GithubWebHook.Secret, "github").URL(), nil
77 87
 	default:
78 88
 		return nil, ErrTriggerIsNotAWebHook
79 89
 	}
... ...
@@ -43,6 +43,7 @@ const (
43 43
 	RegistryRoleName          = "system:registry"
44 44
 	InternalComponentRoleName = "system:component"
45 45
 	DeleteTokensRoleName      = "system:delete-tokens"
46
+	WebHooksRoleName          = "system:webhook"
46 47
 
47 48
 	OpenshiftSharedResourceViewRoleName = "shared-resource-viewer"
48 49
 )
... ...
@@ -58,6 +59,7 @@ const (
58 58
 	StatusCheckerRoleBindingName     = StatusCheckerRoleName + "-binding"
59 59
 	RouterRoleBindingName            = RouterRoleName + "s"
60 60
 	RegistryRoleBindingName          = RegistryRoleName + "s"
61
+	WebHooksRoleBindingName          = WebHooksRoleName + "s"
61 62
 
62 63
 	OpenshiftSharedResourceViewRoleBindingName = OpenshiftSharedResourceViewRoleName + "s"
63 64
 )
... ...
@@ -180,6 +180,17 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
180 180
 				},
181 181
 			},
182 182
 		},
183
+		{
184
+			ObjectMeta: kapi.ObjectMeta{
185
+				Name: WebHooksRoleName,
186
+			},
187
+			Rules: []authorizationapi.PolicyRule{
188
+				{
189
+					Verbs:     util.NewStringSet("get", "create"),
190
+					Resources: util.NewStringSet("buildconfigs/webhooks"),
191
+				},
192
+			},
193
+		},
183 194
 	}
184 195
 }
185 196
 
... ...
@@ -283,5 +294,14 @@ func GetBootstrapClusterRoleBindings() []authorizationapi.ClusterRoleBinding {
283 283
 			},
284 284
 			Groups: util.NewStringSet(RegistryGroup),
285 285
 		},
286
+		{
287
+			ObjectMeta: kapi.ObjectMeta{
288
+				Name: WebHooksRoleBindingName,
289
+			},
290
+			RoleRef: kapi.ObjectReference{
291
+				Name: WebHooksRoleName,
292
+			},
293
+			Groups: util.NewStringSet(AuthenticatedGroup, UnauthenticatedGroup),
294
+		},
286 295
 	}
287 296
 }
... ...
@@ -232,11 +232,22 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin
232 232
 	}
233 233
 	projectRequestStorage := projectrequeststorage.NewREST(c.Options.ProjectRequestConfig.ProjectRequestMessage, namespace, templateName, c.PrivilegedLoopbackOpenShiftClient)
234 234
 
235
+	bcClient, _ := c.BuildControllerClients()
236
+	buildConfigWebHooks := buildconfigregistry.NewWebHookREST(
237
+		buildEtcd,
238
+		buildclient.NewOSClientBuildConfigInstantiatorClient(bcClient),
239
+		map[string]webhook.Plugin{
240
+			"generic": generic.New(),
241
+			"github":  github.New(),
242
+		},
243
+	)
244
+
235 245
 	// initialize OpenShift API
236 246
 	storage := map[string]rest.Storage{
237 247
 		"builds":                   buildregistry.NewREST(buildEtcd),
238 248
 		"builds/clone":             buildClone,
239 249
 		"buildConfigs":             buildconfigregistry.NewREST(buildEtcd),
250
+		"buildConfigs/webhooks":    buildConfigWebHooks,
240 251
 		"buildConfigs/instantiate": buildConfigInstantiate,
241 252
 		"buildLogs":                buildlogregistry.NewREST(buildEtcd, c.BuildLogClient(), kubeletClient),
242 253
 		"builds/log":               buildlogregistry.NewREST(buildEtcd, c.BuildLogClient(), kubeletClient),
... ...
@@ -387,12 +398,9 @@ func (c *MasterConfig) InstallUnprotectedAPI(container *restful.Container) []str
387 387
 			"github":  github.New(),
388 388
 		})
389 389
 
390
-	// TODO: go-restfulize this
390
+	// TODO: deprecated, remove when v1beta1 is dropped
391 391
 	prefix := OpenShiftAPIPrefixV1Beta1 + "/buildConfigHooks/"
392 392
 	container.Handle(prefix, http.StripPrefix(prefix, handler))
393
-	// TODO: broken
394
-	prefix = OpenShiftAPIPrefixV1Beta3 + "/buildconfighooks/"
395
-	container.Handle(prefix, http.StripPrefix(prefix, handler))
396 393
 	return []string{}
397 394
 }
398 395
 
... ...
@@ -32,6 +32,7 @@ import (
32 32
 	buildconfigregistry "github.com/openshift/origin/pkg/build/registry/buildconfig"
33 33
 	buildetcd "github.com/openshift/origin/pkg/build/registry/etcd"
34 34
 	"github.com/openshift/origin/pkg/build/webhook"
35
+	"github.com/openshift/origin/pkg/build/webhook/generic"
35 36
 	"github.com/openshift/origin/pkg/build/webhook/github"
36 37
 	osclient "github.com/openshift/origin/pkg/client"
37 38
 	"github.com/openshift/origin/pkg/image/registry/image"
... ...
@@ -240,10 +241,20 @@ func NewTestBuildOpenshift(t *testing.T) *testBuildOpenshift {
240 240
 	}
241 241
 	buildClone, buildConfigInstantiate := buildgenerator.NewREST(buildGenerator)
242 242
 
243
+	buildConfigWebHooks := buildconfigregistry.NewWebHookREST(
244
+		buildEtcd,
245
+		buildclient.NewOSClientBuildConfigInstantiatorClient(osClient),
246
+		map[string]webhook.Plugin{
247
+			"generic": generic.New(),
248
+			"github":  github.New(),
249
+		},
250
+	)
251
+
243 252
 	storage := map[string]rest.Storage{
244 253
 		"builds":                   buildregistry.NewREST(buildEtcd),
245 254
 		"builds/clone":             buildClone,
246 255
 		"buildConfigs":             buildconfigregistry.NewREST(buildEtcd),
256
+		"buildConfigs/webhooks":    buildConfigWebHooks,
247 257
 		"buildConfigs/instantiate": buildConfigInstantiate,
248 258
 		"imageStreams":             imageStreamStorage,
249 259
 		"imageStreams/status":      imageStreamStatus,
... ...
@@ -277,7 +288,8 @@ func NewTestBuildOpenshift(t *testing.T) *testBuildOpenshift {
277 277
 	osMux.Handle(openshift.whPrefix, http.StripPrefix(openshift.whPrefix,
278 278
 		webhook.NewController(bcClient, buildclient.NewOSClientBuildConfigInstantiatorClient(osClient),
279 279
 			map[string]webhook.Plugin{
280
-				"github": github.New(),
280
+				"github":  github.New(),
281
+				"generic": generic.New(),
281 282
 			})))
282 283
 
283 284
 	bcFactory := buildcontrollerfactory.BuildControllerFactory{
... ...
@@ -89,18 +89,24 @@ func TestWebhookGithubPushWithImage(t *testing.T) {
89 89
 	}
90 90
 	defer watch.Stop()
91 91
 
92
-	// trigger build event sending push notification
93
-	postFile(clusterAdminClient.RESTClient.Client, "push", "pushevent.json", clusterAdminClientConfig.Host+whPrefix+"pushbuild/secret101/github?namespace="+testutil.Namespace(), http.StatusOK, t)
92
+	for _, s := range []string{
93
+		whPrefix + "pushbuild/secret101/github?namespace=" + testutil.Namespace(),
94
+		"/osapi/v1beta1/buildConfigs/pushbuild/webhooks/secret101/github?namespace=" + testutil.Namespace(),
95
+	} {
94 96
 
95
-	event := <-watch.ResultChan()
96
-	actual := event.Object.(*buildapi.Build)
97
+		// trigger build event sending push notification
98
+		postFile(clusterAdminClient.RESTClient.Client, "push", "pushevent.json", clusterAdminClientConfig.Host+s, http.StatusOK, t)
97 99
 
98
-	if actual.Status != buildapi.BuildStatusNew {
99
-		t.Errorf("Expected %s, got %s", buildapi.BuildStatusNew, actual.Status)
100
-	}
100
+		event := <-watch.ResultChan()
101
+		actual := event.Object.(*buildapi.Build)
102
+
103
+		if actual.Status != buildapi.BuildStatusNew {
104
+			t.Errorf("Expected %s, got %s", buildapi.BuildStatusNew, actual.Status)
105
+		}
101 106
 
102
-	if actual.Parameters.Strategy.DockerStrategy.From.Name != "originalImage" {
103
-		t.Errorf("Expected %s, got %s", "originalImage", actual.Parameters.Strategy.DockerStrategy.From.Name)
107
+		if actual.Parameters.Strategy.DockerStrategy.From.Name != "originalImage" {
108
+			t.Errorf("Expected %s, got %s", "originalImage", actual.Parameters.Strategy.DockerStrategy.From.Name)
109
+		}
104 110
 	}
105 111
 }
106 112
 
... ...
@@ -168,18 +174,24 @@ func TestWebhookGithubPushWithImageStream(t *testing.T) {
168 168
 	}
169 169
 	defer watch.Stop()
170 170
 
171
-	// trigger build event sending push notification
172
-	postFile(clusterAdminClient.RESTClient.Client, "push", "pushevent.json", clusterAdminClientConfig.Host+whPrefix+"pushbuild/secret101/github?namespace="+testutil.Namespace(), http.StatusOK, t)
171
+	for _, s := range []string{
172
+		whPrefix + "pushbuild/secret101/github?namespace=" + testutil.Namespace(),
173
+		"/osapi/v1beta1/buildConfigs/pushbuild/webhooks/secret101/github?namespace=" + testutil.Namespace(),
174
+	} {
173 175
 
174
-	event := <-watch.ResultChan()
175
-	actual := event.Object.(*buildapi.Build)
176
+		// trigger build event sending push notification
177
+		postFile(clusterAdminClient.RESTClient.Client, "push", "pushevent.json", clusterAdminClientConfig.Host+s, http.StatusOK, t)
176 178
 
177
-	if actual.Status != buildapi.BuildStatusNew {
178
-		t.Errorf("Expected %s, got %s", buildapi.BuildStatusNew, actual.Status)
179
-	}
179
+		event := <-watch.ResultChan()
180
+		actual := event.Object.(*buildapi.Build)
181
+
182
+		if actual.Status != buildapi.BuildStatusNew {
183
+			t.Errorf("Expected %s, got %s", buildapi.BuildStatusNew, actual.Status)
184
+		}
180 185
 
181
-	if actual.Parameters.Strategy.STIStrategy.From.Name != "registry:3000/integration/imageStream:success" {
182
-		t.Errorf("Expected %s, got %s", "registry:3000/integration-test/imageStream:success", actual.Parameters.Strategy.STIStrategy.From.Name)
186
+		if actual.Parameters.Strategy.STIStrategy.From.Name != "registry:3000/integration/imageStream:success" {
187
+			t.Errorf("Expected %s, got %s", "registry:3000/integration-test/imageStream:success", actual.Parameters.Strategy.STIStrategy.From.Name)
188
+		}
183 189
 	}
184 190
 }
185 191
 
... ...
@@ -204,17 +216,22 @@ func TestWebhookGithubPing(t *testing.T) {
204 204
 	}
205 205
 	defer watch.Stop()
206 206
 
207
-	// trigger build event sending push notification
208
-	postFile(&http.Client{}, "ping", "pingevent.json", openshift.server.URL+openshift.whPrefix+"pushbuild/secret101/github?namespace="+testutil.Namespace(), http.StatusOK, t)
209
-
210
-	// TODO: improve negative testing
211
-	timer := time.NewTimer(time.Second / 2)
212
-	select {
213
-	case <-timer.C:
214
-		// nothing should happen
215
-	case event := <-watch.ResultChan():
216
-		build := event.Object.(*buildapi.Build)
217
-		t.Fatalf("Unexpected build created: %#v", build)
207
+	for _, s := range []string{
208
+		openshift.whPrefix + "pushbuild/secret101/github?namespace=" + testutil.Namespace(),
209
+		"/osapi/v1beta1/buildConfigs/pushbuild/webhooks/secret101/github?namespace=" + testutil.Namespace(),
210
+	} {
211
+		// trigger build event sending push notification
212
+		postFile(&http.Client{}, "ping", "pingevent.json", openshift.server.URL+s, http.StatusOK, t)
213
+
214
+		// TODO: improve negative testing
215
+		timer := time.NewTimer(time.Second / 2)
216
+		select {
217
+		case <-timer.C:
218
+			// nothing should happen
219
+		case event := <-watch.ResultChan():
220
+			build := event.Object.(*buildapi.Build)
221
+			t.Fatalf("Unexpected build created: %#v", build)
222
+		}
218 223
 	}
219 224
 }
220 225