diff --git a/eventing/samples/github-events/Dockerfile b/eventing/samples/github-events/Dockerfile new file mode 100644 index 00000000000..9f71b85069a --- /dev/null +++ b/eventing/samples/github-events/Dockerfile @@ -0,0 +1,27 @@ +# 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 +# +# https://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. + +FROM golang AS builder + +WORKDIR /go/src/github.com/knative/docs/ +ADD . /go/src/github.com/knative/docs/ + +RUN CGO_ENABLED=0 go build ./eventing/samples/github-events + +FROM gcr.io/distroless/base + +COPY --from=builder /go/src/github.com/knative/docs/github-events /sample + +ENTRYPOINT ["/sample"] +EXPOSE 8080 diff --git a/eventing/samples/github-events/README.md b/eventing/samples/github-events/README.md new file mode 100644 index 00000000000..b2024c1125a --- /dev/null +++ b/eventing/samples/github-events/README.md @@ -0,0 +1,174 @@ +# Reacting to GitHub Events + +In response to a pull request event, the sample app _legit_ Service will add +`(looks pretty legit)` to the PR title. + +A GitHub webhook will be created on a repository and a Knative `Service` will be +deployed to receive the webhook's event deliveries and forward them into a +`Channel`, through a `Bus`, and out to the consumer via a `Subscription`. The +`Flow` resource takes care of provisioning the webhook, the `Service`, the +`Channel`, and the `Subscription`. + +## Prerequisites + +You will need: + +- A Kubernetes cluster with Knative serving installed. Follow the + [installation instructions](https://github.com/knative/docs/blob/master/install/README.md) + if you need to create one. +- [Docker](https://www.docker.com/) installed and running on your local machine, + and a Docker Hub account configured (you'll use it for a container registry). +- Knative eventing core installed on your Kubernetes cluster. You can install + with: + ```shell + kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release.yaml + ``` +- A domain name that allows GitHub to call into the cluster: Follow the + [assign a static IP address](https://github.com/knative/docs/blob/master/serving/gke-assigning-static-ip-address.md) + and + [configure a custom domain](https://github.com/knative/docs/blob/master/serving/using-a-custom-domain.md) + instructions. + +## Configuring Knative + +To use this sample, you'll need to install the `stub` ClusterBus and the +`github` EventSource: + +```shell +# Installs ClusterBus +kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-clusterbus-stub.yaml +# Installs EventSource +kubectl apply -f https://storage.googleapis.com/knative-releases/eventing/latest/release-source-github.yaml +``` + +## Granting permissions + +Because the `github` EventSource needs to create a Knative Service, you'll need +to provision a special ServiceAccount with the necessary permissions. + +The `eventing/samples/github-events/auth.yaml` file provisions a service +account, and creates a role which can create a Knative Service in the `default` +namespace. In a production environment, you might want to limit the access of +this service account to only specific namespaces. + +```shell +kubectl apply -f eventing/samples/github-events/auth.yaml +``` + +## Building and deploying the sample + +1. Use Docker to build the sample code into a container. To build and push with + Docker Hub, run the following commands, replacing `{username}` with your + Docker Hub username. Run these commands, r following from the _root_ of the + `knative/docs` repo: + + ```shell + # Build the container on your local machine + docker build -t {username}/github-events --file=eventing/samples/github-events/Dockerfile . + + # Push the container to docker registry + docker push {username}/github-events + ``` + +1. After the build has completed and the container is pushed to Docker Hub, you + can deploy the function into your cluster. **Ensure that the container image + value in `function.yaml` matches the container you built in the previous + step.** Apply the configuration using `kubectl`: + + ```shell + kubectl apply -f eventing/samples/github-events/function.yaml + ``` + +1. Check that your service is running using: + + ```shell + kubectl get services.serving.knative.dev -o "custom-columns=NAME:.metadata.name,READY:.status.conditions[2].status,REASON:.status.conditions[2].message" + NAME READY REASON + legit True + ``` + +1. Create a [personal access token](https://github.com/settings/tokens) to + GitHub repo that the GitHub source can use to register webhooks with the + GitHub API. Also decide on a token that your code will use to authenticate + the incoming webhooks from GitHub (*accessToken*). + + The token can be named anything you find convenient. This sample requires + full `repo` control to be able update the title of the _Pull Request_. + The Source requires `admin:repo_hook`, this allows it to create webhooks + into repos that your account is allowed to do so. Copy and save this token; + GitHub will force you to generate it again if misplaced. + + Here I named my token "EventingSample" and have selected the recommended + scopes: + + ![GitHub UI](personal_access_token.png "GitHub personal access token screenshot") + + Update `eventing/samples/github-events/githubsecret.yaml` with those + values. If your generated access token is `'asdfasfdsaf'` and you choose + your *secretToken* as `'personal_access_token_value'`, you'd modify + `eventing/samples/github-events/githubsecret.yaml` like so: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: githubsecret + type: Opaque + stringData: + githubCredentials: > + { + "accessToken": "asdfasfdsaf", + "secretToken": "personal_access_token_value" + } + ``` + + Hint: you can makeup a random *accessToken* with: + + ```shell + head -c 8 /dev/urandom | base64 + ``` + + Then, apply the githubsecret using `kubectl`: + + ```shell + kubectl apply -f eventing/samples/github-events/githubsecret.yaml + ``` + +1. Update the resource inside `eventing/samples/github-events/flow.yaml` to the + org/repo of your choosing. Note that the personal access token must be valid + for the chosen org/repo. + + Then create the flow sending GitHub Events to the service: + + ```shell + kubectl apply -f eventing/samples/github-events/flow.yaml + ``` + +1. Create a PR for the repo you configured the webhook for, and you'll see that + the Title will be modified with the suffix `(looks pretty legit)` + + +## Understanding what happened + +`TODO: similar to k8s-events.` + + + +## Cleaning up + +To clean up the function, `Flow`, auth, and secret: + +```shell +kubectl delete -f eventing/samples/github-events/function.yaml +kubectl delete -f eventing/samples/github-events/flow.yaml +kubectl delete -f eventing/samples/github-events/auth.yaml +kubectl delete -f eventing/samples/github-events/githubsecret.yaml +``` + +And then delete the [personal access token](https://github.com/settings/tokens) +created from GitHub. diff --git a/eventing/samples/github-events/auth.yaml b/eventing/samples/github-events/auth.yaml new file mode 100644 index 00000000000..d4ed3e8116e --- /dev/null +++ b/eventing/samples/github-events/auth.yaml @@ -0,0 +1,43 @@ +# 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. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: feed-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: create-knative-service + namespace: default +rules: +- apiGroups: ["serving.knative.dev"] + resources: ["services"] + verbs: ["get", "list", "watch", "create", "update", "delete", "patch"] +--- +# This enables the feed-sa to deploy the receive adapter. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: feed-sa-deploy + namespace: default +subjects: + - kind: ServiceAccount + name: feed-sa + namespace: default +roleRef: + kind: Role + name: create-knative-service + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/eventing/samples/github-events/flow.yaml b/eventing/samples/github-events/flow.yaml new file mode 100644 index 00000000000..cdf11438678 --- /dev/null +++ b/eventing/samples/github-events/flow.yaml @@ -0,0 +1,37 @@ +# 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 +# +# https://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. + +apiVersion: flows.knative.dev/v1alpha1 +kind: Flow +metadata: + name: github-flow + namespace: default +spec: + serviceAccountName: feed-sa + trigger: + eventType: dev.knative.github.pullrequest + resource: / # TODO: update this + service: github + parameters: + secretName: githubsecret + secretKey: githubCredentials + parametersFrom: + - secretKeyRef: + name: githubsecret + key: githubCredentials + action: + target: + kind: Service + apiVersion: serving.knative.dev/v1alpha1 + name: legit diff --git a/eventing/samples/github-events/function.go b/eventing/samples/github-events/function.go new file mode 100644 index 00000000000..831f48f33ff --- /dev/null +++ b/eventing/samples/github-events/function.go @@ -0,0 +1,106 @@ +/* +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 + + https://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 main + +import ( + "context" + "encoding/json" + "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" + "log" + "net/http" + "os" + "strings" +) + +const ( + // Environment variable containing json credentials + envSecret = "GITHUB_SECRET" + // this is what we tack onto each PR title if not there already + titleSuffix = "looks pretty legit" +) + +// GithubHandler holds necessary objects for communicating with the Github. +type GithubHandler struct { + client *ghclient.Client + ctx context.Context +} + +type GithubSecrets struct { + AccessToken string `json:"accessToken"` + SecretToken string `json:"secretToken"` +} + +func (h *GithubHandler) newPullRequestPayload(ctx context.Context, pl *github.PullRequestPayload) { + + title := pl.PullRequest.Title + log.Printf("GOT PR with Title: %q", title) + + // Check the title and if it contains 'looks pretty legit' leave it alone + if strings.Contains(title, titleSuffix) { + // already modified, leave it alone. + return + } + + newTitle := fmt.Sprintf("%s (%s)", title, titleSuffix) + 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) + if err != nil { + log.Printf("Failed to update PR: %s\n%s", err, response) + return + } + if newPR.Title != nil { + log.Printf("New PR Title: %q", *newPR.Title) + } else { + log.Printf("New PR title is nil") + } +} + +func main() { + flag.Parse() + githubSecrets := os.Getenv(envSecret) + + var credentials GithubSecrets + err := json.Unmarshal([]byte(githubSecrets), &credentials) + if err != nil { + log.Fatalf("Failed to unmarshal credentials: %s", err) + return + } + + // Set up the auth for being able to talk to Github. + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: credentials.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := ghclient.NewClient(tc) + + h := &GithubHandler{ + client: client, + ctx: ctx, + } + + log.Fatal(http.ListenAndServe(":8080", event.Handler(h.newPullRequestPayload))) +} diff --git a/eventing/samples/github-events/function.yaml b/eventing/samples/github-events/function.yaml new file mode 100644 index 00000000000..b553f27083d --- /dev/null +++ b/eventing/samples/github-events/function.yaml @@ -0,0 +1,34 @@ +# 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 +# +# https://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. + +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: legit +spec: + runLatest: + configuration: + revisionTemplate: + metadata: + labels: + knative.dev/type: function + spec: + container: + image: docker.io/{username}/github-events # TODO: fill username out + env: + - name: GITHUB_SECRET + valueFrom: + secretKeyRef: + key: githubCredentials + name: githubsecret diff --git a/eventing/samples/github-events/githubsecret.yaml b/eventing/samples/github-events/githubsecret.yaml new file mode 100644 index 00000000000..0a7f3da5e3b --- /dev/null +++ b/eventing/samples/github-events/githubsecret.yaml @@ -0,0 +1,25 @@ +# 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 +# +# https://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. + +apiVersion: v1 +kind: Secret +metadata: + name: githubsecret +type: Opaque +stringData: + githubCredentials: > + { + "accessToken": "", + "secretToken": "" + } diff --git a/eventing/samples/github-events/personal_access_token.png b/eventing/samples/github-events/personal_access_token.png new file mode 100644 index 00000000000..8f03ecc1d07 Binary files /dev/null and b/eventing/samples/github-events/personal_access_token.png differ