Skip to content

Commit 49db4be

Browse files
committed
Add support for repository dispatch notifications to GitHub service.
Part of #356 Repository dispatch allows for triggering GitHub Actions via event type (as opposed to "workflow dispatch", which requires specifying a specific workflow to trigger). The triggering calls can pass arbitrary data via "client_payload" JSON. API docs here: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-dispatch-event Signed-off-by: Daniel Moran <[email protected]>
1 parent c449507 commit 49db4be

File tree

2 files changed

+135
-5
lines changed

2 files changed

+135
-5
lines changed

pkg/services/github.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package services
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
"net/http"
89
"regexp"
@@ -41,6 +42,7 @@ type GitHubNotification struct {
4142
RepoURLPath string `json:"repoURLPath,omitempty"`
4243
RevisionPath string `json:"revisionPath,omitempty"`
4344
CheckRun *GitHubCheckRun `json:"checkRun,omitempty"`
45+
RepositoryDispatch *GitHubRepositoryDispatch `json:"repositoryDispatch,omitempty"`
4446
}
4547

4648
type GitHubStatus struct {
@@ -82,6 +84,11 @@ type GitHubPullRequestComment struct {
8284
CommentTag string `json:"commentTag,omitempty"`
8385
}
8486

87+
type GitHubRepositoryDispatch struct {
88+
EventType string `json:"event_type,omitempty"`
89+
ClientPayload string `json:"client_payload,omitempty"`
90+
}
91+
8592
const (
8693
repoURLtemplate = "{{.app.spec.source.repoURL}}"
8794
revisionTemplate = "{{.app.status.operationState.syncResult.revision}}"
@@ -204,6 +211,18 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) (
204211
}
205212
}
206213

214+
var repoDispatchEventType, repoDispatchClientPayload *texttemplate.Template
215+
if g.RepositoryDispatch != nil {
216+
repoDispatchEventType, err = texttemplate.New(name).Funcs(f).Parse(g.RepositoryDispatch.EventType)
217+
if err != nil {
218+
return nil, err
219+
}
220+
repoDispatchClientPayload, err = texttemplate.New(name).Funcs(f).Parse(g.RepositoryDispatch.ClientPayload)
221+
if err != nil {
222+
return nil, err
223+
}
224+
}
225+
207226
return func(notification *Notification, vars map[string]interface{}) error {
208227
if notification.GitHub == nil {
209228
notification.GitHub = &GitHubNotification{
@@ -375,6 +394,20 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) (
375394
notification.GitHub.CheckRun.Output.Text = textData.String()
376395
}
377396

397+
if g.RepositoryDispatch != nil {
398+
notification.GitHub.RepositoryDispatch = &GitHubRepositoryDispatch{}
399+
var eventTypeData bytes.Buffer
400+
if err := repoDispatchEventType.Execute(&eventTypeData, vars); err != nil {
401+
return err
402+
}
403+
notification.GitHub.RepositoryDispatch.EventType = eventTypeData.String()
404+
var clientPayloadData bytes.Buffer
405+
if err := repoDispatchClientPayload.Execute(&clientPayloadData, vars); err != nil {
406+
return err
407+
}
408+
notification.GitHub.RepositoryDispatch.ClientPayload = clientPayloadData.String()
409+
}
410+
378411
return nil
379412
}, nil
380413
}
@@ -445,6 +478,7 @@ type repositoriesService interface {
445478
ListDeployments(ctx context.Context, owner, repo string, opts *github.DeploymentsListOptions) ([]*github.Deployment, *github.Response, error)
446479
CreateDeployment(ctx context.Context, owner, repo string, request *github.DeploymentRequest) (*github.Deployment, *github.Response, error)
447480
CreateDeploymentStatus(ctx context.Context, owner, repo string, deploymentID int64, request *github.DeploymentStatusRequest) (*github.DeploymentStatus, *github.Response, error)
481+
Dispatch(ctx context.Context, owner, repo string, opts github.DispatchRequestOptions) (*github.Repository, *github.Response, error)
448482
}
449483

450484
type checksService interface {
@@ -698,6 +732,23 @@ func (g gitHubService) Send(notification Notification, _ Destination) error {
698732
}
699733
}
700734

735+
if notification.GitHub.RepositoryDispatch != nil {
736+
payload := json.RawMessage(notification.GitHub.RepositoryDispatch.ClientPayload)
737+
_, _, err := g.client.GetRepositories().Dispatch(
738+
context.Background(),
739+
u[0],
740+
u[1],
741+
github.DispatchRequestOptions{
742+
EventType: notification.GitHub.RepositoryDispatch.EventType,
743+
ClientPayload: &payload,
744+
},
745+
)
746+
747+
if err != nil {
748+
return err
749+
}
750+
}
751+
701752
return nil
702753
}
703754

pkg/services/github_test.go

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,52 @@ func TestGetTemplater_Github_PullRequestComment(t *testing.T) {
265265
assert.Equal(t, "This is a comment", notification.GitHub.PullRequestComment.Content)
266266
}
267267

268+
func TestGetTemplater_Github_RepositoryDispatch(t *testing.T) {
269+
n := Notification{
270+
GitHub: &GitHubNotification{
271+
RepoURLPath: "{{.sync.spec.git.repo}}",
272+
RevisionPath: "{{.sync.status.lastSyncedCommit}}",
273+
RepositoryDispatch: &GitHubRepositoryDispatch{
274+
EventType: "sync",
275+
ClientPayload: `{ "sha": "{{.sync.status.lastSyncedCommit}}" }`,
276+
},
277+
},
278+
}
279+
templater, err := n.GetTemplater("", template.FuncMap{})
280+
281+
if !assert.NoError(t, err) {
282+
return
283+
}
284+
285+
var notification Notification
286+
err = templater(&notification, map[string]interface{}{
287+
"sync": map[string]interface{}{
288+
"metadata": map[string]interface{}{
289+
"name": "root-sync-test",
290+
},
291+
"spec": map[string]interface{}{
292+
"git": map[string]interface{}{
293+
"repo": "https://github.com/argoproj-labs/argocd-notifications.git",
294+
},
295+
},
296+
"status": map[string]interface{}{
297+
"lastSyncedCommit": "0123456789",
298+
},
299+
},
300+
})
301+
302+
if !assert.NoError(t, err) {
303+
return
304+
}
305+
306+
assert.Equal(t, "{{.sync.spec.git.repo}}", notification.GitHub.RepoURLPath)
307+
assert.Equal(t, "{{.sync.status.lastSyncedCommit}}", notification.GitHub.RevisionPath)
308+
assert.Equal(t, "https://github.com/argoproj-labs/argocd-notifications.git", notification.GitHub.repoURL)
309+
assert.Equal(t, "0123456789", notification.GitHub.revision)
310+
assert.Equal(t, "sync", notification.GitHub.RepositoryDispatch.EventType)
311+
assert.Equal(t, `{ "sha": "0123456789" }`, notification.GitHub.RepositoryDispatch.ClientPayload)
312+
}
313+
268314
func TestGetTemplater_Github_PullRequestCommentWithTag(t *testing.T) {
269315
n := Notification{
270316
GitHub: &GitHubNotification{
@@ -357,7 +403,9 @@ type mockPullRequestsService struct {
357403
prs []*github.PullRequest
358404
}
359405

360-
type mockRepositoriesService struct{}
406+
type mockRepositoriesService struct {
407+
dispatches []github.DispatchRequestOptions
408+
}
361409

362410
func (m *mockRepositoriesService) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) {
363411
return status, nil, nil
@@ -375,6 +423,11 @@ func (m *mockRepositoriesService) CreateDeploymentStatus(ctx context.Context, ow
375423
return &github.DeploymentStatus{}, nil, nil
376424
}
377425

426+
func (m *mockRepositoriesService) Dispatch(ctx context.Context, owner, repo string, request github.DispatchRequestOptions) (*github.Repository, *github.Response, error) {
427+
m.dispatches = append(m.dispatches, request)
428+
return &github.Repository{}, nil, nil
429+
}
430+
378431
type mockChecksService struct{}
379432

380433
func (m *mockChecksService) CreateCheckRun(ctx context.Context, owner, repo string, opts github.CreateCheckRunOptions) (*github.CheckRun, *github.Response, error) {
@@ -394,20 +447,46 @@ func (m *mockGitHubClientImpl) GetPullRequests() pullRequestsService { return m.
394447
func (m *mockGitHubClientImpl) GetRepositories() repositoriesService { return m.repos }
395448
func (m *mockGitHubClientImpl) GetChecks() checksService { return m.checks }
396449

397-
func setupMockServices() (*mockIssuesService, *mockPullRequestsService, githubClient) {
450+
func setupMockServices() (*mockIssuesService, *mockPullRequestsService, *mockRepositoriesService, githubClient) {
398451
issues := &mockIssuesService{comments: []*github.IssueComment{}}
399452
pulls := &mockPullRequestsService{prs: []*github.PullRequest{{Number: github.Ptr(1)}}}
453+
repos := &mockRepositoriesService{}
400454
client := &mockGitHubClientImpl{
401455
issues: issues,
402456
prs: pulls,
403-
repos: &mockRepositoriesService{},
457+
repos: repos,
404458
checks: &mockChecksService{},
405459
}
406-
return issues, pulls, client
460+
return issues, pulls, repos, client
461+
}
462+
463+
func TestGitHubService_Send_RepositoryDispatch(t *testing.T) {
464+
_, _, repos, client := setupMockServices()
465+
466+
service := &gitHubService{client: client}
467+
468+
err := service.Send(Notification{
469+
GitHub: &GitHubNotification{
470+
repoURL: "https://github.com/owner/repo",
471+
revision: "abc123",
472+
RepositoryDispatch: &GitHubRepositoryDispatch{
473+
EventType: "sync",
474+
ClientPayload: `{ "sha": "12345678" }`,
475+
},
476+
},
477+
}, Destination{})
478+
479+
assert.NoError(t, err)
480+
assert.Len(t, repos.dispatches, 1)
481+
assert.Equal(t, repos.dispatches[0].EventType, "sync")
482+
483+
payload, err := repos.dispatches[0].ClientPayload.MarshalJSON()
484+
assert.NoError(t, err)
485+
assert.Equal(t, string(payload), `{ "sha": "12345678" }`)
407486
}
408487

409488
func TestGitHubService_Send_PullRequestCommentWithTag(t *testing.T) {
410-
issues, _, client := setupMockServices()
489+
issues, _, _, client := setupMockServices()
411490

412491
service := &gitHubService{client: client}
413492

0 commit comments

Comments
 (0)