1
1
package main
2
2
3
+ import (
4
+ "context"
5
+ "log"
6
+ "log/slog"
7
+ "net/http"
8
+
9
+ "github.com/google/go-github/v69/github"
10
+ "github.com/gravitational/shared-workflows/libs/github/webhook"
11
+ "github.com/gravitational/trace"
12
+ "golang.org/x/sync/errgroup"
13
+ )
14
+
15
+ var logger = slog .Default ()
16
+
3
17
// Process:
4
18
// 1. Take in events from CI/CD systems
5
19
// 2. Extract common information
@@ -28,19 +42,22 @@ func main() {
28
42
eventSources := []EventSource {
29
43
NewGitHubEventSource (processor ),
30
44
}
31
-
32
45
for _ , eventSource := range eventSources {
33
46
_ = eventSource .Setup ()
34
47
}
35
48
36
- done := make (chan struct {}) // TODO replace with error?
49
+ // 3. Start event sources
50
+ eg , ctx := errgroup .WithContext (context .Background ())
37
51
for _ , eventSource := range eventSources {
38
- _ = eventSource .Run (done )
52
+ eg .Go (func () error {
53
+ return eventSource .Run (ctx )
54
+ })
39
55
}
40
56
41
57
// Block until an event source has a fatal error
42
- <- done
43
- close (done )
58
+ if err := eg .Wait (); err != nil {
59
+ log .Fatal (err )
60
+ }
44
61
}
45
62
46
63
// This contains information needed to process a request
@@ -99,12 +116,15 @@ type EventSource interface {
99
116
Setup () error
100
117
101
118
// Handle actual requests. This should not block.
102
- Run (chan struct {} ) error
119
+ Run (ctx context. Context ) error
103
120
}
104
121
105
122
type GitHubEventSource struct {
106
123
processor ApprovalProcessor
107
- // TODO
124
+
125
+ deployReviewChan chan * github.DeploymentReviewEvent
126
+ addr string
127
+ srv * http.Server
108
128
}
109
129
110
130
func NewGitHubEventSource (processor ApprovalProcessor ) * GitHubEventSource {
@@ -114,43 +134,73 @@ func NewGitHubEventSource(processor ApprovalProcessor) *GitHubEventSource {
114
134
// Setup GH client, webhook secret, etc.
115
135
// https://github.com/go-playground/webhooks may help here
116
136
func (ghes * GitHubEventSource ) Setup () error {
117
- // TODO
137
+ deployReviewChan := make (chan * github.DeploymentReviewEvent )
138
+ ghes .deployReviewChan = deployReviewChan
139
+
140
+ mux := http .NewServeMux ()
141
+ eventProcessor := webhook .EventHandlerFunc (func (event interface {}) error {
142
+ switch event := event .(type ) {
143
+ case * github.DeploymentReviewEvent :
144
+ deployReviewChan <- event
145
+ return nil
146
+ default :
147
+ return trace .Errorf ("unknown event type: %T" , event )
148
+ }
149
+ })
150
+ mux .Handle ("/webhook" , webhook .NewHandler (
151
+ eventProcessor ,
152
+ webhook .WithSecretToken ([]byte ("secret-token" )), // TODO: get from config
153
+ webhook .WithLogger (logger ),
154
+ ))
155
+
156
+ ghes .srv = & http.Server {
157
+ Addr : ghes .addr ,
158
+ Handler : mux ,
159
+ }
160
+
118
161
return nil
119
162
}
120
163
121
164
// Take incoming events and respond to them
122
- func (ghes * GitHubEventSource ) Run (done chan struct {}) error {
123
- // If anything errors, deny the request. For safety, maybe `defer`
124
- // the "response" function?
125
- go func () {
126
- // Notify the service that the listener is completely done.
127
- // Normally this should only be hit if there is a fatal error
128
- defer func () { done <- struct {}{} }()
165
+ func (ghes * GitHubEventSource ) Run (ctx context.Context ) error {
166
+ errc := make (chan error )
129
167
130
- // Incoming webhook payloads
131
- // This should be closed by the webhook listener func
132
- payloads := make ( chan interface {} )
133
-
134
- // Listen for webhook calls
135
- go ghes . listenForPayloads ( payloads , done )
168
+ // Start the HTTP server
169
+ go func () {
170
+ logger . Info ( "Listening for GitHub Webhooks" , "address" , ghes . addr )
171
+ errc <- ghes . srv . ListenAndServe ()
172
+ close ( errc )
173
+ }( )
136
174
137
- for payload := range payloads {
138
- go ghes .processWebhookPayload (payload , done )
175
+ // Process incoming events
176
+ go func () {
177
+ defer close (ghes .deployReviewChan )
178
+ logger .Info ("Starting GitHub event processor" )
179
+ for {
180
+ select {
181
+ case <- ctx .Done ():
182
+ return
183
+ case deployReview := <- ghes .deployReviewChan :
184
+ // Process the event
185
+ go ghes .processDeploymentReviewEvent (deployReview )
186
+ }
139
187
}
140
188
}()
141
189
142
- return nil
143
- }
144
-
145
- // Listen for incoming webhook events. Register HTTP routes, start server, etc. Long running, blocking.
146
- func (ghes * GitHubEventSource ) listenForPayloads (payloads chan interface {}, done chan struct {}) {
147
- // Once a call is received, it should return a 200 response immediately.
190
+ var err error
191
+ // This will block until an error occurs or the context is done
192
+ select {
193
+ case err = <- errc :
194
+ ghes .srv .Shutdown (context .Background ()) // Ignore error - we're already handling one
195
+ case <- ctx .Done ():
196
+ err = ghes .srv .Shutdown (context .Background ())
197
+ <- errc // flush the error channel to avoid a goroutine leak
198
+ }
148
199
149
- // TODO
200
+ return trace . Wrap ( err )
150
201
}
151
202
152
- // Given an event, approve or deny it. This is a long running, blocking function.
153
- func (ghes * GitHubEventSource ) processWebhookPayload (payload interface {}, done chan struct {}) {
203
+ func (ghes * GitHubEventSource ) processDeploymentReviewEvent (payload * github.DeploymentReviewEvent ) error {
154
204
// Do GitHub-specific checks. Don't approve based off ot this - just deny
155
205
// if one fails.
156
206
automatedDenial , err := ghes .performAutomatedChecks (payload )
@@ -170,6 +220,11 @@ func (ghes *GitHubEventSource) processWebhookPayload(payload interface{}, done c
170
220
}
171
221
172
222
_ = ghes .respondToDeployRequest (true , payload )
223
+ return nil
224
+ }
225
+
226
+ // Given an event, approve or deny it. This is a long running, blocking function.
227
+ func (ghes * GitHubEventSource ) processWebhookPayload (payload interface {}, done chan struct {}) {
173
228
}
174
229
175
230
// Turns GH-specific information into "common" information for the approver
@@ -183,10 +238,13 @@ func (ghes *GitHubEventSource) convertWebhookPayloadToEvent(payload interface{})
183
238
184
239
// Performs approval checks that are GH-specific. This should only be used to deny requests,
185
240
// never approve them.
186
- func (ghes * GitHubEventSource ) performAutomatedChecks (payload interface {} ) (pass bool , err error ) {
241
+ func (ghes * GitHubEventSource ) performAutomatedChecks (payload * github. DeploymentReviewEvent ) (pass bool , err error ) {
187
242
// Verify request is from Gravitational org repo
188
243
// Verify request is from Gravitational org member
189
244
// See RFD for additional examples
245
+ if * payload .Organization .Login != "gravitational" {
246
+ return true , nil
247
+ }
190
248
191
249
return false , nil
192
250
}
0 commit comments