Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions pkg/webhook/gerrit/gerrit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package gerrit

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)

var (
ErrEventNotSpecifiedToParse = errors.New("no Event specified to parse")
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
ErrMissingGerritEvent = errors.New("missing type field")
ErrMissingHubSignatureHeader = errors.New("missing X-Hub-Signature-256 Header")
ErrEventNotFound = errors.New("event not defined to be parsed")
ErrParsingPayload = errors.New("error parsing payload")
ErrHMACVerificationFailed = errors.New("HMAC verification failed")
)

type Event string

const (
ChangeMergedEvent Event = "change-merged"
)

// Option is a configuration option for the webhook
type Option func(*Webhook) error

// Options is a namespace var for configuration options
var Options = WebhookOptions{}

// WebhookOptions is a namespace for configuration option methods
type WebhookOptions struct{}

// GerritWebhook instance contains all methods needed to process events
type Webhook struct {}

// New creates and returns a GerritWebhook instance
func New(options ...Option) (*Webhook, error) {
hook := new(Webhook)
for _, opt := range options {
if err := opt(hook); err != nil {
return nil, errors.New("error applying option")
}
}
return hook, nil
}
func (hook *Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) {
defer func() {
_, _ = io.Copy(io.Discard, r.Body)
_ = r.Body.Close()
}()

if len(events) == 0 {
return nil, ErrEventNotSpecifiedToParse
}
if r.Method != http.MethodPost {
return nil, ErrInvalidHTTPMethod
}

payload, err := io.ReadAll(r.Body)
if err != nil || len(payload) == 0 {
return nil, ErrParsingPayload
}

var envelope Envelope
err = json.Unmarshal([]byte(payload), &envelope)

if err != nil {
return nil, ErrParsingPayload
}

event := envelope.Type

if event == "" {
return nil, ErrMissingGerritEvent
}

gerritEvent := Event(event)

var found bool
for _, evt := range events {
if evt == gerritEvent {
found = true
break
}
}

// event not defined to be parsed
if !found {
return nil, ErrEventNotFound
}

switch gerritEvent {
case ChangeMergedEvent:
var pl ChangeMergedPayload
err = json.Unmarshal([]byte(payload), &pl)
return pl, err
default:
return nil, fmt.Errorf("unknown event %s", gerritEvent)
}

}

func ExtractRepoURL(payload ChangeMergedPayload) (string, error) {
// URL is in the format of https://<gerrit-host>/c/<project>/+/<change-id>
parts := strings.Split(payload.Change.URL, "/")
if len(parts) < 7 {
return "", fmt.Errorf("invalid URL %s", payload.Change.URL)
}

url := fmt.Sprintf("%s//%s/%s", parts[0], parts[2], parts[4])
return url, nil
}
65 changes: 65 additions & 0 deletions pkg/webhook/gerrit/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gerrit

type Envelope struct {
Submitter User `json:"-"`
NewRev string `json:"-"`
PatchSet PatchSet `json:"-"`
Change Change `json:"-"`
Project Project `json:"-"`
RefName string `json:"-"`
ChangeKey ChangeKey `json:"-"`
Type string `json:"type"`
EventCreatedOn int64 `json:"-"`
}

type ChangeMergedPayload struct {
Submitter User `json:"submitter"`
NewRev string `json:"newRev"`
PatchSet PatchSet `json:"patchSet"`
Change Change `json:"change"`
Project Project `json:"project"`
RefName string `json:"refName"`
ChangeKey ChangeKey `json:"changeKey"`
Type string `json:"type"`
EventCreatedOn int64 `json:"eventCreatedOn"`
}

type User struct {
Name string `json:"name"`
Email string `json:"email"`
Username string `json:"username"`
}

type PatchSet struct {
Number int64 `json:"number"`
Revision string `json:"revision"`
Parents []string `json:"parents"`
Ref string `json:"ref"`
Uploader User `json:"uploader"`
CreatedOn int64 `json:"createdOn"`
Author User `json:"author"`
Kind string `json:"kind"`
SizeInsertions int64 `json:"sizeInsertions"`
SizeDeletions int64 `json:"sizeDeletions"`
}

type Change struct {
Project string `json:"project"`
Branch string `json:"branch"`
ID string `json:"id"`
Number int64 `json:"number"`
Subject string `json:"subject"`
Owner User `json:"owner"`
URL string `json:"url"`
CommitMessage string `json:"commitMessage"`
CreatedOn int64 `json:"createdOn"`
Status string `json:"status"`
}

type Project struct {
Name string `json:"name"`
}

type ChangeKey struct {
Key string `json:"key"`
}
22 changes: 22 additions & 0 deletions pkg/webhook/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/go-playground/webhooks/v6/gitlab"
"github.com/go-playground/webhooks/v6/gogs"
corev1 "k8s.io/api/core/v1"

gerrit "github.com/rancher/fleet/pkg/webhook/gerrit"
)

const (
Expand Down Expand Up @@ -39,6 +41,9 @@ func parseWebhook(r *http.Request, secret *corev1.Secret) (any, error) {
return parseBitbucketServer(r, secret)
case r.Header.Get("X-Vss-Activityid") != "" || r.Header.Get("X-Vss-Subscriptionid") != "":
return parseAzureDevops(r, secret)
// Gerrit does not provide any discernible headers to identify the event. So we need to put it at the end.
case r.Header.Get("x-origin-url") != "":
return parseGerrit(r, secret)
}

return nil, nil
Expand Down Expand Up @@ -79,6 +84,23 @@ func parseGogs(r *http.Request, secret *corev1.Secret) (any, error) {
return hook.Parse(r, gogs.PushEvent)
}

func parseGerrit(r *http.Request, secret *corev1.Secret) (any, error) {
var hook *gerrit.Webhook
var err error

if secret != nil {
// Gerrit does not support secrets.
}

hook, err = gerrit.New()

if err != nil {
return nil, err
}

return hook.Parse(r, gerrit.ChangeMergedEvent)
}

func parseGithub(r *http.Request, secret *corev1.Secret) (any, error) {
var hook *github.Webhook
var err error
Expand Down
Loading