Skip to content

Commit

Permalink
refactor: matrix/check cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
aeddi committed Nov 29, 2024
1 parent 97b2159 commit d11ad5a
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 155 deletions.
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
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,16 +55,16 @@ 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 {
prs = make([]*github.PullRequest, len(flags.PRNums))
for i, prNum := range flags.PRNums {
pr, _, err := gh.Client.PullRequests.Get(gh.Ctx, gh.Owner, gh.Repo, prNum)
if err != nil {
return fmt.Errorf("unable to retrieve specified pull request (%d): %w", prNum, err)
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
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}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package check

import (
"bytes"
Expand All @@ -9,6 +9,7 @@ import (
"text/template"

"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/utils"

"github.com/google/go-github/v64/github"
Expand Down Expand Up @@ -157,12 +158,12 @@ func handleCommentUpdate(gh *client.GitHub, actionCtx *githubactions.GitHubConte
// Get teams allowed to edit this box from config.
var teams []string
found := false
_, manualRules := config(gh)
_, manualRules := config.Config(gh)

for _, manualRule := range manualRules {
if manualRule.description == key {
if manualRule.Description == key {
found = true
teams = manualRule.teams
teams = manualRule.Teams
}
}

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package check

import (
"context"
Expand Down
Loading

0 comments on commit d11ad5a

Please sign in to comment.