| 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 |
|