1 package plugin
2
3 import (
4 "context"
5 "fmt"
6 "io/fs"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "edge-infra.dev/pkg/lib/logging"
12
13 "github.com/google/go-cmp/cmp"
14 "github.com/google/go-github/v47/github"
15 "gopkg.in/yaml.v2"
16 utilerrors "k8s.io/apimachinery/pkg/util/errors"
17
18 "edge-infra.dev/pkg/f8n/devinfra/jack/plugin/options"
19 )
20
21 var (
22 allHandlers = map[string]string{}
23 issueHandlers = map[string]IssueHandler{}
24 issueCommentHandlers = map[string]IssueCommentHandler{}
25 pullRequestHandlers = map[string]PullRequestHandler{}
26 pushEventHandlers = map[string]PushEventHandler{}
27 reviewEventHandlers = map[string]ReviewEventHandler{}
28 reviewCommentEventHandlers = map[string]ReviewCommentEventHandler{}
29 statusEventHandlers = map[string]StatusEventHandler{}
30 workflowEventHandlers = map[string]WorkflowEventHandler{}
31 workflowRunEventHandlers = map[string]WorkflowRunEventHandler{}
32 timeEventHandlers = map[string]TimeEventHandler{}
33 )
34
35 func init() {
36
37 }
38
39 type Config struct {
40 Plugins Plugins `yaml:"plugins,omitempty"`
41 PluginOptions options.PluginOptions `yaml:"plugin_options"`
42 }
43
44
45 type Plugins map[string]OrgRepoPlugins
46
47 type ProjectPlugin struct {
48 IDs []int64 `json:"ids,omitempty"`
49 }
50
51
52
53 type UploadJobBucket struct {
54 Name string `yaml:"name"`
55 Bucket string `yaml:"bucket"`
56
57
58 }
59
60 type EpicsPlugin struct{}
61 type IssuesPlugin struct{}
62 type PRPlugin struct{}
63 type DraftPRPlugin struct{}
64 type JackPlugin struct{}
65 type SizePlugin struct{}
66
67 type OrgRepoPlugins struct {
68 Jack JackPlugin `json:"jack,omitempty"`
69 Epics EpicsPlugin `json:"epics,omitempty"`
70 Issues IssuesPlugin `json:"issues,omitempty"`
71 PR PRPlugin `json:"pr,omitempty"`
72 Project ProjectPlugin `json:"project,omitempty"`
73 Uploadjob []UploadJobBucket `json:"uploadjob,omitempty"`
74 DraftPR DraftPRPlugin `json:"draftpr,omitempty"`
75 Size SizePlugin `json:"size,omitempty"`
76 Enabled []string `json:"enabled,omitempty"`
77 Crons Crons `json:"crons,omitempty"`
78 Webhooks Webhooks `json:"webhooks,omitempty"`
79 }
80
81
82 type OrgRepo struct {
83 Org string
84 Repo string
85 }
86
87 type HandlerParams struct {
88 Ctx context.Context
89 Org string
90 Repo string
91 Client GithubClientInterface
92 ClientV4 GithubV4ClientInterface
93 Log logging.EdgeLogger
94 Params OrgRepoPlugins
95 }
96
97
98 func OrgRepoToString(org, repo string) string {
99 return fmt.Sprintf("%s/%s", org, repo)
100 }
101
102
103 func OrgRepoFromString(orgRepo string) (string, string, error) {
104 orgRepoArr := strings.Split(orgRepo, "/")
105 if len(orgRepoArr) != 2 {
106 return "", "", fmt.Errorf("org/repo string is incorrect %s ", orgRepo)
107 }
108 return orgRepoArr[0], orgRepoArr[1], nil
109 }
110
111 type Crons map[string]Cron
112
113 type Cron struct {
114 Org string `yaml:"org"`
115 Repo string `yaml:"repo"`
116 Time string `yaml:"time"`
117 }
118
119 type Webhooks map[string]Webhook
120
121 type Webhook struct {
122 Endpoint string `yaml:"endpoint"`
123 }
124
125 type Configuration struct {
126 Plugins Plugins `json:"plugins,omitempty"`
127
128 }
129
130
131 type ClientAgent struct {
132 GitHubClient github.Client
133 }
134
135
136 type ConfigAgent struct {
137
138 configuration *Configuration
139 AllPlugins bool
140 }
141
142
143 func (pa *ConfigAgent) GetRegisteredPlugins() []string {
144 names := []string{}
145 for plugin := range allHandlers {
146 names = append(names, plugin)
147 }
148 return names
149 }
150
151 func (pa *ConfigAgent) IsOrgRepoInList(owner, repo string) bool {
152 _, ok := pa.configuration.Plugins[OrgRepoToString(owner, repo)]
153 return ok
154 }
155
156
157 func (pa *ConfigAgent) getPlugins(owner, repo string) []string {
158 return pa.configuration.Plugins[OrgRepoToString(owner, repo)].Enabled
159 }
160
161
162 func (pa *ConfigAgent) GetPlugins() Plugins {
163 return pa.configuration.Plugins
164 }
165
166
167
168
169
170
171
172
173 func (pa *ConfigAgent) GetPluginParams(owner, repo string) OrgRepoPlugins {
174 return pa.configuration.Plugins[OrgRepoToString(owner, repo)]
175 }
176
177
178 func (pa *ConfigAgent) IssueHandlers(o, r string) map[string]IssueHandler {
179
180
181
182 hs := map[string]IssueHandler{}
183 for _, p := range pa.getPlugins(o, r) {
184 if h, ok := issueHandlers[p]; ok {
185 hs[p] = h
186 }
187 }
188
189 return hs
190 }
191
192
193 func (pa *ConfigAgent) ExecuteIssueHandlers(hp HandlerParams, e github.IssuesEvent) {
194 p := pa.IssueHandlers(hp.Org, hp.Repo)
195 for plugins := range p {
196 p[plugins](hp, e)
197 }
198 }
199
200
201 func (pa *ConfigAgent) IssueCommentHandlers(o, r string) map[string]IssueCommentHandler {
202
203
204
205 hs := map[string]IssueCommentHandler{}
206 for _, p := range pa.getPlugins(o, r) {
207 if h, ok := issueCommentHandlers[p]; ok {
208 hs[p] = h
209 }
210 }
211
212 return hs
213 }
214
215
216 func (pa *ConfigAgent) ExecuteIssueCommentHandlers(hp HandlerParams, event github.IssueCommentEvent) {
217 p := pa.IssueCommentHandlers(hp.Org, hp.Repo)
218 for plugins := range p {
219 p[plugins](hp, event)
220 }
221 }
222
223
224 func (pa *ConfigAgent) PullRequestHandlers(owner, repo string) map[string]PullRequestHandler {
225
226
227
228 hs := map[string]PullRequestHandler{}
229 for _, p := range pa.getPlugins(owner, repo) {
230 if h, ok := pullRequestHandlers[p]; ok {
231 hs[p] = h
232 }
233 }
234
235 return hs
236 }
237
238
239 func (pa *ConfigAgent) ExecutePullRequestHandlers(hp HandlerParams, event github.PullRequestEvent) {
240 p := pa.PullRequestHandlers(hp.Org, hp.Repo)
241 for plugins := range p {
242 p[plugins](hp, event)
243 }
244 }
245
246
247 func (pa *ConfigAgent) ReviewEventHandlers(owner, repo string) map[string]ReviewEventHandler {
248
249
250
251 hs := map[string]ReviewEventHandler{}
252 for _, p := range pa.getPlugins(owner, repo) {
253 if h, ok := reviewEventHandlers[p]; ok {
254 hs[p] = h
255 }
256 }
257
258 return hs
259 }
260
261
262 func (pa *ConfigAgent) ExecuteReviewEventHandlers(hp HandlerParams, event github.PullRequestReviewEvent) {
263 p := pa.ReviewEventHandlers(hp.Org, hp.Repo)
264 for plugins := range p {
265 p[plugins](hp, event)
266 }
267 }
268
269
270 func (pa *ConfigAgent) ReviewCommentEventHandlers(owner, repo string) map[string]ReviewCommentEventHandler {
271
272
273
274 hs := map[string]ReviewCommentEventHandler{}
275 for _, p := range pa.getPlugins(owner, repo) {
276 if h, ok := reviewCommentEventHandlers[p]; ok {
277 hs[p] = h
278 }
279 }
280
281 return hs
282 }
283
284
285 func (pa *ConfigAgent) ExecuteReviewCommentEventHandlers(hp HandlerParams, event github.PullRequestReviewCommentEvent) {
286 p := pa.ReviewCommentEventHandlers(hp.Org, hp.Repo)
287 for plugins := range p {
288 p[plugins](hp, event)
289 }
290 }
291
292
293 func (pa *ConfigAgent) StatusEventHandlers(owner, repo string) map[string]StatusEventHandler {
294
295
296
297 hs := map[string]StatusEventHandler{}
298 for _, p := range pa.getPlugins(owner, repo) {
299 if h, ok := statusEventHandlers[p]; ok {
300 hs[p] = h
301 }
302 }
303
304 return hs
305 }
306
307
308 func (pa *ConfigAgent) ExecuteStatusEventHandlers(hp HandlerParams, event github.StatusEvent) {
309 p := pa.StatusEventHandlers(hp.Org, hp.Repo)
310 for plugins := range p {
311 p[plugins](hp, event)
312 }
313 }
314
315
316 func (pa *ConfigAgent) WorkflowEventHandlers(owner, repo string) map[string]WorkflowEventHandler {
317
318
319
320 hs := map[string]WorkflowEventHandler{}
321 for _, p := range pa.getPlugins(owner, repo) {
322 if h, ok := workflowEventHandlers[p]; ok {
323 hs[p] = h
324 }
325 }
326
327 return hs
328 }
329
330
331 func (pa *ConfigAgent) ExecuteWorkflowEventHandlers(hp HandlerParams, event github.WorkflowJobEvent) {
332 p := pa.WorkflowEventHandlers(hp.Org, hp.Repo)
333 for plugins := range p {
334 p[plugins](hp, event)
335 }
336 }
337
338
339 func (pa *ConfigAgent) WorkflowRunEventHandlers(owner, repo string) map[string]WorkflowRunEventHandler {
340
341
342
343 hs := map[string]WorkflowRunEventHandler{}
344 for _, p := range pa.getPlugins(owner, repo) {
345 if h, ok := workflowRunEventHandlers[p]; ok {
346 hs[p] = h
347 }
348 }
349
350 return hs
351 }
352
353
354 func (pa *ConfigAgent) ExecuteWorkflowRunEventHandlers(hp HandlerParams, event github.WorkflowRunEvent) {
355 p := pa.WorkflowRunEventHandlers(hp.Org, hp.Repo)
356 for plugins := range p {
357 p[plugins](hp, event)
358 }
359 }
360
361
362 func (pa *ConfigAgent) ExecuteTimeEventHandlers(hp HandlerParams, plugin string) {
363 if _, ok := timeEventHandlers[plugin]; ok {
364 timeEventHandlers[plugin](hp)
365 }
366 }
367
368
369 func (pa *ConfigAgent) PushEventHandlers(owner, repo string) map[string]PushEventHandler {
370
371
372
373 hs := map[string]PushEventHandler{}
374 for _, p := range pa.getPlugins(owner, repo) {
375 if h, ok := pushEventHandlers[p]; ok {
376 hs[p] = h
377 }
378 }
379
380 return hs
381 }
382
383
384 func (pa *ConfigAgent) ExecutePushEventHandlers(hp HandlerParams, event github.PushEvent) {
385 p := pa.PushEventHandlers(hp.Org, hp.Repo)
386 for plugins := range p {
387 p[plugins](hp, event)
388 }
389 }
390
391
392
393
394
395 func (pa *ConfigAgent) Start(path string, supplementalPluginConfigDirs []string, supplementalPluginConfigFileSuffix string, checkUnknownPlugins bool) error {
396 if err := pa.Load(path, supplementalPluginConfigDirs, supplementalPluginConfigFileSuffix, checkUnknownPlugins); err != nil {
397 return err
398 }
399
400
401
402
403
404
405
406
407 return nil
408 }
409
410
411
412
413
414 func (pa *ConfigAgent) Load(path string, supplementalPluginConfigDirs []string, supplementalPluginConfigFileSuffix string, _ bool) error {
415 b, err := os.ReadFile(path)
416 if err != nil {
417 return err
418 }
419 np := &Configuration{}
420 if err := yaml.Unmarshal(b, np); err != nil {
421 return err
422 }
423
424 var errs []error
425 for _, supplementalPluginConfigDir := range supplementalPluginConfigDirs {
426 if err := filepath.Walk(supplementalPluginConfigDir, func(path string, info fs.FileInfo, err error) error {
427 if err != nil {
428 return err
429 }
430 if info.IsDir() || !strings.HasSuffix(path, supplementalPluginConfigFileSuffix) {
431 return nil
432 }
433
434 data, err := os.ReadFile(path)
435 if err != nil {
436 errs = append(errs, fmt.Errorf("failed to read %s: %w", path, err))
437 return nil
438 }
439
440 cfg := &Configuration{}
441 if err := yaml.Unmarshal(data, cfg); err != nil {
442 errs = append(errs, fmt.Errorf("failed to unmarshal %s: %w", path, err))
443 return nil
444 }
445
446 if err := np.mergeFrom(cfg); err != nil {
447 errs = append(errs, fmt.Errorf("failed to merge config from %s into main config: %w", path, err))
448 }
449
450 return nil
451 }); err != nil {
452 errs = append(errs, fmt.Errorf("failed to walk %s: %w", supplementalPluginConfigDir, err))
453 }
454 }
455
456 if pa.AllPlugins {
457 pa.setupTesting(np)
458 }
459
460 pa.Set(np)
461 return nil
462 }
463
464
465 func (pa *ConfigAgent) setupTesting(np *Configuration) {
466 for orgRepo := range np.Plugins {
467 orgRepoPlugin := np.Plugins[orgRepo]
468
469
470 orgRepoPlugin.Enabled = pa.GetRegisteredPlugins()
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 np.Plugins[orgRepo] = orgRepoPlugin
486 }
487 }
488
489 func (c *Configuration) mergeFrom(other *Configuration) error {
490 var errs []error
491 if diff := cmp.Diff(other, &Configuration{Plugins: other.Plugins}); diff != "" {
492 errs = append(errs, fmt.Errorf("supplemental plugin configuration has config that doesn't support merging: %s", diff))
493 }
494
495 if c.Plugins == nil {
496 c.Plugins = Plugins{}
497 }
498 if err := c.Plugins.mergeFrom(&other.Plugins); err != nil {
499 errs = append(errs, fmt.Errorf("failed to merge .plugins from supplemental config: %w", err))
500 }
501
502 return utilerrors.NewAggregate(errs)
503 }
504
505 func (p *Plugins) mergeFrom(other *Plugins) error {
506 if other == nil {
507 return nil
508 }
509 if len(*p) == 0 {
510 *p = *other
511 return nil
512 }
513
514 var errs []error
515 for orgOrRepo, config := range *other {
516 if _, ok := (*p)[orgOrRepo]; ok {
517 errs = append(errs, fmt.Errorf("found duplicate config for plugins.%s", orgOrRepo))
518 continue
519 }
520 (*p)[orgOrRepo] = config
521 }
522 return utilerrors.NewAggregate(errs)
523 }
524
525
526
527
528
529 func (pa *ConfigAgent) Set(pc *Configuration) {
530
531
532
533 pa.configuration = pc
534 }
535
536
537 type IssueHandler func(HandlerParams, github.IssuesEvent)
538
539
540 func RegisterIssueHandler(name string, fn IssueHandler) {
541 allHandlers[name] = name
542 issueHandlers[name] = fn
543 }
544
545
546 type IssueCommentHandler func(HandlerParams, github.IssueCommentEvent)
547
548
549 func RegisterIssueCommentHandler(name string, fn IssueCommentHandler) {
550 allHandlers[name] = name
551 issueCommentHandlers[name] = fn
552 }
553
554
555 type PullRequestHandler func(HandlerParams, github.PullRequestEvent)
556
557
558 func RegisterPullRequestHandler(name string, fn PullRequestHandler) {
559 allHandlers[name] = name
560 pullRequestHandlers[name] = fn
561 }
562
563
564 type StatusEventHandler func(HandlerParams, github.StatusEvent)
565
566
567 func RegisterStatusEventHandler(name string, fn StatusEventHandler) {
568 allHandlers[name] = name
569 statusEventHandlers[name] = fn
570 }
571
572
573 type PushEventHandler func(HandlerParams, github.PushEvent)
574
575
576 func RegisterPushEventHandler(name string, fn PushEventHandler) {
577 allHandlers[name] = name
578 pushEventHandlers[name] = fn
579 }
580
581
582 type ReviewEventHandler func(HandlerParams, github.PullRequestReviewEvent)
583
584
585 func RegisterReviewEventHandler(name string, fn ReviewEventHandler) {
586 allHandlers[name] = name
587 reviewEventHandlers[name] = fn
588 }
589
590
591 type ReviewCommentEventHandler func(HandlerParams, github.PullRequestReviewCommentEvent)
592
593
594 func RegisterReviewCommentEventHandler(name string, fn ReviewCommentEventHandler) {
595 allHandlers[name] = name
596 reviewCommentEventHandlers[name] = fn
597 }
598
599
600 type WorkflowEventHandler func(HandlerParams, github.WorkflowJobEvent)
601
602
603 func RegisterWorkflowEventHandler(name string, fn WorkflowEventHandler) {
604 allHandlers[name] = name
605 workflowEventHandlers[name] = fn
606 }
607
608
609 type WorkflowRunEventHandler func(HandlerParams, github.WorkflowRunEvent)
610
611
612 func RegisterWorkflowRunEventHandler(name string, fn WorkflowRunEventHandler) {
613 allHandlers[name] = name
614 workflowRunEventHandlers[name] = fn
615 }
616
617
618 type TimeEventHandler func(HandlerParams)
619
620
621 func RegisterTimeEventHandler(name string, fn TimeEventHandler) {
622 timeEventHandlers[name] = fn
623 }
624
View as plain text