Skip to content

Commit

Permalink
ci: add debug on github-bot matrix subcommand + fixes (gnolang#3244)
Browse files Browse the repository at this point in the history
This PR will allow debugging errors of [this
type](https://github.com/gnolang/gno/actions/runs/12072757244) that
unfortunately cannot be tested locally since they rely on the context of
GitHub Actions.

Since I also had to add flags to the matrix subcommand, I moved the two
matrix and check subcommands into subfolders.

This PR also modify the comment to stick to moul's request and fixes
several Github Actions errors.

Related to gnolang#3238 

Changes:
-
gnolang@d11ad5a
moves matrix and check subcommands to their own packages in internal
-
gnolang@462ac01
gnolang@5c1edda
gnolang@ffdce93
adds a debug to matrix subcommand (print event input / matrix output) +
direct output of matrix to GitHub Actions using a matrix-key flag
-
gnolang@6af501d
embed comment template file as a string at compile time instead of
opening it at runtime
-
gnolang@59c3ad6
modifies bot comment to meet [this
requirements](gnolang#3238 (comment))
-
gnolang@241a755
filter out from the matrix generation and the PR processing all issues
or closed PRs (process / list only opened PRs)

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
</details>

---------

Co-authored-by: Morgan <[email protected]>
  • Loading branch information
2 people authored and r3v4s committed Dec 10, 2024
1 parent 49fef5d commit 890ab3a
Show file tree
Hide file tree
Showing 21 changed files with 490 additions and 351 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ jobs:
working-directory: contribs/github-bot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: echo "pr-numbers=$(go run . matrix)" >> "$GITHUB_OUTPUT"
run: go run . matrix -matrix-key 'pr-numbers' -verbose

# This job processes each pull request in the matrix individually while ensuring
# that a same PR cannot be processed concurrently by mutliple runners
process-pr:
name: Process PR
needs: define-prs-matrix
# Just skip this job if PR numbers matrix is empty (prevent failed state)
if: ${{ needs.define-prs-matrix.outputs.pr-numbers != '[]' && needs.define-prs-matrix.outputs.pr-numbers != '' }}
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down
4 changes: 2 additions & 2 deletions contribs/github-bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The bot operates by defining a set of rules that are evaluated against each pull
- **Automatic Checks**: These are rules that the bot evaluates automatically. If a pull request meets the conditions specified in the rule, then the corresponding requirements are executed. For example, ensuring that changes to specific directories are reviewed by specific team members.
- **Manual Checks**: These require human intervention. If a pull request meets the conditions specified in the rule, then a checkbox that can be checked only by specified teams is displayed on the bot comment. For example, determining if infrastructure needs to be updated based on changes to specific files.

The bot configuration is defined in Go and is located in the file [config.go](./config.go).
The bot configuration is defined in Go and is located in the file [config.go](./internal/config/config.go).

### GitHub Token

Expand All @@ -31,7 +31,7 @@ For the bot to make requests to the GitHub API, it needs a Personal Access Token
USAGE
github-bot check [flags]

This tool checks if the requirements for a pull request to be merged are satisfied (defined in config.go) and displays PR status checks accordingly.
This tool checks if the requirements for a pull request to be merged are satisfied (defined in ./internal/config/config.go) and displays PR status checks accordingly.
A valid GitHub Token must be provided by setting the GITHUB_TOKEN environment variable.

FLAGS
Expand Down
51 changes: 0 additions & 51 deletions contribs/github-bot/comment.tmpl

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package check

import (
"context"
Expand All @@ -9,44 +9,30 @@ import (
"sync/atomic"

"github.com/gnolang/gno/contribs/github-bot/internal/client"
"github.com/gnolang/gno/contribs/github-bot/internal/config"
"github.com/gnolang/gno/contribs/github-bot/internal/logger"
p "github.com/gnolang/gno/contribs/github-bot/internal/params"
"github.com/gnolang/gno/contribs/github-bot/internal/utils"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/google/go-github/v64/github"
"github.com/sethvargo/go-githubactions"
"github.com/xlab/treeprint"
)

func newCheckCmd() *commands.Command {
params := &p.Params{}

return commands.NewCommand(
commands.Metadata{
Name: "check",
ShortUsage: "github-bot check [flags]",
ShortHelp: "checks requirements for a pull request to be merged",
LongHelp: "This tool checks if the requirements for a pull request to be merged are satisfied (defined in config.go) and displays PR status checks accordingly.\nA valid GitHub Token must be provided by setting the GITHUB_TOKEN environment variable.",
},
params,
func(_ context.Context, _ []string) error {
params.ValidateFlags()
return execCheck(params)
},
)
}

func execCheck(params *p.Params) error {
func execCheck(flags *checkFlags) error {
// Create context with timeout if specified in the parameters.
ctx := context.Background()
if params.Timeout > 0 {
if flags.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), params.Timeout)
ctx, cancel = context.WithTimeout(context.Background(), flags.Timeout)
defer cancel()
}

// Init GitHub API client.
gh, err := client.New(ctx, params)
gh, err := client.New(ctx, &client.Config{
Owner: flags.Owner,
Repo: flags.Repo,
Verbose: *flags.Verbose,
DryRun: flags.DryRun,
})
if err != nil {
return fmt.Errorf("comment update handling failed: %w", err)
}
Expand All @@ -69,19 +55,19 @@ func execCheck(params *p.Params) error {
var prs []*github.PullRequest

// If requested, retrieve all open pull requests.
if params.PRAll {
if flags.PRAll {
prs, err = gh.ListPR(utils.PRStateOpen)
if err != nil {
return fmt.Errorf("unable to list all PR: %w", err)
}
} else {
// Otherwise, retrieve only specified pull request(s)
// (flag or GitHub Action context).
prs = make([]*github.PullRequest, len(params.PRNums))
for i, prNum := range params.PRNums {
pr, _, err := gh.Client.PullRequests.Get(gh.Ctx, gh.Owner, gh.Repo, prNum)
prs = make([]*github.PullRequest, len(flags.PRNums))
for i, prNum := range flags.PRNums {
pr, err := gh.GetOpenedPullRequest(prNum)
if err != nil {
return fmt.Errorf("unable to retrieve specified pull request (%d): %w", prNum, err)
return fmt.Errorf("unable to process PR list: %w", err)
}
prs[i] = pr
}
Expand All @@ -101,7 +87,7 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
}

// Process all pull requests in parallel.
autoRules, manualRules := config(gh)
autoRules, manualRules := config.Config(gh)
var wg sync.WaitGroup

// Used in dry-run mode to log cleanly from different goroutines.
Expand All @@ -122,15 +108,15 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
ifDetails := treeprint.NewWithRoot(fmt.Sprintf("%s Condition met", utils.Success))

// Check if conditions of this rule are met by this PR.
if !autoRule.ifC.IsMet(pr, ifDetails) {
if !autoRule.If.IsMet(pr, ifDetails) {
continue
}

c := AutoContent{Description: autoRule.description, Satisfied: false}
c := AutoContent{Description: autoRule.Description, Satisfied: false}
thenDetails := treeprint.NewWithRoot(fmt.Sprintf("%s Requirement not satisfied", utils.Fail))

// Check if requirements of this rule are satisfied by this PR.
if autoRule.thenR.IsSatisfied(pr, thenDetails) {
if autoRule.Then.IsSatisfied(pr, thenDetails) {
thenDetails.SetValue(fmt.Sprintf("%s Requirement satisfied", utils.Success))
c.Satisfied = true
} else {
Expand All @@ -153,24 +139,24 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error {
ifDetails := treeprint.NewWithRoot(fmt.Sprintf("%s Condition met", utils.Success))

// Check if conditions of this rule are met by this PR.
if !manualRule.ifC.IsMet(pr, ifDetails) {
if !manualRule.If.IsMet(pr, ifDetails) {
continue
}

// Get check status from current comment, if any.
checkedBy := ""
check, ok := checks[manualRule.description]
check, ok := checks[manualRule.Description]
if ok {
checkedBy = check.checkedBy
}

commentContent.ManualRules = append(
commentContent.ManualRules,
ManualContent{
Description: manualRule.description,
Description: manualRule.Description,
ConditionDetails: ifDetails.String(),
CheckedBy: checkedBy,
Teams: manualRule.teams,
Teams: manualRule.Teams,
},
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,118 +1,131 @@
package params
package check

import (
"context"
"flag"
"fmt"
"os"
"time"

"github.com/gnolang/gno/contribs/github-bot/internal/utils"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/sethvargo/go-githubactions"
)

type Params struct {
type checkFlags struct {
Owner string
Repo string
PRAll bool
PRNums PRList
Verbose bool
PRNums utils.PRList
Verbose *bool
DryRun bool
Timeout time.Duration
flagSet *flag.FlagSet
}

func (p *Params) RegisterFlags(fs *flag.FlagSet) {
func NewCheckCmd(verbose *bool) *commands.Command {
flags := &checkFlags{Verbose: verbose}

return commands.NewCommand(
commands.Metadata{
Name: "check",
ShortUsage: "github-bot check [flags]",
ShortHelp: "checks requirements for a pull request to be merged",
LongHelp: "This tool checks if the requirements for a pull request to be merged are satisfied (defined in ./internal/config/config.go) and displays PR status checks accordingly.\nA valid GitHub Token must be provided by setting the GITHUB_TOKEN environment variable.",
},
flags,
func(_ context.Context, _ []string) error {
flags.validateFlags()
return execCheck(flags)
},
)
}

func (flags *checkFlags) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&p.Owner,
&flags.Owner,
"owner",
"",
"owner of the repo to process, if empty, will be retrieved from GitHub Actions context",
)

fs.StringVar(
&p.Repo,
&flags.Repo,
"repo",
"",
"repo to process, if empty, will be retrieved from GitHub Actions context",
)

fs.BoolVar(
&p.PRAll,
&flags.PRAll,
"pr-all",
false,
"process all opened pull requests",
)

fs.TextVar(
&p.PRNums,
&flags.PRNums,
"pr-numbers",
PRList(nil),
utils.PRList(nil),
"pull request(s) to process, must be a comma separated list of PR numbers, e.g '42,1337,7890'. If empty, will be retrieved from GitHub Actions context",
)

fs.BoolVar(
&p.Verbose,
"verbose",
false,
"set logging level to debug",
)

fs.BoolVar(
&p.DryRun,
&flags.DryRun,
"dry-run",
false,
"print if pull request requirements are satisfied without updating anything on GitHub",
)

fs.DurationVar(
&p.Timeout,
&flags.Timeout,
"timeout",
0,
"timeout after which the bot execution is interrupted",
)

p.flagSet = fs
flags.flagSet = fs
}

func (p *Params) ValidateFlags() {
func (flags *checkFlags) validateFlags() {
// Helper to display an error + usage message before exiting.
errorUsage := func(err string) {
fmt.Fprintf(p.flagSet.Output(), "Error: %s\n\n", err)
p.flagSet.Usage()
fmt.Fprintf(flags.flagSet.Output(), "Error: %s\n\n", err)
flags.flagSet.Usage()
os.Exit(1)
}

// Check if flags are coherent.
if p.PRAll && len(p.PRNums) != 0 {
if flags.PRAll && len(flags.PRNums) != 0 {
errorUsage("You can specify only one of the '-pr-all' and '-pr-numbers' flags.")
}

// If one of these values is empty, it must be retrieved
// from GitHub Actions context.
if p.Owner == "" || p.Repo == "" || (len(p.PRNums) == 0 && !p.PRAll) {
if flags.Owner == "" || flags.Repo == "" || (len(flags.PRNums) == 0 && !flags.PRAll) {
actionCtx, err := githubactions.Context()
if err != nil {
errorUsage(fmt.Sprintf("Unable to get GitHub Actions context: %v.", err))
}

if p.Owner == "" {
if p.Owner, _ = actionCtx.Repo(); p.Owner == "" {
if flags.Owner == "" {
if flags.Owner, _ = actionCtx.Repo(); flags.Owner == "" {
errorUsage("Unable to retrieve owner from GitHub Actions context, you may want to set it using -onwer flag.")
}
}
if p.Repo == "" {
if _, p.Repo = actionCtx.Repo(); p.Repo == "" {
if flags.Repo == "" {
if _, flags.Repo = actionCtx.Repo(); flags.Repo == "" {
errorUsage("Unable to retrieve repo from GitHub Actions context, you may want to set it using -repo flag.")
}
}

if len(p.PRNums) == 0 && !p.PRAll {
if len(flags.PRNums) == 0 && !flags.PRAll {
prNum, err := utils.GetPRNumFromActionsCtx(actionCtx)
if err != nil {
errorUsage(fmt.Sprintf("Unable to retrieve pull request number from GitHub Actions context: %s\nYou may want to set it using -pr-numbers flag.", err.Error()))
}

p.PRNums = PRList{prNum}
flags.PRNums = utils.PRList{prNum}
}
}
}
Loading

0 comments on commit 890ab3a

Please sign in to comment.