diff --git a/charts/argobot/Chart.yaml b/charts/argobot/Chart.yaml index 15c37fe..e68e05b 100644 --- a/charts/argobot/Chart.yaml +++ b/charts/argobot/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: argobot description: Helm chart for the corymurphy/argobot app type: application -version: 0.16.4 -appVersion: 0.16.4 +version: 0.16.5 +appVersion: 0.16.5 diff --git a/pkg/events/apply_runner.go b/pkg/events/apply_runner.go index a7893d3..bf6d056 100644 --- a/pkg/events/apply_runner.go +++ b/pkg/events/apply_runner.go @@ -31,7 +31,7 @@ func NewApplyRunner(vcsClient *github.Client, config *env.Config, log logging.Si func (a *ApplyRunner) Run(ctx context.Context, app string, event vsc.Event) (CommentResponse, error) { var resp CommentResponse - status, err := vsc.NewPullRequestStatusFetcher(ctx, a.Log, a.vcsClient).Fetch(event) + status, err := vsc.NewPullRequestStatusFetcher(a.Log, a.vcsClient).Fetch(ctx, event) if err != nil { return resp, fmt.Errorf("unable to get status of pr %w", err) } diff --git a/pkg/events/comment_parser_test.go b/pkg/events/comment_parser_test.go index b60566d..4ae0f45 100644 --- a/pkg/events/comment_parser_test.go +++ b/pkg/events/comment_parser_test.go @@ -29,7 +29,7 @@ func Test_Comment_IsHelp(t *testing.T) { ` var comment github.IssueCommentEvent json.Unmarshal([]byte(serialized), &comment) - event := vsc.InitializeFromIssueComment(comment, "") + event, _ := vsc.InitializeFromIssueComment(comment, "") parser := NewCommentParser(logging.NewLogger(logging.Silent)) result := parser.Parse(event) @@ -57,7 +57,7 @@ func Test_Comment_IsBot(t *testing.T) { ` var comment github.IssueCommentEvent json.Unmarshal([]byte(serialized), &comment) - event := vsc.InitializeFromIssueComment(comment, "") + event, _ := vsc.InitializeFromIssueComment(comment, "") parser := NewCommentParser(logging.NewLogger(logging.Silent)) result := parser.Parse(event) @@ -87,7 +87,7 @@ func Test_PlanHasApplicationName(t *testing.T) { var comment github.IssueCommentEvent json.Unmarshal([]byte(serialized), &comment) - event := vsc.InitializeFromIssueComment(comment, "") + event, _ := vsc.InitializeFromIssueComment(comment, "") parser := NewCommentParser(logging.NewLogger(logging.Silent)) result := parser.Parse(event) @@ -121,7 +121,7 @@ func Test_ApplyHasApplicationName(t *testing.T) { var comment github.IssueCommentEvent json.Unmarshal([]byte(serialized), &comment) - event := vsc.InitializeFromIssueComment(comment, "") + event, _ := vsc.InitializeFromIssueComment(comment, "") parser := NewCommentParser(logging.NewLogger(logging.Silent)) result := parser.Parse(event) diff --git a/pkg/events/issue_comment_handler.go b/pkg/events/issue_comment_handler.go index 5cad2fc..1a83150 100644 --- a/pkg/events/issue_comment_handler.go +++ b/pkg/events/issue_comment_handler.go @@ -48,7 +48,7 @@ func (h *IssueCommentHandler) Handle(ctx context.Context, eventType string, deli h.Log.Err(err, "unable to get revision from pull request") return nil } - event := vsc.InitializeFromIssueComment(issue, *pr.GetHead().SHA) + event, commentId := vsc.InitializeFromIssueComment(issue, *pr.GetHead().SHA) comment := NewCommentParser(h.Log).Parse(event) if (comment.Ignore || comment.ImmediateResponse) && !comment.HasResponseComment { @@ -65,6 +65,11 @@ func (h *IssueCommentHandler) Handle(ctx context.Context, eventType string, deli } } + _, _, err = client.Reactions.CreateIssueCommentReaction(ctx, event.Repository.Owner, event.Repository.Name, *commentId, "eyes") + if err != nil { + h.Log.Err(err, "unable to create reaction on comment") + } + var apps []string if comment.Command.ExplicitApplication { apps = comment.Command.Applications @@ -117,10 +122,14 @@ func (h *IssueCommentHandler) Handle(ctx context.Context, eventType string, deli h.Log.Info("requested apply with more than 1 app, only one app allowed when applying") return nil } + if !comment.Command.ExplicitApplication { + h.Log.Info("apply does not supply auto discovery. an application must be explicitly defined.") + return nil + } go func() { + applyContext, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() for _, app := range apps { - applyContext, cancel := context.WithTimeout(ctx, 60*time.Second) - defer cancel() apply := NewApplyRunner(client, h.Config, h.Log, &h.ArgoClient) response, err := apply.Run(applyContext, app, event) if err != nil { @@ -128,7 +137,11 @@ func (h *IssueCommentHandler) Handle(ctx context.Context, eventType string, deli return } comment := fmt.Sprintf("apply result for `%s`\n\n", app) + "```\n" + response.Message + "\n```" - commenter.Comment(&event, &comment) + err = commenter.Comment(&event, &comment) + if err != nil { + h.Log.Err(err, "unable to comment with apply result") + return + } } }() return nil diff --git a/pkg/github/commenter.go b/pkg/github/commenter.go index 757e0f8..7f37317 100644 --- a/pkg/github/commenter.go +++ b/pkg/github/commenter.go @@ -11,6 +11,11 @@ import ( const maxCommentLength = 32768 +type VscCommenter interface { + Plan(event *Event, app string, command string, comment string) + Comment(event *Event, comment *string) +} + type Commenter struct { client *github.Client log logging.SimpleLogging diff --git a/pkg/github/commit_status.go b/pkg/github/commit_status.go new file mode 100644 index 0000000..558b0dc --- /dev/null +++ b/pkg/github/commit_status.go @@ -0,0 +1,21 @@ +package github + +type CommitState int + +const ( + PendingCommitState CommitState = iota + SuccessCommitState + FailedCommitState +) + +func (s CommitState) String() string { + switch s { + case PendingCommitState: + return "pending" + case SuccessCommitState: + return "success" + case FailedCommitState: + return "failure" + } + return "failure" +} diff --git a/pkg/github/event_metadata.go b/pkg/github/event_metadata.go index d9a0e35..201eb7d 100644 --- a/pkg/github/event_metadata.go +++ b/pkg/github/event_metadata.go @@ -68,7 +68,7 @@ func (e *Event) HasMessage() bool { return e.Message != "" } -func InitializeFromIssueComment(source github.IssueCommentEvent, revision string) Event { +func InitializeFromIssueComment(source github.IssueCommentEvent, revision string) (Event, *int64) { return Event{ Actor: Actor{Name: source.GetComment().GetUser().GetLogin()}, Action: Comment, @@ -83,7 +83,7 @@ func InitializeFromIssueComment(source github.IssueCommentEvent, revision string }, Message: *source.GetComment().Body, InstallationProvider: &source, - } + }, source.Comment.ID } func InitializeFromPullRequest(source github.PullRequestEvent) Event { diff --git a/pkg/github/pull_status_fetcher.go b/pkg/github/pull_status_fetcher.go index 2d2f963..5b98ca9 100644 --- a/pkg/github/pull_status_fetcher.go +++ b/pkg/github/pull_status_fetcher.go @@ -10,32 +10,30 @@ import ( ) type VscPullRequestStatusFetcher interface { - Fetch(event Event) (models.PullRequestStatus, error) + Fetch(ctx context.Context, event Event) (models.PullRequestStatus, error) } type GithubPullRequestStatusFetcher struct { client *github.Client - ctx context.Context logger logging.SimpleLogging } -func NewPullRequestStatusFetcher(ctx context.Context, logger logging.SimpleLogging, client *github.Client) VscPullRequestStatusFetcher { +func NewPullRequestStatusFetcher(logger logging.SimpleLogging, client *github.Client) VscPullRequestStatusFetcher { return &GithubPullRequestStatusFetcher{ - ctx: ctx, client: client, logger: logger, } } -func (g *GithubPullRequestStatusFetcher) Fetch(event Event) (status models.PullRequestStatus, err error) { +func (g *GithubPullRequestStatusFetcher) Fetch(ctx context.Context, event Event) (status models.PullRequestStatus, err error) { g.logger.Debug("getting approval status of pull request %d", event.PullRequest.Number) - approvalStatus, err := g.PullIsApproved(event) + approvalStatus, err := g.PullIsApproved(ctx, event) if err != nil { return status, errors.Wrapf(err, "fetching pull approval status for repo: %s/%s, and pull number: %d", event.Repository.Owner, event.Repository.Name, event.PullRequest.Number) } - mergeable, err := g.PullIsMergeable(event, "") + mergeable, err := g.PullIsMergeable(ctx, event, "") if err != nil { return status, errors.Wrapf(err, "fetching mergeability status for repo: %s/%s, and pull number: %d", event.Repository.Owner, event.Repository.Name, event.PullRequest.Number) } @@ -46,8 +44,8 @@ func (g *GithubPullRequestStatusFetcher) Fetch(event Event) (status models.PullR }, err } -func (g *GithubPullRequestStatusFetcher) PullIsApproved(event Event) (approvalStatus models.ApprovalStatus, err error) { - g.logger.Debug("Checking if GitHub pull request %d is approved", event.PullRequest.Number) +func (g *GithubPullRequestStatusFetcher) PullIsApproved(ctx context.Context, event Event) (approvalStatus models.ApprovalStatus, err error) { + g.logger.Debug("checking if pull request %d is approved", event.PullRequest.Number) nextPage := 0 for { opts := github.ListOptions{ @@ -56,7 +54,7 @@ func (g *GithubPullRequestStatusFetcher) PullIsApproved(event Event) (approvalSt if nextPage != 0 { opts.Page = nextPage } - pageReviews, resp, err := g.client.PullRequests.ListReviews(g.ctx, event.Repository.Owner, event.Repository.Name, event.PullRequest.Number, &opts) + pageReviews, resp, err := g.client.PullRequests.ListReviews(ctx, event.Repository.Owner, event.Repository.Name, event.PullRequest.Number, &opts) if resp != nil { g.logger.Debug("GET /repos/%v/%v/pulls/%d/reviews returned: %v", event.Repository.Owner, event.Repository.Name, event.PullRequest.Number, resp.StatusCode) } @@ -82,9 +80,9 @@ func (g *GithubPullRequestStatusFetcher) PullIsApproved(event Event) (approvalSt // TODO: complete the bypass functionality from atlantis // TODO: check if approved by codeowner -func (g *GithubPullRequestStatusFetcher) PullIsMergeable(event Event, vcsstatusname string) (bool, error) { +func (g *GithubPullRequestStatusFetcher) PullIsMergeable(ctx context.Context, event Event, vcsstatusname string) (bool, error) { g.logger.Debug("Checking if GitHub pull request %d is mergeable", event.PullRequest.Number) - githubPR, _, err := g.client.PullRequests.Get(g.ctx, event.Repository.Owner, event.Repository.Name, event.PullRequest.Number) + githubPR, _, err := g.client.PullRequests.Get(ctx, event.Repository.Owner, event.Repository.Name, event.PullRequest.Number) if err != nil { return false, errors.Wrap(err, "getting pull request") } diff --git a/pkg/github/vsc_client.go b/pkg/github/vsc_client.go new file mode 100644 index 0000000..9dabf4f --- /dev/null +++ b/pkg/github/vsc_client.go @@ -0,0 +1,29 @@ +package github + +import ( + "context" + + "github.com/corymurphy/argobot/pkg/logging" + "github.com/google/go-github/v53/github" +) + +type VscClient struct { + client *github.Client + logger logging.SimpleLogging +} + +func (v *VscClient) SetStatusCheck(ctx context.Context, event Event, state CommitState, context string, description string) error { + + url := "" + status := &github.RepoStatus{ + State: github.String(state.String()), + Description: github.String(description), + Context: github.String(context), + TargetURL: &url, + } + _, resp, err := v.client.Repositories.CreateStatus(ctx, event.Repository.Owner, event.Repository.Name, event.Revision, status) + if resp != nil { + v.logger.Debug("POST /repos/%v/%v/statuses/%s returned: %v", event.Repository.Owner, event.Repository.Name, event.Revision, resp.StatusCode) + } + return err +} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 786ccb3..6690ff8 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -40,16 +40,14 @@ func Test_HealthCheck(t *testing.T) { func Test_PRCommentHandler(t *testing.T) { requests := map[string]bool{ - "/app/installations/345345345/access_tokens": false, - "/repos/atlas8518/argocd-data/issues/1/comments": false, - "/repos/atlas8518/argocd-data/pulls/1": false, - "/api/v1/applications/testapp/managed-resources": false, - "/api/v1/applications/testapp": false, + "/app/installations/345345345/access_tokens": false, + "/repos/atlas8518/argocd-data/issues/1/comments": false, + "/repos/atlas8518/argocd-data/pulls/1": false, + "/api/v1/applications/testapp/managed-resources": false, + "/api/v1/applications/testapp": false, + "/repos/atlas8518/argocd-data/issues/comments/456456/reactions": false, } mu := sync.Mutex{} - - // ctx := context.Background() - mockServer := mockServer(requests, &mu, t) defer mockServer.Close() @@ -240,6 +238,12 @@ func mockServer(requests map[string]bool, mu *sync.Mutex, t *testing.T) *httptes mu.Unlock() fmt.Fprint(w, string(response)) }) + router.HandleFunc("/repos/atlas8518/argocd-data/issues/comments/456456/reactions", func(w http.ResponseWriter, r *http.Request) { + mu.Lock() + requests["/repos/atlas8518/argocd-data/issues/comments/456456/reactions"] = true + mu.Unlock() + fmt.Fprint(w, string("")) + }) router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { t.Error(r) }) diff --git a/version b/version index 5f2491c..1927038 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.16.4 +0.16.5