-
Notifications
You must be signed in to change notification settings - Fork 630
Convert github source to cloud events. #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| Knative GitHub Source | ||
| ===================== | ||
|
|
||
| This source will enable receiving of GitHub events from within Knative Eventing. | ||
|
|
||
| ## Events | ||
|
|
||
| - dev.knative.github.pullrequest | ||
|
|
||
| ## Images | ||
|
|
||
| Uses two images, the feedlet and the receive adapter. | ||
|
|
||
| ### Feedlet | ||
|
|
||
| A Feedlet is a container to create or destroy event source bindings. | ||
|
|
||
| The Feedlet is run as a Job by the Feed controller. | ||
|
|
||
| This Feedlet will create the Receive Adapter and register a webhook in GitHub | ||
| for the account provided. | ||
|
|
||
| ### Receive Adapter | ||
|
|
||
| The Receive Adapter is a Service.serving.knative.dev resource that is registered | ||
| to receive GitHub webhook requests. The source will wrap this request in a | ||
| CloudEvent and send it to the action provided. | ||
|
|
||
| ## Usage | ||
|
|
||
| The GitHub Source expects there to be a secret in the following form: | ||
|
|
||
| ```yaml | ||
| apiVersion: v1 | ||
| kind: Secret | ||
| metadata: | ||
| name: githubsecret | ||
| type: Opaque | ||
| stringData: | ||
| githubCredentials: > | ||
| { | ||
| "accessToken": "<YOUR PERSONAL TOKEN FROM GITHUB>", | ||
| "secretToken": "<YOUR RANDOM STRING>" | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| The Feedlet requires a ServiceAccount to run as a cluster admin for the targeted namespace: | ||
|
|
||
| ```yaml | ||
| apiVersion: v1 | ||
| kind: ServiceAccount | ||
| metadata: | ||
| name: feed-sa | ||
| namespace: default | ||
| --- | ||
| apiVersion: rbac.authorization.k8s.io/v1beta1 | ||
| kind: ClusterRoleBinding | ||
| metadata: | ||
| name: feed-admin | ||
| subjects: | ||
| - kind: ServiceAccount | ||
| name: feed-sa | ||
| namespace: default | ||
| roleRef: | ||
| kind: ClusterRole | ||
| name: cluster-admin | ||
| apiGroup: rbac.authorization.k8s.io | ||
|
|
||
| ``` | ||
|
|
||
| To create a Receive Adapter (via the Feedlet), a Flow needs to exist: | ||
|
|
||
| ```yaml | ||
| apiVersion: flows.knative.dev/v1alpha1 | ||
| kind: Flow | ||
| metadata: | ||
| name: my-github-flow | ||
| namespace: default | ||
| spec: | ||
| serviceAccountName: feed-sa | ||
| trigger: | ||
| eventType: pullrequest | ||
| resource: <org>/<repo> # TODO: Fill this out | ||
| service: github | ||
| parameters: | ||
| secretName: githubsecret | ||
| secretKey: githubCredentials | ||
| parametersFrom: | ||
| - secretKeyRef: | ||
| name: githubsecret | ||
| key: githubCredentials | ||
| action: | ||
| target: | ||
| kind: Service | ||
| apiVersion: serving.knative.dev/v1alpha1 | ||
| name: my-service | ||
|
|
||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* | ||
| Copyright 2018 The Knative Authors | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package github | ||
|
|
||
| const ( | ||
| // Event Names | ||
| PullrequestEvent = "dev.knative.github.pullrequest" | ||
| UnsupportedEvent = "dev.knative.github.unsupported" | ||
| ) | ||
|
|
||
| var CloudEventType = map[string]string{ | ||
| "pull_request": PullrequestEvent, | ||
| "": UnsupportedEvent, | ||
| } | ||
|
|
||
| var GithubEventType = map[string]string{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this just the reverse of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, is there a better way?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Build it automatically, like: var GithubEventType map[string]string
for k, v := range(CloudEventType) {
GitHubEventType[v] = k
} |
||
| PullrequestEvent: "pull_request", | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ | |
| apiVersion: feeds.knative.dev/v1alpha1 | ||
| kind: EventType | ||
| metadata: | ||
| name: pullrequest | ||
| name: dev.knative.github.pullrequest | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-registration would make it easier to make this name align with the one in |
||
| namespace: default | ||
| spec: | ||
| eventSource: github | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ import ( | |
| "os" | ||
|
|
||
| ghclient "github.com/google/go-github/github" | ||
| "github.com/knative/eventing/pkg/sources/github" | ||
| "github.com/knative/eventing/pkg/sources/github/resources" | ||
| "github.com/knative/serving/pkg/apis/serving/v1alpha1" | ||
| "k8s.io/apimachinery/pkg/watch" | ||
|
|
@@ -451,11 +452,9 @@ func parseEventsFrom(eventType string) ([]string, error) { | |
| if len(eventType) == 0 { | ||
| return []string(nil), fmt.Errorf("event type is empty") | ||
| } | ||
| switch eventType { | ||
| case "pullrequest": | ||
| return []string{"pull_request"}, nil | ||
| // TODO: Add more supported event types. | ||
| default: | ||
| event, ok := github.GithubEventType[eventType] | ||
| if !ok { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may also be able to check |
||
| return []string(nil), fmt.Errorf("event type is unknown: %s", eventType) | ||
| } | ||
| return []string{event}, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,15 +22,18 @@ import ( | |
| "fmt" | ||
| "os" | ||
|
|
||
| webhooks "gopkg.in/go-playground/webhooks.v3" | ||
| "gopkg.in/go-playground/webhooks.v3/github" | ||
| "gopkg.in/go-playground/webhooks.v3" | ||
| gh "gopkg.in/go-playground/webhooks.v3/github" | ||
|
|
||
| "bytes" | ||
| "io/ioutil" | ||
| "net/http" | ||
|
|
||
| ghclient "github.com/google/go-github/github" | ||
| "github.com/google/uuid" | ||
| "github.com/knative/eventing/pkg/event" | ||
| "github.com/knative/eventing/pkg/sources/github" | ||
| "log" | ||
| "time" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -54,8 +57,26 @@ type GithubSecrets struct { | |
| // HandlePullRequest is invoked whenever a PullRequest is modified (created, updated, etc.) | ||
| func (h *GithubHandler) HandlePullRequest(payload interface{}, header webhooks.Header) { | ||
| log.Print("Handling Pull Request") | ||
| pl := payload.(github.PullRequestPayload) | ||
| postMessage(h.target, &pl) | ||
|
|
||
| hdr := http.Header(header) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a webhooks.Header is an http.Header, so it should already have
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not have access to .Get() from the webhooks.Header object for some reason, it was a compile error when I was trying.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha -- http.Header is an So glad they "minimize imports": https://github.com/go-playground/webhooks/blob/v3.13.0/webhooks.go#L8 It looks like this declaration was removed in v5. |
||
|
|
||
| pl := payload.(gh.PullRequestPayload) | ||
|
|
||
| source := pl.PullRequest.HTMLURL | ||
|
|
||
| eventType, ok := github.CloudEventType[hdr.Get("X-GitHub-Event")] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does, this catches all other github events that have a real name but are not "" and "pull_request"
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, right. I was thinking about the "header missing" case. |
||
| if !ok { | ||
| eventType = github.UnsupportedEvent | ||
| } | ||
|
|
||
| eventID := hdr.Get("X-GitHub-Delivery") | ||
| if len(eventID) == 0 { | ||
| if uuid, err := uuid.NewRandom(); err != nil { | ||
| eventID = uuid.String() | ||
| } | ||
| } | ||
|
|
||
| postMessage(h.target, payload, source, eventType, eventID) | ||
| } | ||
|
|
||
| func main() { | ||
|
|
@@ -86,10 +107,10 @@ func main() { | |
| target: target, | ||
| } | ||
|
|
||
| hook := github.New(&github.Config{Secret: credentials.SecretToken}) | ||
| hook := gh.New(&gh.Config{Secret: credentials.SecretToken}) | ||
| // TODO: GitHub has more than just Pull Request Events. This needs to | ||
| // handle them all? | ||
| hook.RegisterEvents(h.HandlePullRequest, github.PullRequestEvent) | ||
| hook.RegisterEvents(h.HandlePullRequest, gh.PullRequestEvent) | ||
|
|
||
| // TODO(n3wscott): Do we need to configure the PORT? | ||
| err = webhooks.Run(hook, ":8080", "/") | ||
|
|
@@ -98,30 +119,35 @@ func main() { | |
| } | ||
| } | ||
|
|
||
| func postMessage(target string, m *github.PullRequestPayload) error { | ||
| jsonStr, err := json.Marshal(m) | ||
| if err != nil { | ||
| log.Printf("Error: Failed to marshal the message: %+v : %v", m, err) | ||
| return err | ||
| } | ||
|
|
||
| func postMessage(target string, payload interface{}, source, eventType, eventID string) error { | ||
| URL := fmt.Sprintf("http://%s/", target) | ||
| req, err := http.NewRequest("POST", URL, bytes.NewBuffer(jsonStr)) | ||
|
|
||
| ctx := event.EventContext{ | ||
| CloudEventsVersion: event.CloudEventsVersion, | ||
| EventType: eventType, | ||
| EventID: eventID, | ||
| EventTime: time.Now(), | ||
| Source: source, | ||
| } | ||
| req, err := event.Binary.NewRequest(URL, payload, ctx) | ||
| if err != nil { | ||
| log.Printf("Error: Failed to create http request: %v", err) | ||
| log.Printf("Failed to marshal the message: %+v : %s", payload, err) | ||
| return err | ||
| } | ||
|
|
||
| req.Header.Set("Content-Type", "application/json") | ||
| log.Printf("Posting to %q", URL) | ||
| client := &http.Client{} | ||
| resp, err := client.Do(req) | ||
| if err != nil { | ||
| log.Printf("Error: Failed to do POST: %v", err) | ||
| return err | ||
| } | ||
| defer resp.Body.Close() | ||
| log.Printf("Error: response Status: %s", resp.Status) | ||
| body, _ := ioutil.ReadAll(resp.Body) | ||
| log.Printf("Error: response Body: %s", string(body)) | ||
|
|
||
| if resp.StatusCode != http.StatusOK { | ||
| // TODO: in general, receive adapters may have to be able to retry for error cases. | ||
| log.Printf("response Status: %s", resp.Status) | ||
| body, _ := ioutil.ReadAll(resp.Body) | ||
| log.Printf("response Body: %s", string(body)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is a 500, should we return an error?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /idk There are some questions here that are bigger than this PR around what the Receive Adapter should do if it gets an error from the "action". Should it try again? How many times?... |
||
| } | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate that we can't put this in a yaml file somewhere to happen at the same time as the controller is installed/the source is created.