forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
controller.go
141 lines (124 loc) · 4.24 KB
/
controller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package webhook
import (
"fmt"
"net/http"
"strings"
"github.com/golang/glog"
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
buildapi "github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
)
// Plugin for Webhook verification is dependent on the sending side, it can be
// eg. github, bitbucket or else, so there must be a separate Plugin
// instance for each webhook provider.
type Plugin interface {
// Method extracts build information and returns:
// - newly created build object or nil if default is to be created
// - information whether to trigger the build itself
// - eventual error.
Extract(buildCfg *buildapi.BuildConfig, secret, path string, req *http.Request) (*buildapi.SourceRevision, bool, error)
}
// controller used for processing webhook requests.
type controller struct {
buildConfigInstantiator buildclient.BuildConfigInstantiator
buildConfigGetter buildclient.BuildConfigGetter
plugins map[string]Plugin
}
// urlVars holds parsed URL parts.
type urlVars struct {
namespace string
buildConfigName string
secret string
plugin string
path string
}
// NewController creates new webhook controller and feed it with provided plugins.
func NewController(bcg buildclient.BuildConfigGetter,
bci buildclient.BuildConfigInstantiator, plugins map[string]Plugin) http.Handler {
return &controller{
buildConfigGetter: bcg,
buildConfigInstantiator: bci,
plugins: plugins,
}
}
// ServeHTTP main REST service method.
func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request) {
uv, err := parseURL(req)
if err != nil {
glog.V(2).Infof("Failed to parse request URL: %v", err)
notFound(w, err.Error())
return
}
buildCfg, err := c.buildConfigGetter.Get(uv.namespace, uv.buildConfigName)
if err != nil {
glog.V(2).Infof("Failed to get BuildConfig %s/%s: %v", uv.namespace, uv.buildConfigName, err)
badRequest(w, err.Error())
return
}
plugin, ok := c.plugins[uv.plugin]
if !ok {
glog.V(2).Infof("Plugin %s not found", uv.plugin)
notFound(w, "Plugin ", uv.plugin, " not found")
return
}
revision, proceed, err := plugin.Extract(buildCfg, uv.secret, uv.path, req)
if err != nil {
glog.V(2).Infof("Failed to extract information from webhook: %v", err)
badRequest(w, err.Error())
return
}
if !proceed {
return
}
request := &buildapi.BuildRequest{
ObjectMeta: kapi.ObjectMeta{Name: buildCfg.Name},
Revision: revision,
}
if _, err := c.buildConfigInstantiator.Instantiate(uv.namespace, request); err != nil {
glog.V(2).Infof("Failed to generate new Build from BuildConfig %s/%s: %v", buildCfg.Namespace, buildCfg.Name, err)
badRequest(w, err.Error())
}
}
// parseURL retrieves the namespace from the query parameters and returns a context wrapping the namespace,
// the parameters for the webhook call, and an error.
// according to the docs (http://godoc.org/code.google.com/p/go.net/context) ctx is not supposed to be wrapped in another object
func parseURL(req *http.Request) (uv urlVars, err error) {
url := req.URL.Path
parts := splitPath(url)
if len(parts) < 3 {
err = fmt.Errorf("unexpected URL %s", url)
return
}
uv = urlVars{
namespace: kapi.NamespaceDefault,
buildConfigName: parts[0],
secret: parts[1],
plugin: parts[2],
path: "",
}
if len(parts) > 3 {
uv.path = strings.Join(parts[3:], "/")
}
// TODO for now, we pull namespace from query parameter, but according to spec, it must go in resource path in future PR
// if a namespace if specified, it's always used.
// for list/watch operations, a namespace is not required if omitted.
// for all other operations, if namespace is omitted, we will default to default namespace.
namespace := req.URL.Query().Get("namespace")
if len(namespace) > 0 {
uv.namespace = namespace
}
return
}
func splitPath(path string) []string {
path = strings.Trim(path, "/")
if path == "" {
return []string{}
}
return strings.Split(path, "/")
}
func notFound(w http.ResponseWriter, args ...string) {
http.Error(w, strings.Join(args, ""), http.StatusNotFound)
}
func badRequest(w http.ResponseWriter, args ...string) {
http.Error(w, strings.Join(args, ""), http.StatusBadRequest)
}