forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
controller.go
138 lines (122 loc) · 4.06 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
package webhook
import (
"fmt"
"net/http"
"strings"
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
buildutil "github.com/openshift/origin/pkg/build/util"
osclient "github.com/openshift/origin/pkg/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 *api.BuildConfig, secret, path string, req *http.Request) (*api.SourceRevision, bool, error)
}
// controller used for processing webhook requests.
type controller struct {
buildCreator buildclient.BuildCreator
buildConfigGetter buildclient.BuildConfigGetter
imageRepoGetter osclient.ImageRepositoryNamespaceGetter
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(buildConfigGetter buildclient.BuildConfigGetter, buildCreator buildclient.BuildCreator, imageRepoGetter osclient.ImageRepositoryNamespaceGetter, plugins map[string]Plugin) http.Handler {
return &controller{
buildConfigGetter: buildConfigGetter,
buildCreator: buildCreator,
imageRepoGetter: imageRepoGetter,
plugins: plugins,
}
}
// ServeHTTP main REST service method.
func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request) {
uv, err := parseURL(req)
if err != nil {
notFound(w, err.Error())
return
}
buildCfg, err := c.buildConfigGetter.Get(uv.namespace, uv.buildConfigName)
if err != nil {
badRequest(w, err.Error())
return
}
plugin, ok := c.plugins[uv.plugin]
if !ok {
notFound(w, "Plugin ", uv.plugin, " not found")
return
}
revision, proceed, err := plugin.Extract(buildCfg, uv.secret, uv.path, req)
if err != nil {
badRequest(w, err.Error())
return
}
if !proceed {
return
}
build, err := buildutil.GenerateBuildWithImageTag(buildCfg, revision, c.imageRepoGetter)
if err != nil {
badRequest(w, err.Error())
return
}
if err := c.buildCreator.Create(uv.namespace, build); err != nil {
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)
}