pkg/event_processor/github/github.go (209 lines of code) (raw):

package github import ( "context" "encoding/json" "errors" "fmt" "net/http" "github.com/google/go-github/v31/github" "go.uber.org/zap" "golang.org/x/oauth2" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/epam/edp-tekton/pkg/event_processor" ) type EventProcessor struct { ksClient ctrlClient.Reader logger *zap.SugaredLogger gitHubClient func(ctx context.Context, token string) *github.Client } type EventProcessorOptions struct { Logger *zap.SugaredLogger GitHubClient func(ctx context.Context, token string) *github.Client } func NewEventProcessor( ksClient ctrlClient.Reader, options *EventProcessorOptions, ) *EventProcessor { if options == nil { options = &EventProcessorOptions{} } if options.Logger == nil { options.Logger = zap.NewNop().Sugar() } if options.GitHubClient == nil { options.GitHubClient = func(ctx context.Context, token string) *github.Client { return github.NewClient( oauth2.NewClient( ctx, oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ), ), ) } } return &EventProcessor{ ksClient: ksClient, logger: options.Logger, gitHubClient: options.GitHubClient, } } func (p *EventProcessor) Process(ctx context.Context, body []byte, ns, eventType string) (*event_processor.EventInfo, error) { switch eventType { case event_processor.GitHubEventTypeCommentAdded: return p.processCommentEvent(ctx, body, ns) default: return p.processMergeEvent(ctx, body, ns) } } // processCommentEvent processes GitHub comment event. // nolint:cyclop // function is not complex func (p *EventProcessor) processMergeEvent(ctx context.Context, body []byte, ns string) (*event_processor.EventInfo, error) { gitHubEvent := &github.PullRequestEvent{} if err := json.Unmarshal(body, gitHubEvent); err != nil { return nil, fmt.Errorf("failed to unmarshal GitHub event: %w", err) } if gitHubEvent.Repo == nil || gitHubEvent.Repo.FullName == nil || *gitHubEvent.Repo.FullName == "" { return nil, errors.New("github repository path empty") } if gitHubEvent.PullRequest == nil || gitHubEvent.PullRequest.Base == nil || gitHubEvent.PullRequest.Base.Ref == nil || *gitHubEvent.PullRequest.Base.Ref == "" { return nil, errors.New("github target branch empty") } if gitHubEvent.GetPullRequest().GetHead() == nil { return nil, errors.New("github head branch empty") } repoPath := event_processor.ConvertRepositoryPath(*gitHubEvent.Repo.FullName) codebase, err := event_processor.GetCodebaseByRepoPath(ctx, p.ksClient, ns, repoPath) if err != nil { return nil, fmt.Errorf("failed to get codebase %s for GitHub IssueCommentEvent: %w", repoPath, err) } gitServerToken, err := event_processor.GetGitServerToken(ctx, p.ksClient, codebase) if err != nil { return nil, fmt.Errorf("failed to get git server token for GitHub PullRequestEvent: %w", err) } client := p.gitHubClient(ctx, gitServerToken) commitMessage, err := p.getCommitMessage( ctx, client, gitHubEvent.GetRepo().GetOwner().GetLogin(), gitHubEvent.GetRepo().GetName(), gitHubEvent.GetNumber(), ) if err != nil { return nil, err } return &event_processor.EventInfo{ GitProvider: event_processor.GitProviderGitHub, RepoPath: repoPath, TargetBranch: *gitHubEvent.PullRequest.Base.Ref, Codebase: codebase, Type: event_processor.EventTypeMerge, PullRequest: &event_processor.PullRequest{ HeadRef: gitHubEvent.GetPullRequest().GetHead().GetRef(), HeadSha: gitHubEvent.GetPullRequest().GetHead().GetSHA(), Title: gitHubEvent.GetPullRequest().GetTitle(), ChangeNumber: gitHubEvent.GetNumber(), LastCommitMessage: commitMessage, }, }, nil } // processCommentEvent processes GitHub comment event. // nolint:funlen // function is not so complex func (p *EventProcessor) processCommentEvent(ctx context.Context, body []byte, ns string) (*event_processor.EventInfo, error) { event := &github.IssueCommentEvent{} if err := json.Unmarshal(body, event); err != nil { return nil, fmt.Errorf("failed to unmarshal GitHub IssueCommentEvent: %w", err) } if event.GetAction() != "created" { return createEventInfoWithoutRecheck(), nil } repoPath := event_processor.ConvertRepositoryPath(event.GetRepo().GetFullName()) codebase, err := event_processor.GetCodebaseByRepoPath(ctx, p.ksClient, ns, repoPath) if err != nil { return nil, fmt.Errorf("failed to get codebase %s for GitHub IssueCommentEvent: %w", repoPath, err) } gitServerToken, err := event_processor.GetGitServerToken(ctx, p.ksClient, codebase) if err != nil { return nil, fmt.Errorf("failed to get git server token for GitHub IssueCommentEvent: %w", err) } client := p.gitHubClient(ctx, gitServerToken) pullReq, _, err := client.PullRequests.Get( ctx, event.GetRepo().GetOwner().GetLogin(), event.GetRepo().GetName(), event.GetIssue().GetNumber(), ) if err != nil { if isPRNotFoundErr(err) { p.logger.Info("GitHub pull request not found") return createEventInfoWithoutRecheck(), nil } return nil, fmt.Errorf("failed to get GitHub pull request: %w", err) } if pullReq.GetBase() == nil { return nil, errors.New("github target branch empty") } if pullReq.GetHead() == nil { return nil, errors.New("github head branch empty") } commitMessage, err := p.getCommitMessage( ctx, client, event.GetRepo().GetOwner().GetLogin(), event.GetRepo().GetName(), event.GetIssue().GetNumber(), ) if err != nil { return nil, err } return &event_processor.EventInfo{ GitProvider: event_processor.GitProviderGitHub, RepoPath: repoPath, TargetBranch: pullReq.GetBase().GetRef(), Type: event_processor.EventTypeReviewComment, HasPipelineRecheck: event_processor.ContainsPipelineRecheck(event.GetComment().GetBody()), Codebase: codebase, PullRequest: &event_processor.PullRequest{ HeadRef: pullReq.GetHead().GetRef(), HeadSha: pullReq.GetHead().GetSHA(), Title: pullReq.GetTitle(), ChangeNumber: pullReq.GetNumber(), LastCommitMessage: commitMessage, }, }, nil } func (*EventProcessor) getCommitMessage( ctx context.Context, client *github.Client, owner string, repo string, number int, ) (string, error) { commits, _, err := client.PullRequests.ListCommits(ctx, owner, repo, number, nil) if err != nil { return "", fmt.Errorf("failed to get GitHub pull request commits: %w", err) } if len(commits) == 0 { return "", errors.New("github pull request commits empty") } m := commits[len(commits)-1].Commit.Message if m == nil { return "", nil } return *m, nil } func createEventInfoWithoutRecheck() *event_processor.EventInfo { return &event_processor.EventInfo{ GitProvider: event_processor.GitProviderGitHub, Type: event_processor.EventTypeReviewComment, HasPipelineRecheck: false, } } func isPRNotFoundErr(err error) bool { var responseErr *github.ErrorResponse if errors.As(err, &responseErr) { if responseErr.Response.StatusCode == http.StatusNotFound { return true } } return false }