diff --git a/cmd/mario/kodata/LICENSE b/cmd/mario/kodata/LICENSE new file mode 120000 index 0000000000..5853aaea53 --- /dev/null +++ b/cmd/mario/kodata/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/cmd/mario/kodata/OWNERS b/cmd/mario/kodata/OWNERS new file mode 120000 index 0000000000..0cca7e5248 --- /dev/null +++ b/cmd/mario/kodata/OWNERS @@ -0,0 +1 @@ +../../../OWNERS \ No newline at end of file diff --git a/cmd/mario/main.go b/cmd/mario/main.go new file mode 100644 index 0000000000..b315a68aae --- /dev/null +++ b/cmd/mario/main.go @@ -0,0 +1,112 @@ +/* + Copyright 2019 The Tekton 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 main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "strconv" + "strings" + + "github.com/google/go-github/github" + "github.com/google/uuid" +) + +const ( + // Environment variable containing GitHub secret token + envSecret = "GITHUB_SECRET_TOKEN" +) + +type triggerPayload struct { + BuildUUID string `json:"buildUUID,omitempty"` + GitRepository string `json:"gitRepository,omitempty"` + GitRevision string `json:"gitRevision,omitempty"` + ContextPath string `json:"contextPath,omitempty"` + TargetImage string `json:"targetImage,omitempty"` + PullRequestID string `json:"pullRequestID,omitempty"` +} + +func main() { + errorMessage := "" + secretToken := os.Getenv(envSecret) + if secretToken == "" { + log.Fatalf("No secret token given") + } + + http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { + //TODO: We should probably send over the EL eventID as a X-Tekton-Event-Id header as well + payload, err := github.ValidatePayload(request, []byte(secretToken)) + id := github.DeliveryID(request) + if err != nil { + log.Printf("Error handling Github Event with delivery ID %s : %q", id, err) + http.Error(writer, fmt.Sprint(err), http.StatusBadRequest) + } + event, err := github.ParseWebHook(github.WebHookType(request), payload) + if err != nil { + log.Printf("Error handling Github Event with delivery ID %s : %q", id, err) + http.Error(writer, fmt.Sprint(err), http.StatusBadRequest) + } + switch event := event.(type) { + case *github.IssueCommentEvent: + if event.GetAction() == "created" { + eventBody := event.GetComment().GetBody() + if strings.HasPrefix(eventBody, "/mario") { + log.Printf("Handling Mario command with delivery ID: %s; Comment: %s", id, eventBody) + commandParts := strings.Fields(eventBody) + command := commandParts[1] + if command == "build" { + // No validation here. Anything beyond commandParts[3] is ignored + prID := strconv.Itoa(int(event.GetIssue().GetNumber())) + triggerBody := triggerPayload{ + BuildUUID: uuid.New().String(), + GitRepository: "github.com/" + event.GetRepo().GetFullName(), + GitRevision: "pull/" + prID + "/head", + ContextPath: commandParts[2], + TargetImage: "us.icr.io/knative/" + commandParts[3], + PullRequestID: prID, + } + tPayload, err := json.Marshal(triggerBody) + if err != nil { + log.Printf("Failed to marshal the trigger body. Error: %q", err) + } + n, err := writer.Write(tPayload) + if err != nil { + log.Printf("Failed to write response for Github event ID: %s. Bytes writted: %d. Error: %q", id, n, err) + } + } else { + errorMessage = "Unknown Mario command" + } + } else { + errorMessage = "Not a Mario command" + } + } else { + errorMessage = "Only new comments are supported" + } + default: + errorMessage = "Event type not supported" + } + if errorMessage != "" { + log.Printf(errorMessage) + http.Error(writer, fmt.Sprint(errorMessage), http.StatusBadRequest) + } + }) + + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil)) +} diff --git a/config/100-namespace.yaml b/config/100-namespace.yaml new file mode 100644 index 0000000000..ddfa798328 --- /dev/null +++ b/config/100-namespace.yaml @@ -0,0 +1,18 @@ +# Copyright 2019 The Tekton 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: Namespace +metadata: + name: mario diff --git a/config/200-serviceaccount.yaml b/config/200-serviceaccount.yaml new file mode 100644 index 0000000000..d0a939e1cf --- /dev/null +++ b/config/200-serviceaccount.yaml @@ -0,0 +1,19 @@ +# Copyright 2019 The Tekton 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: mario-bot + namespace: mario diff --git a/config/mario.yaml b/config/mario.yaml new file mode 100644 index 0000000000..51dff1c4aa --- /dev/null +++ b/config/mario.yaml @@ -0,0 +1,53 @@ +# Copyright 2019 The Tekton 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: apps/v1 +kind: Deployment +metadata: + name: mario + namespace: mario +spec: + replicas: 1 + selector: + matchLabels: + app: mario + template: + metadata: + labels: + app: mario + spec: + serviceAccountName: mario-bot + containers: + - name: mario-interceptor + image: github.com/tektoncd/plumbing/cmd/mario + env: + - name: GITHUB_SECRET_TOKEN + valueFrom: + secretKeyRef: + name: mario-github-secret + key: secret-token +--- +apiVersion: v1 +kind: Service +metadata: + name: mario + namespace: mario +spec: + type: ClusterIP + selector: + app: mario + ports: + - protocol: TCP + port: 80 + targetPort: 8080 diff --git a/pipelinerun-logs/config/deployment.yaml b/pipelinerun-logs/config/deployment.yaml index 61c9bab25d..56b7e84899 100644 --- a/pipelinerun-logs/config/deployment.yaml +++ b/pipelinerun-logs/config/deployment.yaml @@ -27,6 +27,8 @@ spec: - "dogfooding" - "--namespace" - "default" + - "--namespace" + - "mario" - "--hostname" - "0.0.0.0" - "--port" @@ -35,4 +37,4 @@ spec: - containerPort: 9999 nodeSelector: # Schedule this deployment onto nodes with workload identity enabled - iam.gke.io/gke-metadata-server-enabled: true + iam.gke.io/gke-metadata-server-enabled: "true" diff --git a/tekton/resources/mario-github-comment.yaml b/tekton/resources/mario-github-comment.yaml new file mode 100644 index 0000000000..35bcbd79f1 --- /dev/null +++ b/tekton/resources/mario-github-comment.yaml @@ -0,0 +1,158 @@ +apiVersion: tekton.dev/v1alpha1 +kind: TriggerBinding +metadata: + name: trigger-to-comment-github +spec: + params: + - name: pullRequestID + value: $(body.taskRun.metadata.labels.mario\.bot/pull-request-id) + - name: buildUUID + value: $(body.taskRun.metadata.labels.prow\.k8s\.io/build-id) + - name: gitURL + value: $(body.taskRun.spec.inputs.resources.#(name=="source").resourceSpec.params.#(name=="url").value) + - name: gitRevision + value: $(body.taskRun.spec.inputs.resources.#(name=="source").resourceSpec.params.#(name=="revision").value) + - name: targetImageResourceName + value: $(body.taskRun.spec.outputs.resources.#(name=="image").resourceRef.name) + - name: passedOrFailed + value: $(body.taskRun.status.conditions.#(type=="Succeeded").status) +--- +apiVersion: tekton.dev/v1alpha1 +kind: EventListener +metadata: + name: github-feedback-trigger +spec: + serviceAccountName: mario-listener + triggers: + - name: trigger + binding: + name: trigger-to-comment-github + template: + name: mario-comment-github +--- +apiVersion: tekton.dev/v1alpha1 +kind: TriggerTemplate +metadata: + name: mario-comment-github +spec: + params: + - name: pullRequestID + description: The pullRequestID to comment to + - name: buildUUID + description: The buildUUID for the logs link + - name: gitURL + description: The URL of the git repo + - name: gitRevision + description: The git revision + - name: targetImageResourceName + description: The name of the target image pipelineresource + - name: passedOrFailed + description: Whether the triggering event was successful or not + resourcetemplates: + - apiVersion: tekton.dev/v1alpha1 + kind: PipelineResource + metadata: + name: pr-$(uid) + spec: + type: pullRequest + params: + - name: url + value: $(params.gitURL)/pull/$(params.pullRequestID) + secrets: + - fieldName: githubToken + secretName: mario-github-token + secretKey: GITHUB_TOKEN + - apiVersion: tekton.dev/v1alpha1 + kind: TaskRun + metadata: + generateName: mario-comment-github-$(uid)- + spec: + serviceAccountName: mario-listener + taskSpec: + inputs: + resources: + - name: source + type: git + - name: pr + type: pullRequest + outputs: + resources: + - name: image + type: image + - name: pr + type: pullRequest + steps: + - name: copy-pr-to-output + image: busybox + script: | + #!/bin/sh + mkdir -p $(outputs.resources.pr.path) + cp -r $(inputs.resources.pr.path)/* $(outputs.resources.pr.path)/ + - name: setup-comment + image: python:3-alpine + script: | + #!/usr/bin/env python + import json + import random + + marios_pics_root = 'https://storage.googleapis.com/mario-bot/pics' + ok_pics = ['mario', 'luigi', 'tekton'] + failed_pics = ['goomba'] + logs_url = 'http://35.222.249.224/?buildid=%s&namespace=mario' + successful = ($(params.passedOrFailed) == "True") + + # Service Image + comment_template = ( + '' + ' at your service!
' + ) + + if successful: + chosen_pic = random.choice(ok_pics) + else: + chosen_pic = random.choice(failed_pics) + pic_url = "/".join([marios_pics_root, chosen_pic]) + '.png' + comment_params = dict(pic_alt=chosen_pic, pic_src=pic_url) + + if successful: + comment_template += ( + 'Here is the image you requested: ' + 'built image|' + ) + comment_params['imageurl'] = '$(outputs.resources.image.url)' + else: + comment_template += ( + 'Cloud not build the requested image. Please check the ' + ) + + comment_template += ( + 'build logs' + ) + comment_params['buildid'] = '$(params.buildUUID)' + + new_comment_path = "$(outputs.resources.pr.path)/comments/new.json" + comment_body = dict(body=comment_template.format(**comment_params)) + with open(new_comment_path, "w") as comment: + json.dump(comment_body, comment) + inputs: + resources: + - name: source + resourceSpec: + type: git + params: + - name: revision + value: $(params.gitRevision) + - name: url + value: $(params.gitURL) + - name: pr + resourceRef: + name: pr-$(uid) + outputs: + resources: + - name: image + resourceRef: + name: $(params.targetImageResourceName) + - name: pr + resourceRef: + name: pr-$(uid) diff --git a/tekton/resources/mario-image-build-trigger.yaml b/tekton/resources/mario-image-build-trigger.yaml new file mode 100644 index 0000000000..60124b9e55 --- /dev/null +++ b/tekton/resources/mario-image-build-trigger.yaml @@ -0,0 +1,187 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: triggers-minimal +rules: +- apiGroups: ["tekton.dev"] + resources: ["eventlisteners", "triggerbindings", "triggertemplates", "tasks", "taskruns"] + verbs: ["get"] +- apiGroups: ["tekton.dev"] + resources: ["pipelineruns", "pipelineresources", "taskruns"] + verbs: ["create"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: mario-listener +secrets: +- name: mario-github-secret +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: mario-releaser +secrets: +- name: release-secret +- name: mario-github-secret +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: mario-releaser-triggers-minimal +subjects: +- kind: ServiceAccount + name: mario-releaser +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: triggers-minimal +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: mario-listener-triggers-minimal +subjects: +- kind: ServiceAccount + name: mario-listener +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: triggers-minimal +--- +apiVersion: tekton.dev/v1alpha1 +kind: TriggerBinding +metadata: + name: trigger-to-build-and-push-image +spec: + params: + - name: buildUUID + value: $(body.buildUUID) + - name: gitRepository + value: $(body.gitRepository) + - name: gitRevision + value: $(body.gitRevision) + - name: contextPath + value: $(body.contextPath) + - name: targetImage + value: $(body.targetImage) + - name: pullRequestID + value: $(body.pullRequestID) +--- +apiVersion: tekton.dev/v1alpha1 +kind: EventListener +metadata: + name: mario-image-builder +spec: + serviceAccountName: mario-listener + serviceType: LoadBalancer + triggers: + - name: trigger + interceptor: + objectRef: + kind: Service + name: mario + apiVersion: v1 + namespace: mario + binding: + name: trigger-to-build-and-push-image + template: + name: build-and-push-image +--- +apiVersion: tekton.dev/v1alpha1 +kind: PipelineResource +metadata: + name: github-feedback-trigger +spec: + type: cloudEvent + params: + - name: targetURI + value: http://el-github-feedback-trigger.mario:8080 +--- +apiVersion: tekton.dev/v1alpha1 +kind: TriggerTemplate +metadata: + name: build-and-push-image +spec: + params: + - name: gitRepository + description: The git repository that hosts context and Dockerfile + - name: gitRevision + description: The Git revision to be used. + - name: contextPath + description: The path to the context within 'gitRepository' + - name: targetImage + description: The fully qualifie image target e.g. repo/name:tag. + resourcetemplates: + - apiVersion: tekton.dev/v1alpha1 + kind: PipelineResource + metadata: + name: target-image-$(uid) + spec: + type: image + params: + - name: url + value: $(params.targetImage) + - apiVersion: tekton.dev/v1alpha1 + kind: TaskRun + metadata: + generateName: build-and-push-$(uid)- + labels: + prow.k8s.io/build-id: $(params.buildUUID) + mario.bot/pull-request-id: $(params.pullRequestID) + spec: + serviceAccountName: mario-releaser + taskSpec: + inputs: + params: + - name: contextPath + description: The path to the context + resources: + - name: source + type: git + outputs: + resources: + - name: image + type: image + - name: endtrigger + type: cloudEvent + steps: + - name: build-and-push + workingdir: $(inputs.resources.source.path) + image: gcr.io/kaniko-project/executor:v0.13.0 + env: + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /secret/release.json + command: + - /kaniko/executor + - --dockerfile=Dockerfile + - --context=$(inputs.params.contextPath) + - --destination=$(params.targetImage) + volumeMounts: + - name: gcp-secret + mountPath: /secret + volumes: + - name: gcp-secret + secret: + secretName: release-secret + inputs: + params: + - name: contextPath + value: $(params.contextPath) + resources: + - name: source + resourceSpec: + type: git + params: + - name: revision + value: $(params.gitRevision) + - name: url + value: https://$(params.gitRepository) + outputs: + resources: + - name: image + resourceRef: + name: target-image-$(uid) + - name: endtrigger + resourceRef: + name: github-feedback-trigger