Skip to content

Commit

Permalink
feat: allow enable needs-retitle like a non external plugin (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
belitre authored Jul 7, 2020
1 parent 78a7b31 commit e5619c5
Show file tree
Hide file tree
Showing 7 changed files with 499 additions and 50 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Overview](#overview)
- [Configuration](#configuration)
- [Enable only in some repositories of an organization](#enable-only-in-some-repositories-of-an-organization)
- [Build](#build)
- [Deploy](#deploy)

Expand Down Expand Up @@ -77,6 +78,74 @@ You'll need to add new things to your prow `plugins.yaml` file:
- needs-retitle
```

### Enable only in some repositories of an organization

To enable the plugin only in some repositories of the same organization you have two options:

* You can add to the `external_plugins` options all the repositories you want like:

```
external_plugins:
org-foo/repo-bar:
- name: needs-retitle
events:
- pull_request
org-foo/repo-bar-2:
- name: needs-retitle
events:
- pull_request
org-foo/repo-bar-3:
- name: needs-retitle
events:
- pull_request
...
```

* You can use `require_enable_as_not_external`, this way you can enable the plugin for the whole organization at the `external_plugins` config level, and then just add the plugin to each repo like any other plugin, example:

* First we enable the `require_enable_as_not_external` setting for the plugin:

```
needs_retitle:
regexp: "^(fix:|feat:|major:).*$"
require_enable_as_not_external: true
error_message: |
Invalid title for the PR, the title needs to be like:
fix: this is a fix commit
feat: this is a feature commit
major: this is a major commit
```

* Then we enable the plugin for the whole organization in `external_plugins`:

```
external_plugins:
org-foo:
- name: needs-retitle
events:
- pull_request
```

* And now we enable it like any other plugin for the repositories:

```
plugins:
org-foo/test-infra:
- approve
- assign
- config-updater
- needs-retitle
org-foo/my-repo:
- approve
- assign
- lgtm
- needs-retitle
org-foo/another-repo:
- trigger
- needs-retitle
```

## Build

* To make just the binary run: `make build`
Expand Down
6 changes: 4 additions & 2 deletions cmd/needs-retitle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ func main() {
log.WithError(err).Fatalf("Error loading plugin config from %q.", o.pluginConfig)
}

pca := config.NewPluginConfigAgent()
p := plugin.NewPlugin(pa)

pca := config.NewPluginConfigAgent(p)
if err := pca.Start(o.pluginConfig); err != nil {
log.WithError(err).Fatalf("Error loading %s config from %q.", plugin.PluginName, o.pluginConfig)
}
Expand All @@ -116,7 +118,7 @@ func main() {

interrupts.TickLiteral(func() {
start := time.Now()
if err := pca.GetPlugin().HandleAll(log, githubClient, pa.Config()); err != nil {
if err := pca.GetPlugin().HandleAll(log, githubClient); err != nil {
log.WithError(err).Error("Error during periodic update of all PRs.")
}
log.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Periodic update complete.")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
gotest.tools v2.2.0+incompatible
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
k8s.io/test-infra v0.0.0-20200702033203-1e71b3526aef
sigs.k8s.io/yaml v1.2.0
Expand Down
42 changes: 7 additions & 35 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,23 @@
package config

import (
"fmt"
"io/ioutil"
"regexp"
"time"

"github.com/ouzi-dev/needs-retitle/pkg/plugin"
"github.com/ouzi-dev/needs-retitle/pkg/types"
"github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
)

// ConfigAgent contains the agent mutex and the Agent configuration.
type PluginConfigAgent struct {
configuration *Configuration
plugin *plugin.Plugin
plugin *plugin.Plugin
}

// Configuration is the top-level serialization target for plugin Configuration.
type Configuration struct {
NeedsRetitle NeedsRetitle `json:"needs_retitle"`
}

type NeedsRetitle struct {
Regexp string `json:"regexp"`
ErrorMessage string `json:"error_message"`
}

func NewPluginConfigAgent() *PluginConfigAgent {
func NewPluginConfigAgent(p *plugin.Plugin) *PluginConfigAgent {
return &PluginConfigAgent{
plugin: &plugin.Plugin{},
plugin: p,
}
}

Expand Down Expand Up @@ -62,7 +50,7 @@ func (pca *PluginConfigAgent) Load(path string) error {
if err != nil {
return err
}
np := &Configuration{}
np := &types.Configuration{}
if err := yaml.Unmarshal(b, np); err != nil {
return err
}
Expand All @@ -76,22 +64,6 @@ func (pca *PluginConfigAgent) Load(path string) error {
}

// Set sets the plugin agent configuration.
func (pca *PluginConfigAgent) Set(pc *Configuration) {
pca.configuration = pc
r, _ := regexp.Compile(pca.configuration.NeedsRetitle.Regexp)
pca.plugin.SetConfig(pc.NeedsRetitle.ErrorMessage, r)
}

func (c *Configuration) Validate() error {
if len(c.NeedsRetitle.Regexp) == 0 {
return fmt.Errorf("needs_pr_rename.regexp can not be empty")
}

_, err := regexp.Compile(c.NeedsRetitle.Regexp)

if err != nil {
return fmt.Errorf("error compiling regular expression %s: %v", c.NeedsRetitle.Regexp, err)
}

return nil
func (pca *PluginConfigAgent) Set(pc *types.Configuration) {
pca.plugin.SetConfig(pc)
}
64 changes: 56 additions & 8 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"sync"
"time"

"github.com/ouzi-dev/needs-retitle/pkg/types"
githubql "github.com/shurcooL/githubv4"
"github.com/sirupsen/logrus"

Expand Down Expand Up @@ -57,11 +58,19 @@ type githubClient interface {
type Plugin struct {
mut sync.Mutex
c *pluginConfig
ca *plugins.ConfigAgent
}

type pluginConfig struct {
errorMessage string
re *regexp.Regexp
config *types.Configuration
}

func NewPlugin(ca *plugins.ConfigAgent) *Plugin {
return &Plugin{
ca: ca,
}
}

// HelpProvider constructs the PluginHelp for this plugin that takes into account enabled repositories.
Expand All @@ -74,18 +83,21 @@ The plugin reacts to commit changes on PRs in addition to periodically scanning
nil
}

func (p *Plugin) SetConfig(m string, r *regexp.Regexp) {
func (p *Plugin) SetConfig(c *types.Configuration) {
p.mut.Lock()
defer p.mut.Unlock()

r, _ := regexp.Compile(c.NeedsRetitle.Regexp)

errorMessage := fmt.Sprintf(defaultNeedsRetitleMessage, r.String())
if len(m) > 0 {
errorMessage = m
if len(c.NeedsRetitle.ErrorMessage) > 0 {
errorMessage = c.NeedsRetitle.ErrorMessage
}

p.c = &pluginConfig{
errorMessage: errorMessage,
re: r,
config: c,
}
}

Expand Down Expand Up @@ -131,6 +143,20 @@ func (p *Plugin) handle(log *logrus.Entry, ghc githubClient, pr *github.PullRequ

org := pr.Base.Repo.Owner.Login
repo := pr.Base.Repo.Name

pluginConfig := p.getConfig()

if pluginConfig.config.NeedsRetitle.RequireEnableAsNotExternal {
config := p.ca.Config()
if orgs, repos := config.EnabledReposForPlugin(PluginName); orgs != nil || repos != nil {
orgRepo := fmt.Sprintf("%s/%s", org, repo)
if !isInArray(org, orgs) && !isInArray(orgRepo, repos) {
log.Warnf("Repo %s not enabled for the %s plugin", orgRepo, PluginName)
return nil
}
}
}

number := pr.Number
title := pr.Title

Expand All @@ -140,14 +166,36 @@ func (p *Plugin) handle(log *logrus.Entry, ghc githubClient, pr *github.PullRequ
}
hasLabel := github.HasLabel(needsRetitleLabel, issueLabels)

return p.takeAction(log, ghc, org, repo, number, pr.User.Login, hasLabel, title)
return p.takeAction(log, ghc, org, repo, number, pr.User.Login, hasLabel, title, pluginConfig)
}

func isInArray(s string, a []string) bool {
for _, v := range a {
if s == v {
return true
}
}

return false
}

// HandleAll checks all orgs and repos that enabled this plugin for open PRs to
// determine if the "needs-retitle" label needs to be added or removed.
func (p *Plugin) HandleAll(log *logrus.Entry, ghc githubClient, config *plugins.Configuration) error {
func (p *Plugin) HandleAll(log *logrus.Entry, ghc githubClient) error {
log.Info("Checking all PRs.")
orgs, repos := config.EnabledReposForExternalPlugin(PluginName)

pluginConfig := p.getConfig()
config := p.ca.Config()

var orgs []string
var repos []string

if pluginConfig.config.NeedsRetitle.RequireEnableAsNotExternal {
orgs, repos = config.EnabledReposForPlugin(PluginName)
} else {
orgs, repos = config.EnabledReposForExternalPlugin(PluginName)
}

if len(orgs) == 0 && len(repos) == 0 {
log.Warnf("No repos have been configured for the %s plugin", PluginName)
return nil
Expand Down Expand Up @@ -192,6 +240,7 @@ func (p *Plugin) HandleAll(log *logrus.Entry, ghc githubClient, config *plugins.
string(pr.Author.Login),
hasLabel,
title,
pluginConfig,
)
if err != nil {
l.WithError(err).Error("Error handling PR.")
Expand All @@ -203,8 +252,7 @@ func (p *Plugin) HandleAll(log *logrus.Entry, ghc githubClient, config *plugins.
// takeAction adds or removes the "needs-rebase" label based on the current
// state of the PR (hasLabel and mergeable). It also handles adding and
// removing GitHub comments notifying the PR author that a rebase is needed.
func (p *Plugin) takeAction(log *logrus.Entry, ghc githubClient, org, repo string, num int, author string, hasLabel bool, title string) error {
c := p.getConfig()
func (p *Plugin) takeAction(log *logrus.Entry, ghc githubClient, org, repo string, num int, author string, hasLabel bool, title string, c *pluginConfig) error {
needsRetitleMessage := c.errorMessage
titleOk := c.re.MatchString(title)
if !titleOk && !hasLabel {
Expand Down
Loading

0 comments on commit e5619c5

Please sign in to comment.