diff --git a/pkg/services/github.go b/pkg/services/github.go index e25af5f2..3129c6cc 100644 --- a/pkg/services/github.go +++ b/pkg/services/github.go @@ -37,6 +37,7 @@ type GitHubNotification struct { Status *GitHubStatus `json:"status,omitempty"` Deployment *GitHubDeployment `json:"deployment,omitempty"` PullRequestComment *GitHubPullRequestComment `json:"pullRequestComment,omitempty"` + InstallationID string `json:"installationID,omitempty"` RepoURLPath string `json:"repoURLPath,omitempty"` RevisionPath string `json:"revisionPath,omitempty"` } @@ -75,6 +76,11 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) ( g.RevisionPath = revisionTemplate } + installationID, err := texttemplate.New(name).Funcs(f).Parse(g.InstallationID) + if err != nil { + return nil, err + } + repoURL, err := texttemplate.New(name).Funcs(f).Parse(g.RepoURLPath) if err != nil { return nil, err @@ -142,11 +148,18 @@ func (g *GitHubNotification) GetTemplater(name string, f texttemplate.FuncMap) ( return func(notification *Notification, vars map[string]interface{}) error { if notification.GitHub == nil { notification.GitHub = &GitHubNotification{ - RepoURLPath: g.RepoURLPath, - RevisionPath: g.RevisionPath, + RepoURLPath: g.RepoURLPath, + RevisionPath: g.RevisionPath, + InstallationID: g.InstallationID, } } + var installationData bytes.Buffer + if err := installationID.Execute(&installationData, vars); err != nil { + return err + } + notification.GitHub.InstallationID = installationData.String() + var repoData bytes.Buffer if err := repoURL.Execute(&repoData, vars); err != nil { return err @@ -284,10 +297,7 @@ func NewGitHubService(opts GitHubOptions) (NotificationService, error) { } } - return &gitHubService{ - opts: opts, - client: client, - }, nil + return &gitHubService{opts: opts, client: client}, nil } type gitHubService struct { @@ -326,6 +336,48 @@ func (g gitHubService) Send(notification Notification, _ Destination) error { if len(u) < 2 { return fmt.Errorf("GitHub.repoURL (%s) does not have a `/`", notification.GitHub.repoURL) } + if g.opts.InstallationID == nil { + if notification.GitHub.InstallationID == "" { + return fmt.Errorf("GitHub Installation ID not found") + } + } + // If an alternate InstallationID is being provided create a new GitHub app client and use that instead + if notification.GitHub.InstallationID != "" { + url := "https://api.github.com" + if g.opts.EnterpriseBaseURL != "" { + url = g.opts.EnterpriseBaseURL + } + + appID, err := cast.ToInt64E(g.opts.AppID) + if err != nil { + return nil + } + + installationID, err := cast.ToInt64E(g.opts.InstallationID) + if err != nil { + return nil + } + + tr := httputil.NewLoggingRoundTripper( + httputil.NewTransport(url, false), log.WithField("service", "github")) + itr, err := ghinstallation.New(tr, appID, installationID, []byte(g.opts.PrivateKey)) + if err != nil { + return nil + } + + var client *github.Client + if g.opts.EnterpriseBaseURL == "" { + client = github.NewClient(&http.Client{Transport: itr}) + } else { + itr.BaseURL = g.opts.EnterpriseBaseURL + client, err = github.NewEnterpriseClient(g.opts.EnterpriseBaseURL, "", &http.Client{Transport: itr}) + if err != nil { + return nil + } + } + g.client = client + } + if notification.GitHub.Status != nil { // maximum is 140 characters description := trunc(notification.Message, 140) diff --git a/pkg/services/github_test.go b/pkg/services/github_test.go index c9649974..f5403cb8 100644 --- a/pkg/services/github_test.go +++ b/pkg/services/github_test.go @@ -121,13 +121,29 @@ func TestSend_GitHubService_BadURL(t *testing.T) { assert.ErrorContains(t, e, "does not have a `/`") } +func TestGetTemplater_GithubNotification_MissingInstallationID(t *testing.T) { + e := gitHubService{}.Send( + Notification{ + GitHub: &GitHubNotification{ + repoURL: "https://github.com/", + }, + }, + Destination{ + Service: "test", + Recipient: "test", + }, + ) + assert.ErrorContains(t, e, "Installation ID not found") +} + func TestGetTemplater_GitHub_Deployment(t *testing.T) { f := false tr := true n := Notification{ GitHub: &GitHubNotification{ - RepoURLPath: "{{.sync.spec.git.repo}}", - RevisionPath: "{{.sync.status.lastSyncedCommit}}", + InstallationID: "12345", + RepoURLPath: "{{.sync.spec.git.repo}}", + RevisionPath: "{{.sync.status.lastSyncedCommit}}", Deployment: &GitHubDeployment{ Reference: "v0.0.1", State: "success", @@ -167,6 +183,7 @@ func TestGetTemplater_GitHub_Deployment(t *testing.T) { return } + assert.Equal(t, "12345", notification.GitHub.InstallationID) 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)