Skip to content

Commit

Permalink
Extract payload manipulation (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
bivas authored Oct 30, 2017
1 parent 21ebcfa commit 840869e
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 135 deletions.
163 changes: 36 additions & 127 deletions connectors/github/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,43 @@ import (
"io"
"io/ioutil"
"net/http"
"path/filepath"

"strings"

"errors"

"github.com/bivas/rivi/config/client"
"github.com/bivas/rivi/types"
"github.com/bivas/rivi/types/builder"
"github.com/bivas/rivi/util"
"github.com/bivas/rivi/util/log"
)

var (
supportedEventTypes = []string{
"issue_comment",
"pull_request",
"pull_request_review",
"pull_request_review_comment"}
)

type builderContext struct {
secret []byte
client *ghClient
data *data
}

type dataBuilder struct {
logger log.Logger
handlers map[string]eventHandler
defaultHandler eventHandler
logger log.Logger
}

func (builder *dataBuilder) validate(context *builderContext, payload []byte, request *http.Request) bool {
if len(context.secret) == 0 {
func validate(secret, payload []byte, request *http.Request) bool {
if len(secret) == 0 {
return true
}
h := hmac.New(sha1.New, context.secret)
h := hmac.New(sha1.New, secret)
h.Write(payload)
result := fmt.Sprintf("sha1=%s", hex.EncodeToString(h.Sum(nil)))
return request.Header.Get("X-Hub-Signature") == result
}

func (builder *dataBuilder) readPayload(context *builderContext, r *http.Request) (*payload, []byte, error) {
func ReadPayload(secret []byte, r *http.Request) (*payload, []byte, error) {
body := r.Body
defer body.Close()
raw, err := ioutil.ReadAll(io.LimitReader(body, r.ContentLength))
if err != nil {
return nil, raw, err
}
if !builder.validate(context, raw, r) {
if !validate(secret, raw, r) {
return nil, raw, fmt.Errorf("Payload could not be validated")
}
var pr payload
Expand All @@ -67,120 +55,41 @@ func (builder *dataBuilder) readPayload(context *builderContext, r *http.Request
return &pr, raw, nil
}

func (builder *dataBuilder) readFromJson(context *builderContext, payload *payload) {
pr := payload.PullRequest
if pr.Number > 0 {
context.data.number = pr.Number
} else {
context.data.number = payload.Number
}
context.data.title = pr.Title
context.data.description = pr.Body
context.data.changedFiles = pr.ChangedFiles
context.data.additions = pr.Additions
context.data.deletions = pr.Deletions
context.data.ref = pr.Base.Ref
head := pr.Head
context.data.origin = types.Origin{
User: strings.ToLower(head.User.Login),
Repo: head.Repo.Name,
Ref: head.Ref,
Head: head.Sha[0:6],
GitURL: head.Repo.GitURL,
}
context.data.state = pr.State
}

func (builder *dataBuilder) readFromClient(context *builderContext) {
id := context.data.number
context.data.assignees = context.client.GetAssignees(id)
context.data.state = context.client.GetState(id)
context.data.labels = context.client.GetLabels(id)
context.data.comments = context.client.GetComments(id)
fileNames := context.client.GetFileNames(id)
context.data.fileNames = fileNames
stringSet := util.StringSet{Transformer: filepath.Ext}
context.data.fileExt = stringSet.AddAll(fileNames).Values()
context.data.reviewers = context.client.GetReviewers(id)
context.data.locked = context.client.Locked(id)
}

func (builder *dataBuilder) checkProcessState(context *builderContext) bool {
builder.logger.DebugWith(log.MetaFields{log.F("issue", context.data.GetShortName())},
"Current state is '%s'", context.data.state)
return context.data.state != "closed"
func (builder *dataBuilder) findEventHandler(githubEvent string) eventHandler {
handler, ok := builder.handlers[githubEvent]
if !ok {
builder.logger.DebugWith(log.MetaFields{
log.F("eventType", githubEvent),
}, "Using default event handler")
handler = builder.defaultHandler
}
return handler
}

func (builder *dataBuilder) BuildFromHook(config client.ClientConfig, r *http.Request) (types.HookData, bool, error) {
githubEvent := r.Header.Get("X-Github-Event")
if githubEvent == "ping" {
builder.logger.Info("Got GitHub 'ping' event")
return nil, false, nil
}
supportedEvent := false
for _, event := range supportedEventTypes {
if event == githubEvent {
supportedEvent = true
}
}
if !supportedEvent {
builder.logger.Debug("Got GitHub '%s' event", githubEvent)
return nil, false, nil
}
context := &builderContext{secret: []byte(config.GetSecret())}
pl, raw, err := builder.readPayload(context, r)
if err != nil {
return nil, false, err
}
if pl.Number == 0 && pl.PullRequest.Number == 0 {
builder.logger.Warning("Payload appear to have issue id 0")
builder.logger.Debug("Faulty payload %+v", pl)
return nil, false, fmt.Errorf("Payload appear to have issue id 0")
}
repo := pl.Repository.Name
owner := pl.Repository.Owner.Login
installation := pl.Installation.ID
if installation > 0 {
context.client = newAppClient(config, owner, repo, installation)
} else {
context.client = newClient(config, owner, repo)
}
if context.client == nil {
return nil, false, errors.New("Unable to initialize github client")
}
context.data = &data{owner: owner, repo: repo, payload: raw, client: context.client}
builder.readFromJson(context, pl)
return context.data, builder.checkProcessState(context), nil
return builder.findEventHandler(githubEvent).FromRequest(config, r)
}

func (builder *dataBuilder) BuildFromPayload(config client.ClientConfig, raw []byte) (types.Data, bool, error) {
var pl payload
if e := json.Unmarshal(raw, &pl); e != nil {
return nil, false, e
}
repo := pl.Repository.Name
owner := pl.Repository.Owner.Login
installation := pl.Installation.ID
context := &builderContext{}
if installation > 0 {
context.client = newAppClient(config, owner, repo, installation)
} else {
context.client = newClient(config, owner, repo)
}
if context.client == nil {
return nil, false, errors.New("Unable to initialize github client")
}
context.data = &data{owner: owner, repo: repo, payload: raw, client: context.client}
builder.readFromJson(context, &pl)
if context.data.GetNumber() == 0 {
builder.logger.Warning("Payload appear to have issue id 0")
builder.logger.Debug("Faulty payload %+v", pl)
return nil, false, fmt.Errorf("Payload appear to have issue id 0")
}
builder.readFromClient(context)
return context.data, builder.checkProcessState(context), nil
func (builder *dataBuilder) BuildFromPayload(config client.ClientConfig, ofType string, raw []byte) (types.Data, bool, error) {
return builder.findEventHandler(ofType).FromPayload(config, raw)
}

var DataBuilder dataBuilder

func init() {
builder.RegisterNewDataBuilder("github", &dataBuilder{logger: log.Get("GitHub.DataBuilder")})
logger := log.Get("GitHub.DataBuilder")
prHandler := &pullRequestEventHandler{
logger: logger.Get("PullRequestHandler"),
}
DataBuilder = dataBuilder{
logger: logger,
handlers: map[string]eventHandler{
"pull_request": prHandler,
"pull_request_review": prHandler,
"pull_request_review_comment": prHandler,
},
defaultHandler: defaultHandler,
}
builder.RegisterNewDataBuilder("github", &DataBuilder)
}
50 changes: 50 additions & 0 deletions connectors/github/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package github

import (
"net/http"
"testing"

"github.com/bivas/rivi/config/client"
"github.com/bivas/rivi/mocks"
"github.com/bivas/rivi/types"
"github.com/stretchr/testify/assert"
)

type mockEventHandler struct {
FromRequestCalled bool
FromPayloadCalled bool
}

func (m *mockEventHandler) FromRequest(client.ClientConfig, *http.Request) (types.HookData, bool, error) {
m.FromRequestCalled = true
return nil, false, nil
}

func (m *mockEventHandler) FromPayload(client.ClientConfig, []byte) (types.Data, bool, error) {
m.FromPayloadCalled = true
return nil, false, nil
}

func TestRequestDefault(t *testing.T) {
DataBuilder.handlers["mock"] = &mockEventHandler{}
DataBuilder.defaultHandler = &mockEventHandler{}

request, err := http.NewRequest("GET", "http://example.com", nil)
assert.NoError(t, err, "shouldn't error")
_, cont, _ := DataBuilder.BuildFromHook(&mocks.MockClientConfig{}, request)
assert.False(t, cont, "shouldn't continue")
assert.True(t, DataBuilder.defaultHandler.(*mockEventHandler).FromRequestCalled)
}

func TestRequest(t *testing.T) {
DataBuilder.handlers["mock"] = &mockEventHandler{}
DataBuilder.defaultHandler = &mockEventHandler{}

request, err := http.NewRequest("GET", "http://example.com", nil)
assert.NoError(t, err, "shouldn't error")
request.Header.Set("X-Github-Event", "mock")
_, cont, _ := DataBuilder.BuildFromHook(&mocks.MockClientConfig{}, request)
assert.False(t, cont, "shouldn't continue")
assert.False(t, DataBuilder.defaultHandler.(*mockEventHandler).FromRequestCalled)
assert.True(t, DataBuilder.handlers["mock"].(*mockEventHandler).FromRequestCalled)
}
4 changes: 2 additions & 2 deletions connectors/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func (c *ghClient) Merge(issue int, mergeMethod string) {
}
}

func newClient(config client.ClientConfig, owner, repo string) *ghClient {
func NewClient(config client.ClientConfig, owner, repo string) *ghClient {
logger := log.Get("Github.Client")
if config.GetOAuthToken() == "" {
logger.ErrorWith(
Expand All @@ -291,7 +291,7 @@ func newClient(config client.ClientConfig, owner, repo string) *ghClient {
}
}

func newAppClient(config client.ClientConfig, owner, repo string, installation int) *ghClient {
func NewAppClient(config client.ClientConfig, owner, repo string, installation int) *ghClient {
logger := log.Get("Github.Client")
if config.GetApplicationID() == 0 || config.GetPrivateKeyFile() == "" {
logger.ErrorWith(
Expand Down
5 changes: 5 additions & 0 deletions connectors/github/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type data struct {
assignees []string
comments []types.Comment
payload []byte
eventType string
reviewers map[string]string
collaborators []string
repoLabels []string
Expand Down Expand Up @@ -116,6 +117,10 @@ func (d *data) GetRawPayload() []byte {
return d.payload
}

func (d *data) GetRawType() string {
return d.eventType
}

func (d *data) Merge(mergeMethod string) {
d.client.Merge(d.number, mergeMethod)
}
Expand Down
31 changes: 31 additions & 0 deletions connectors/github/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package github

import (
"net/http"

"github.com/bivas/rivi/config/client"
"github.com/bivas/rivi/types"
"github.com/bivas/rivi/util/log"
)

type eventHandler interface {
FromRequest(client.ClientConfig, *http.Request) (types.HookData, bool, error)
FromPayload(client.ClientConfig, []byte) (types.Data, bool, error)
}

type defaultEventHandler struct {
logger log.Logger
}

func (h *defaultEventHandler) FromRequest(config client.ClientConfig, r *http.Request) (types.HookData, bool, error) {
githubEvent := r.Header.Get("X-Github-Event")
h.logger.Info("Got GitHub '%s' event", githubEvent)
return nil, false, nil
}

func (h *defaultEventHandler) FromPayload(client.ClientConfig, []byte) (types.Data, bool, error) {
h.logger.Warning("Calling 'FromPayload' of default handler")
return nil, false, nil
}

var defaultHandler = &defaultEventHandler{log.Get("GitHub.DataBuilder.DefaultHandler")}
1 change: 1 addition & 0 deletions connectors/github/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type repositorySection struct {
Owner struct {
Login string `json:"login"`
} `json:"owner"`
Private bool `json:"private"`
}

type payload struct {
Expand Down
Loading

0 comments on commit 840869e

Please sign in to comment.