diff --git a/pkg/sources/github/README.md b/pkg/sources/github/README.md new file mode 100644 index 00000000000..c71e1061496 --- /dev/null +++ b/pkg/sources/github/README.md @@ -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": "", + "secretToken": "" + } + +``` + +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: / # 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 + +``` \ No newline at end of file diff --git a/pkg/sources/github/events.go b/pkg/sources/github/events.go new file mode 100644 index 00000000000..310fd55287e --- /dev/null +++ b/pkg/sources/github/events.go @@ -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{ + PullrequestEvent: "pull_request", +} diff --git a/pkg/sources/github/eventsource.yaml b/pkg/sources/github/eventsource.yaml index ee8e685bfb2..a86410f0a4b 100644 --- a/pkg/sources/github/eventsource.yaml +++ b/pkg/sources/github/eventsource.yaml @@ -18,8 +18,7 @@ metadata: name: github namespace: default spec: - type: github source: github - image: github.com/knative/eventing/pkg/sources/github + image: github.com/knative/eventing/pkg/sources/github/feedlet parameters: image: github.com/knative/eventing/pkg/sources/github/receive_adapter diff --git a/pkg/sources/github/eventtype.yaml b/pkg/sources/github/eventtype.yaml index 049018a74a3..32069634693 100644 --- a/pkg/sources/github/eventtype.yaml +++ b/pkg/sources/github/eventtype.yaml @@ -15,7 +15,7 @@ apiVersion: feeds.knative.dev/v1alpha1 kind: EventType metadata: - name: pullrequest + name: dev.knative.github.pullrequest namespace: default spec: eventSource: github diff --git a/pkg/sources/github/feedlet.go b/pkg/sources/github/feedlet/feedlet.go similarity index 99% rename from pkg/sources/github/feedlet.go rename to pkg/sources/github/feedlet/feedlet.go index f4cbcfa83be..5f479d33f20 100644 --- a/pkg/sources/github/feedlet.go +++ b/pkg/sources/github/feedlet/feedlet.go @@ -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 { return []string(nil), fmt.Errorf("event type is unknown: %s", eventType) } + return []string{event}, nil } diff --git a/pkg/sources/github/receive_adapter/receive_adapter.go b/pkg/sources/github/receive_adapter/receive_adapter.go index 7ff51036574..78238a695ec 100644 --- a/pkg/sources/github/receive_adapter/receive_adapter.go +++ b/pkg/sources/github/receive_adapter/receive_adapter.go @@ -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) + + pl := payload.(gh.PullRequestPayload) + + source := pl.PullRequest.HTMLURL + + eventType, ok := github.CloudEventType[hdr.Get("X-GitHub-Event")] + 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)) + } return nil } diff --git a/sample/github/legit.go b/sample/github/legit.go index 766b2ef62f5..bde49f65c79 100644 --- a/sample/github/legit.go +++ b/sample/github/legit.go @@ -22,9 +22,9 @@ import ( "flag" "fmt" ghclient "github.com/google/go-github/github" + "github.com/knative/eventing/pkg/event" "golang.org/x/oauth2" "gopkg.in/go-playground/webhooks.v3/github" - "io/ioutil" "log" "net/http" "os" @@ -49,20 +49,8 @@ type GithubSecrets struct { SecretToken string `json:"secretToken"` } -func (h *GithubHandler) handlePost(rw http.ResponseWriter, req *http.Request) { - body, err := ioutil.ReadAll(req.Body) - if err != nil { - panic(err) - } - rw.WriteHeader(200) - - var pl github.PullRequestPayload - if err := json.Unmarshal(body, &pl); err != nil { - log.Printf("error: could not unmarshal payload %v", err) - return - } +func (h *GithubHandler) newPullRequestPayload(ctx context.Context, pl *github.PullRequestPayload) { - // Do whatever you want from here... title := pl.PullRequest.Title log.Printf("GOT PR with Title: %q", title) @@ -76,7 +64,8 @@ func (h *GithubHandler) handlePost(rw http.ResponseWriter, req *http.Request) { updatedPR := ghclient.PullRequest{ Title: &newTitle, } - newPR, response, err := h.client.PullRequests.Edit(h.ctx, pl.Repository.Owner.Login, pl.Repository.Name, int(pl.Number), &updatedPR) + newPR, response, err := h.client.PullRequests.Edit(h.ctx, + pl.Repository.Owner.Login, pl.Repository.Name, int(pl.Number), &updatedPR) if err != nil { log.Printf("Failed to update PR: %s\n%s", err, response) return @@ -115,6 +104,5 @@ func main() { ctx: ctx, } - http.HandleFunc("/", h.handlePost) - log.Fatal(http.ListenAndServe(":8080", nil)) + log.Fatal(http.ListenAndServe(":8080", event.Handler(h.newPullRequestPayload))) }