Skip to content

Commit d62302f

Browse files
authored
Actions status (#46)
1 parent 6f921a8 commit d62302f

File tree

13 files changed

+307
-27
lines changed

13 files changed

+307
-27
lines changed

.rivi.rules.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ rules:
111111
labeler:
112112
label: area/actions/slack
113113

114+
action-status:
115+
condition:
116+
files:
117+
patterns:
118+
- "engine/actions/status/"
119+
labeler:
120+
label: area/actions/status
121+
114122
action-trigger:
115123
condition:
116124
files:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Find out about available `condition` options [here](docs/condition.md)
9393
- [`trigger`](engine/actions/trigger/trigger.md) - Send HTTP triggers
9494
- [`locker`](engine/actions/locker/locker.md) - Lock an issue
9595
- [`slack`](engine/actions/slack/slack.md) - Send Slack messages
96+
- [`status`](engine/actions/status/status.md) - Update pull request commit status
9697

9798
# Example Configuration
9899

actions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ import (
88
_ "github.com/bivas/rivi/engine/actions/locker"
99
_ "github.com/bivas/rivi/engine/actions/sizing"
1010
_ "github.com/bivas/rivi/engine/actions/slack"
11+
_ "github.com/bivas/rivi/engine/actions/status"
1112
_ "github.com/bivas/rivi/engine/actions/trigger"
1213
)

connectors/github/client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ func (c *ghClient) handleFileContentResponse(file *github.RepositoryContent, err
3636
return content
3737
}
3838

39+
func (c *ghClient) SetStatus(sha string, description string, state string) {
40+
status := &github.RepoStatus{
41+
Context: github.String("rivi"),
42+
Description: &description,
43+
State: &state,
44+
}
45+
if _, _, err := c.client.Repositories.CreateStatus(context.Background(), c.owner, c.repo, sha, status); err != nil {
46+
c.logger.ErrorWith(log.MetaFields{log.E(err), log.F("repo", c.repo), log.F("sha", sha)}, "Unable to set status")
47+
}
48+
}
49+
3950
func (c *ghClient) GetFileContentFromRef(path, owner, repo, ref string) string {
4051
opts := &github.RepositoryContentGetOptions{Ref: ref}
4152
file, _, _, err := c.client.Repositories.GetContents(context.Background(), owner, repo, path, opts)

connectors/github/data.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,14 @@ func (d *data) GetFileExtensions() []string {
216216
func (d *data) GetChanges() (int, int) {
217217
return d.additions, d.deletions
218218
}
219+
220+
func (d *data) SetStatus(description string, state types.State) {
221+
set := "pending"
222+
switch state {
223+
case types.Failure:
224+
set = "failure"
225+
case types.Success:
226+
set = "success"
227+
}
228+
d.client.SetStatus(d.origin.SHA, description, set)
229+
}

connectors/github/pullrequest_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (h *pullRequestEventHandler) readFromJson(context *builderContext, payload
3737
Repo: head.Repo.Name,
3838
Ref: head.Ref,
3939
Head: head.Sha[0:6],
40+
SHA: head.Sha,
4041
GitURL: head.Repo.GitURL,
4142
}
4243
context.data.state = pr.State

engine/actions/status/rule.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package status
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const (
8+
defaultDescription = "Rivi completed processing rules"
9+
defaultState = "failure"
10+
unknownState = "error"
11+
)
12+
13+
var (
14+
states = []string{
15+
"error",
16+
"failure",
17+
"pending",
18+
"success",
19+
}
20+
searchState map[string]bool
21+
)
22+
23+
type rule struct {
24+
State string `mapstructure:"state"`
25+
Description string `mapstructure:"description"`
26+
}
27+
28+
func (r *rule) Defaults() {
29+
if r.State == "" {
30+
r.State = defaultState
31+
} else {
32+
search := strings.ToLower(r.State)
33+
if _, ok := searchState[search]; !ok {
34+
r.State = unknownState
35+
}
36+
}
37+
if r.Description == "" {
38+
r.Description = defaultDescription
39+
}
40+
}
41+
42+
func init() {
43+
searchState = make(map[string]bool)
44+
for _, s := range states {
45+
searchState[s] = true
46+
}
47+
}

engine/actions/status/status.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package status
2+
3+
import (
4+
"errors"
5+
6+
"github.com/bivas/rivi/engine/actions"
7+
"github.com/bivas/rivi/types"
8+
"github.com/bivas/rivi/util/log"
9+
10+
"github.com/mitchellh/mapstructure"
11+
"github.com/mitchellh/multistep"
12+
"github.com/prometheus/client_golang/prometheus"
13+
)
14+
15+
type action struct {
16+
rule *rule
17+
err error
18+
19+
logger log.Logger
20+
}
21+
22+
type HasSetStatusAPIData interface {
23+
SetStatus(string, types.State)
24+
}
25+
26+
func (a *action) Apply(state multistep.StateBag) {
27+
meta := state.Get("data").(types.Data)
28+
setStatus, ok := meta.(HasSetStatusAPIData)
29+
if !ok {
30+
a.logger.Warning("Event data does not support setting status. Check your configurations")
31+
a.err = errors.New("event data does not support setting status")
32+
return
33+
}
34+
setStatus.SetStatus(a.rule.Description, types.GetState(a.rule.State))
35+
}
36+
37+
type factory struct {
38+
}
39+
40+
func (*factory) BuildAction(config map[string]interface{}) actions.Action {
41+
item := rule{}
42+
logger := log.Get("status")
43+
if e := mapstructure.Decode(config, &item); e != nil {
44+
logger.ErrorWith(log.MetaFields{log.E(e)}, "Unable to build action")
45+
return nil
46+
}
47+
item.Defaults()
48+
return &action{rule: &item, logger: logger}
49+
}
50+
51+
var counter = actions.NewCounter("status")
52+
53+
func init() {
54+
actions.RegisterAction("status", &factory{})
55+
prometheus.Register(counter)
56+
}

engine/actions/status/status.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Setting Commit Status
2+
3+
## Goal
4+
5+
Ability to set a status to pull request submitted commit
6+
7+
## Requirements
8+
9+
None
10+
11+
## Options
12+
13+
- `description` (required) - status description to set
14+
- `state` (optional) - which state to set. can be `failure`, `pending` or `success` (default: `failure`)
15+
16+
## Example
17+
```yaml
18+
rules:
19+
example:
20+
status:
21+
description: pull request doesn't follow guidelines
22+
state: failure
23+
```
24+
or
25+
```yaml
26+
rules:
27+
example:
28+
status:
29+
description: pull request is okay
30+
state: success
31+
```
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package status
2+
3+
import (
4+
"testing"
5+
6+
"github.com/bivas/rivi/mocks"
7+
"github.com/bivas/rivi/types"
8+
"github.com/bivas/rivi/util/state"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestSerialization(t *testing.T) {
13+
input := map[string]interface{}{
14+
"state": "Success",
15+
"description": "this is a test",
16+
}
17+
18+
var f factory
19+
result := f.BuildAction(input)
20+
assert.NotNil(t, result, "should create action")
21+
s, ok := result.(*action)
22+
assert.True(t, ok, "should be of this package")
23+
assert.Equal(t, "Success", s.rule.State, "state")
24+
assert.Equal(t, "this is a test", s.rule.Description, "desc")
25+
}
26+
27+
func TestSetDefaultStatus(t *testing.T) {
28+
input := map[string]interface{}{
29+
"description": "TestSetDefaultStatus",
30+
}
31+
var f factory
32+
action := f.BuildAction(input)
33+
assert.NotNil(t, action, "should create action")
34+
meta := &mocks.MockData{}
35+
config := &mocks.MockConfiguration{}
36+
action.Apply(state.New(config, meta))
37+
assert.Equal(t, types.GetState(defaultState), meta.StatusState, "state")
38+
assert.Equal(t, "TestSetDefaultStatus", meta.StatusDescription, "desc")
39+
}
40+
41+
func TestSetUnknownStatus(t *testing.T) {
42+
input := map[string]interface{}{
43+
"state": "TestSetUnknownStatus",
44+
"description": "TestSetUnknownStatus",
45+
}
46+
var f factory
47+
action := f.BuildAction(input)
48+
assert.NotNil(t, action, "should create action")
49+
meta := &mocks.MockData{}
50+
config := &mocks.MockConfiguration{}
51+
action.Apply(state.New(config, meta))
52+
assert.Equal(t, types.GetState(unknownState), meta.StatusState, "state")
53+
assert.Equal(t, "TestSetUnknownStatus", meta.StatusDescription, "desc")
54+
}
55+
56+
func TestSetSuccessStatus(t *testing.T) {
57+
input := map[string]interface{}{
58+
"state": "Success",
59+
"description": "TestSetSuccessStatus",
60+
}
61+
var f factory
62+
action := f.BuildAction(input)
63+
assert.NotNil(t, action, "should create action")
64+
meta := &mocks.MockData{}
65+
config := &mocks.MockConfiguration{}
66+
action.Apply(state.New(config, meta))
67+
assert.Equal(t, types.Success, meta.StatusState, "state")
68+
assert.Equal(t, "TestSetSuccessStatus", meta.StatusDescription, "desc")
69+
}
70+
71+
func TestDefault(t *testing.T) {
72+
input := map[string]interface{}{}
73+
var f factory
74+
action := f.BuildAction(input)
75+
assert.NotNil(t, action, "should create action")
76+
meta := &mocks.MockData{}
77+
config := &mocks.MockConfiguration{}
78+
action.Apply(state.New(config, meta))
79+
assert.Equal(t, types.GetState(defaultState), meta.StatusState, "state")
80+
assert.Equal(t, defaultDescription, meta.StatusDescription, "desc")
81+
}

0 commit comments

Comments
 (0)