Skip to content

Commit

Permalink
Added case for pull request event.
Browse files Browse the repository at this point in the history
  • Loading branch information
blkt committed May 24, 2024
1 parent 7585061 commit ffe3c63
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 30 deletions.
80 changes: 66 additions & 14 deletions internal/controlplane/handlers_githubwebhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,18 @@ type pkg struct {
}

type user struct {
ID *int64 `json:"id,omitempty"`
Login *string `json:"login,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
}

func (u *user) GetID() int64 {
if u.ID != nil {
return *u.ID
}
return 0
}

func (u *user) GetLogin() string {
if u.Login != nil {
return *u.Login
Expand Down Expand Up @@ -212,6 +220,53 @@ func (r *repo) GetPrivate() bool {
return false
}

// pullRequestEvent are events related to pull requests issued around
// a specific repository
type pullRequestEvent struct {
Action *string `json:"action,omitempty"`
Repo *repo `json:"repository,omitempty"`
PullRequest *pullRequest `json:"pull_request,omitempty"`
}

func (p *pullRequestEvent) GetAction() string {
if p.Action != nil {
return *p.Action
}
return ""
}

func (p *pullRequestEvent) GetRepo() *repo {
return p.Repo
}

func (p *pullRequestEvent) GetPullRequest() *pullRequest {
return p.PullRequest
}

type pullRequest struct {
URL *string `json:"url,omitempty"`
Number *int64 `json:"number,omitempty"`
User *user `json:"user,omitempty"`
}

func (p *pullRequest) GetURL() string {
if p.URL != nil {
return *p.URL
}
return ""
}

func (p *pullRequest) GetNumber() int64 {
if p.Number != nil {
return *p.Number
}
return 0
}

func (p *pullRequest) GetUser() *user {
return p.User
}

// installationEvent are events related the GitHub App. Minder uses
// them for provider enrollement.
type installationEvent struct {
Expand Down Expand Up @@ -279,6 +334,7 @@ func (s *Server) HandleGitHubAppWebhook() http.HandlerFunc {
Str("providertype", m.Metadata[events.ProviderTypeKey]).
Str("upstream-delivery-id", m.Metadata[events.ProviderDeliveryIdKey]).
Logger()
ctx = l.WithContext(ctx)

wes.Accepted = true
var res *processingResult
Expand Down Expand Up @@ -371,6 +427,7 @@ func (s *Server) HandleGitHubWebHook() http.HandlerFunc {
Str("providertype", m.Metadata[events.ProviderTypeKey]).
Str("upstream-delivery-id", m.Metadata[events.ProviderDeliveryIdKey]).
Logger()
ctx = l.WithContext(ctx)

l.Debug().Msg("parsing event")

Expand Down Expand Up @@ -410,12 +467,14 @@ func (s *Server) HandleGitHubWebHook() http.HandlerFunc {
// This is an artifact-related event, and can
// only trigger a reconciliation.
res, processingErr = s.processPackageEvent(ctx, rawWBPayload)
case "pull_request":
res, processingErr = s.processPullRequestEvent(ctx, rawWBPayload)
// This event is not currently handled.
case "branch_protection_configuration",
"repository_advisory",
"repository_ruleset",
"secret_scanning_alert_location":
break
l.Info().Msgf("webhook events %s not handled", wes.Typ)
case "org_block",
"organization":
l.Info().Msgf("webhook events %s do not contain repo", wes.Typ)
Expand All @@ -428,18 +487,6 @@ func (s *Server) HandleGitHubWebHook() http.HandlerFunc {
s.processPingEvent(ctx, rawWBPayload)
}

event, err := github.ParseWebHook(github.WebHookType(r), rawWBPayload)
if err != nil {
l.Error().Err(err).Msg("Error parsing github webhook message")
}

switch event := event.(type) {
case *github.PullRequestEvent:
res, processingErr = s.processPullRequestEvent(context.TODO(), event)
default:
l.Info().Msgf("webhook event %s not handled", wes.Typ)
}

if processingErr != nil {
wes = handleParseError(wes.Typ, processingErr)
if wes.Error {
Expand Down Expand Up @@ -706,8 +753,13 @@ func (s *Server) processRepositoryEvent(

func (s *Server) processPullRequestEvent(
ctx context.Context,
event *github.PullRequestEvent,
payload []byte,
) (*processingResult, error) {
var event *pullRequestEvent
if err := json.Unmarshal(payload, &event); err != nil {
return nil, err
}

if event.GetAction() == "" {
return nil, errors.New("invalid event: action is nil")
}
Expand Down
168 changes: 152 additions & 16 deletions internal/controlplane/handlers_githubwebhooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ import (
"github.com/stacklok/minder/internal/util/testqueue"
)

//go:embed test-payloads/installation-deleted.json
var rawInstallationDeletedEvent string

//go:embed test-payloads/package-published.json
var rawPackageEventPublished string

Expand Down Expand Up @@ -872,6 +875,10 @@ func (s *UnitTestSuite) TestHandleGitHubWebHook() {
RepoID: 12345,
Provider: providerName,
ProviderID: providerID,
WebhookID: sql.NullInt64{
Int64: 54321,
Valid: true,
},
},
),
),
Expand Down Expand Up @@ -931,6 +938,43 @@ func (s *UnitTestSuite) TestHandleGitHubWebHook() {
require.Equal(t, repositoryID.String(), received.Metadata["repository_id"])
},
},
{
name: "meta bad hook",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#meta
event: "meta",
// https://pkg.go.dev/github.com/google/go-github/v62@v62.0.0/github#MetaEvent
payload: &github.MetaEvent{
Action: github.String("deleted"),
HookID: github.Int64(54321),
Hook: &github.Hook{
ID: github.Int64(54321),
},
Repo: newGitHubRepo(
12345,
"minder",
"stacklok/minder",
"https://github.com/stacklok/minder",
),
},
mockStoreFunc: newMockStore(
withSuccessfulGetRepositoryByRepoID(
db.Repository{
ID: repositoryID,
ProjectID: projectID,
RepoID: 12345,
Provider: providerName,
ProviderID: providerID,
WebhookID: sql.NullInt64{
Int64: 12345,
Valid: true,
},
},
),
),
topic: events.TopicQueueReconcileEntityDelete,
statusCode: http.StatusOK,
queued: nil,
},
{
name: "branch_protection_rule created",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#branch_protection_rule
Expand Down Expand Up @@ -1569,6 +1613,80 @@ func (s *UnitTestSuite) TestHandleGitHubWebHook() {
require.Equal(t, repositoryID.String(), received.Metadata["repository_id"])
},
},
{
name: "repository private repos not enabled",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#repository
event: "repository",
// https://pkg.go.dev/github.com/google/go-github/v62@v62.0.0/github#RepositoryEvent
payload: &github.RepositoryEvent{
Action: github.String("transferred"),
Repo: &github.Repository{
ID: github.Int64(12345),
Name: github.String("minder"),
FullName: github.String("stacklok/minder"),
HTMLURL: github.String("https://github.com/stacklok/minder"),
Private: github.Bool(true),
},
},
mockStoreFunc: newMockStore(
withSuccessfulGetRepositoryByRepoID(
db.Repository{
ID: repositoryID,
ProjectID: projectID,
RepoID: 12345,
Provider: providerName,
ProviderID: providerID,
},
),
withSuccessfulGetFeatureInProject(false),
),
topic: events.TopicQueueEntityEvaluate,
statusCode: http.StatusOK,
queued: nil,
},
{
name: "repository private repos enabled",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#repository
event: "repository",
// https://pkg.go.dev/github.com/google/go-github/v62@v62.0.0/github#RepositoryEvent
payload: &github.RepositoryEvent{
Action: github.String("transferred"),
Repo: &github.Repository{
ID: github.Int64(12345),
Name: github.String("minder"),
FullName: github.String("stacklok/minder"),
HTMLURL: github.String("https://github.com/stacklok/minder"),
Private: github.Bool(true),
},
},
mockStoreFunc: newMockStore(
withSuccessfulGetRepositoryByRepoID(
db.Repository{
ID: repositoryID,
ProjectID: projectID,
RepoID: 12345,
Provider: providerName,
ProviderID: providerID,
},
),
withSuccessfulGetFeatureInProject(true),
),
topic: events.TopicQueueEntityEvaluate,
statusCode: http.StatusOK,
//nolint:thelper
queued: func(t *testing.T, event string, ch <-chan *message.Message) {
timeout := 1 * time.Second
received := withTimeout(ch, timeout)
require.NotNilf(t, received, "no event received after waiting %s", timeout)
require.Equal(t, "12345", received.Metadata["id"])
require.Equal(t, event, received.Metadata["type"])
require.Equal(t, "https://api.github.com/", received.Metadata["source"])
require.Equal(t, providerID.String(), received.Metadata["provider_id"])
require.Equal(t, projectID.String(), received.Metadata[entities.ProjectIDEventKey])
require.Equal(t, repositoryID.String(), received.Metadata["repository_id"])
},
},

{
name: "repository_import",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#repository_import
Expand Down Expand Up @@ -2910,6 +3028,25 @@ func (s *UnitTestSuite) TestHandleGitHubAppWebHook() {
require.Equal(t, "https://api.github.com/", received.Metadata["source"])
},
},
{
name: "installation deleted raw payload",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#installation
event: "installation",
// https://pkg.go.dev/github.com/google/go-github/v62@v62.0.0/github#InstallationEvent
rawPayload: []byte(rawInstallationDeletedEvent),
mockStoreFunc: newMockStore(),
topic: installations.ProviderInstallationTopic,
statusCode: http.StatusOK,
//nolint:thelper
queued: func(t *testing.T, event string, ch <-chan *message.Message) {
timeout := 1 * time.Second
received := withTimeout(ch, timeout)
require.NotNilf(t, received, "no event received after waiting %s", timeout)
require.Equal(t, "12345", received.Metadata["id"])
require.Equal(t, event, received.Metadata["type"])
require.Equal(t, "https://api.github.com/", received.Metadata["source"])
},
},
{
name: "installation new_permissions_accepted",
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#installation
Expand Down Expand Up @@ -3059,22 +3196,6 @@ func (s *UnitTestSuite) TestHandleGitHubAppWebHook() {

<-evt.Running()

providerName := "github"
repositoryID := uuid.New()
projectID := uuid.New()
providerID := uuid.New()

mockStore.EXPECT().
GetRepositoryByRepoID(gomock.Any(), gomock.Any()).
Return(db.Repository{
ID: repositoryID,
ProjectID: projectID,
RepoID: 12345,
Provider: providerName,
ProviderID: providerID,
}, nil).
AnyTimes()

ts := httptest.NewServer(http.HandlerFunc(srv.HandleGitHubAppWebhook()))
defer ts.Close()

Expand Down Expand Up @@ -3182,6 +3303,21 @@ func withSuccessfulGetRepositoryByRepoID(repository db.Repository) func(*mockdb.
}
}

func withSuccessfulGetFeatureInProject(active bool) func(*mockdb.MockStore) {
if active {
return func(mockStore *mockdb.MockStore) {
mockStore.EXPECT().
GetFeatureInProject(gomock.Any(), gomock.Any()).
Return(json.RawMessage{}, nil)
}
}
return func(mockStore *mockdb.MockStore) {
mockStore.EXPECT().
GetFeatureInProject(gomock.Any(), gomock.Any()).
Return(nil, sql.ErrNoRows)
}
}

func withSuccessfulUpsertPullRequest(pullRequest db.PullRequest) func(*mockdb.MockStore) {
return func(mockStore *mockdb.MockStore) {
mockStore.EXPECT().
Expand Down
Loading

0 comments on commit ffe3c63

Please sign in to comment.