diff --git a/pkg/services/github.go b/pkg/services/github.go index 5971db5e..04ca3dee 100644 --- a/pkg/services/github.go +++ b/pkg/services/github.go @@ -3,6 +3,7 @@ package services import ( "bytes" "context" + "encoding/json" "fmt" "net/http" "regexp" @@ -41,6 +42,7 @@ type GitHubNotification struct { RepoURLPath string `json:"repoURLPath,omitempty"` RevisionPath string `json:"revisionPath,omitempty"` CheckRun *GitHubCheckRun `json:"checkRun,omitempty"` + RepositoryDispatch *GitHubRepositoryDispatch `json:"repositoryDispatch,omitempty"` } type GitHubStatus struct { @@ -82,6 +84,11 @@ type GitHubPullRequestComment struct { CommentTag string `json:"commentTag,omitempty"` } +type GitHubRepositoryDispatch struct { + EventType string `json:"event_type,omitempty"` + ClientPayload string `json:"client_payload,omitempty"` +} + const ( repoURLtemplate = "{{.app.spec.source.repoURL}}" revisionTemplate = "{{.app.status.operationState.syncResult.revision}}" @@ -204,6 +211,18 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) ( } } + var repoDispatchEventType, repoDispatchClientPayload *texttemplate.Template + if g.RepositoryDispatch != nil { + repoDispatchEventType, err = texttemplate.New(name).Funcs(f).Parse(g.RepositoryDispatch.EventType) + if err != nil { + return nil, err + } + repoDispatchClientPayload, err = texttemplate.New(name).Funcs(f).Parse(g.RepositoryDispatch.ClientPayload) + if err != nil { + return nil, err + } + } + return func(notification *Notification, vars map[string]interface{}) error { if notification.GitHub == nil { notification.GitHub = &GitHubNotification{ @@ -375,6 +394,20 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) ( notification.GitHub.CheckRun.Output.Text = textData.String() } + if g.RepositoryDispatch != nil { + notification.GitHub.RepositoryDispatch = &GitHubRepositoryDispatch{} + var eventTypeData bytes.Buffer + if err := repoDispatchEventType.Execute(&eventTypeData, vars); err != nil { + return err + } + notification.GitHub.RepositoryDispatch.EventType = eventTypeData.String() + var clientPayloadData bytes.Buffer + if err := repoDispatchClientPayload.Execute(&clientPayloadData, vars); err != nil { + return err + } + notification.GitHub.RepositoryDispatch.ClientPayload = clientPayloadData.String() + } + return nil }, nil } @@ -445,6 +478,7 @@ type repositoriesService interface { ListDeployments(ctx context.Context, owner, repo string, opts *github.DeploymentsListOptions) ([]*github.Deployment, *github.Response, error) CreateDeployment(ctx context.Context, owner, repo string, request *github.DeploymentRequest) (*github.Deployment, *github.Response, error) CreateDeploymentStatus(ctx context.Context, owner, repo string, deploymentID int64, request *github.DeploymentStatusRequest) (*github.DeploymentStatus, *github.Response, error) + Dispatch(ctx context.Context, owner, repo string, opts github.DispatchRequestOptions) (*github.Repository, *github.Response, error) } type checksService interface { @@ -698,6 +732,23 @@ func (g gitHubService) Send(notification Notification, _ Destination) error { } } + if notification.GitHub.RepositoryDispatch != nil { + payload := json.RawMessage(notification.GitHub.RepositoryDispatch.ClientPayload) + _, _, err := g.client.GetRepositories().Dispatch( + context.Background(), + u[0], + u[1], + github.DispatchRequestOptions{ + EventType: notification.GitHub.RepositoryDispatch.EventType, + ClientPayload: &payload, + }, + ) + + if err != nil { + return err + } + } + return nil } diff --git a/pkg/services/github_test.go b/pkg/services/github_test.go index 80b0032b..54ab8271 100644 --- a/pkg/services/github_test.go +++ b/pkg/services/github_test.go @@ -265,6 +265,52 @@ func TestGetTemplater_Github_PullRequestComment(t *testing.T) { assert.Equal(t, "This is a comment", notification.GitHub.PullRequestComment.Content) } +func TestGetTemplater_Github_RepositoryDispatch(t *testing.T) { + n := Notification{ + GitHub: &GitHubNotification{ + RepoURLPath: "{{.sync.spec.git.repo}}", + RevisionPath: "{{.sync.status.lastSyncedCommit}}", + RepositoryDispatch: &GitHubRepositoryDispatch{ + EventType: "sync", + ClientPayload: `{ "sha": "{{.sync.status.lastSyncedCommit}}" }`, + }, + }, + } + templater, err := n.GetTemplater("", template.FuncMap{}) + + if !assert.NoError(t, err) { + return + } + + var notification Notification + err = templater(¬ification, map[string]interface{}{ + "sync": map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "root-sync-test", + }, + "spec": map[string]interface{}{ + "git": map[string]interface{}{ + "repo": "https://github.com/argoproj-labs/argocd-notifications.git", + }, + }, + "status": map[string]interface{}{ + "lastSyncedCommit": "0123456789", + }, + }, + }) + + if !assert.NoError(t, err) { + return + } + + assert.Equal(t, "{{.sync.spec.git.repo}}", notification.GitHub.RepoURLPath) + assert.Equal(t, "{{.sync.status.lastSyncedCommit}}", notification.GitHub.RevisionPath) + assert.Equal(t, "https://github.com/argoproj-labs/argocd-notifications.git", notification.GitHub.repoURL) + assert.Equal(t, "0123456789", notification.GitHub.revision) + assert.Equal(t, "sync", notification.GitHub.RepositoryDispatch.EventType) + assert.Equal(t, `{ "sha": "0123456789" }`, notification.GitHub.RepositoryDispatch.ClientPayload) +} + func TestGetTemplater_Github_PullRequestCommentWithTag(t *testing.T) { n := Notification{ GitHub: &GitHubNotification{ @@ -357,7 +403,9 @@ type mockPullRequestsService struct { prs []*github.PullRequest } -type mockRepositoriesService struct{} +type mockRepositoriesService struct { + dispatches []github.DispatchRequestOptions +} func (m *mockRepositoriesService) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) { return status, nil, nil @@ -375,6 +423,11 @@ func (m *mockRepositoriesService) CreateDeploymentStatus(ctx context.Context, ow return &github.DeploymentStatus{}, nil, nil } +func (m *mockRepositoriesService) Dispatch(ctx context.Context, owner, repo string, request github.DispatchRequestOptions) (*github.Repository, *github.Response, error) { + m.dispatches = append(m.dispatches, request) + return &github.Repository{}, nil, nil +} + type mockChecksService struct{} 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. func (m *mockGitHubClientImpl) GetRepositories() repositoriesService { return m.repos } func (m *mockGitHubClientImpl) GetChecks() checksService { return m.checks } -func setupMockServices() (*mockIssuesService, *mockPullRequestsService, githubClient) { +func setupMockServices() (*mockIssuesService, *mockPullRequestsService, *mockRepositoriesService, githubClient) { issues := &mockIssuesService{comments: []*github.IssueComment{}} pulls := &mockPullRequestsService{prs: []*github.PullRequest{{Number: github.Ptr(1)}}} + repos := &mockRepositoriesService{} client := &mockGitHubClientImpl{ issues: issues, prs: pulls, - repos: &mockRepositoriesService{}, + repos: repos, checks: &mockChecksService{}, } - return issues, pulls, client + return issues, pulls, repos, client +} + +func TestGitHubService_Send_RepositoryDispatch(t *testing.T) { + _, _, repos, client := setupMockServices() + + service := &gitHubService{client: client} + + err := service.Send(Notification{ + GitHub: &GitHubNotification{ + repoURL: "https://github.com/owner/repo", + revision: "abc123", + RepositoryDispatch: &GitHubRepositoryDispatch{ + EventType: "sync", + ClientPayload: `{ "sha": "12345678" }`, + }, + }, + }, Destination{}) + + assert.NoError(t, err) + assert.Len(t, repos.dispatches, 1) + assert.Equal(t, repos.dispatches[0].EventType, "sync") + + payload, err := repos.dispatches[0].ClientPayload.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, string(payload), `{ "sha": "12345678" }`) } func TestGitHubService_Send_PullRequestCommentWithTag(t *testing.T) { - issues, _, client := setupMockServices() + issues, _, _, client := setupMockServices() service := &gitHubService{client: client}