Skip to content
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

Setup a mario bot to build images on demand #1

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions cmd/mario/kodata/LICENSE
1 change: 1 addition & 0 deletions cmd/mario/kodata/OWNERS
112 changes: 112 additions & 0 deletions cmd/mario/main.go
Original file line number Diff line number Diff line change
@@ -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))
}
18 changes: 18 additions & 0 deletions config/100-namespace.yaml
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions config/200-serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions config/mario.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion pipelinerun-logs/config/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ spec:
- "dogfooding"
- "--namespace"
- "default"
- "--namespace"
- "mario"
- "--hostname"
- "0.0.0.0"
- "--port"
Expand All @@ -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"
159 changes: 159 additions & 0 deletions tekton/resources/mario-github-comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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")
print("PassedOrFailed: {}".format("$(params.passedOrFailed)"))

# Service Image
comment_template = (
'<img width="200" alt="{pic_alt}" src="{pic_src}">'
' at your service! </p>'
)

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: '
'<a href="https://{imageurl}">built image</a>|'
)
comment_params['imageurl'] = '$(outputs.resources.image.url)'
else:
comment_template += (
'Cloud not build the requested image. Please check the '
)

comment_template += (
'<a href="http://35.222.249.224/?buildid={buildid}&'
'namespace=mario">build logs</a>'
)
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)
Loading