Skip to content

Commit

Permalink
Add DELETE option to github-add-comment
Browse files Browse the repository at this point in the history
This will be used as part of tektoncd/plumbing#483 in Tekton's own CI setup, and I think it could be generally useful for replicating behavior such as Prow's job failure comments. Those comments are deleted once the job completes next, with a new comment created for subsequent failures, so that the latest result is always towards the end of the comment list.

Signed-off-by: Andrew Bayer <[email protected]>
  • Loading branch information
abayer committed Aug 22, 2022
1 parent e91c913 commit 71d9c82
Show file tree
Hide file tree
Showing 7 changed files with 642 additions and 0 deletions.
149 changes: 149 additions & 0 deletions task/github-add-comment/0.8/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Add a comment to an issue or a pull request

The `github-add-comment` task let you add a comment to a pull request or an
issue.

## Install the Task

```
kubectl apply -f https://api.hub.tekton.dev/v1/resource/tekton/task/github-add-comment/0.7/raw
```

## Secrets

This Task requires access to a GitHub token set via a Kubernetes Secret. By default, the name of this Secret should be `github` and the secret key should be `token`, but you can configure this via the `GITHUB_TOKEN_SECRET_NAME` and `GITHUB_TOKEN_SECRET_KEY` [parameters](#parameters) described below.

To create such a Secret via `kubectl`:

```
kubectl create secret generic github --from-literal token="MY_TOKEN"
```

Check [this](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to get personal access token for `Github`.

See GitHub's documentation on [Understanding scopes for OAuth Apps](https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/) to figure out what scopes you need to give to this token to add comment to an issue or a pull request.

## Parameters

- **GITHUB_HOST_URL:**: The GitHub host domain (_default:_ `api.github.com`)
- **API_PATH_PREFIX:**: The GitHub Enterprise has a prefix for the API path. _e.g:_ `/api/v3`
- **REQUEST_URL:**: The GitHub pull request or issue url, _e.g:_
`https://github.com/tektoncd/catalog/issues/46`
- **COMMENT_OR_FILE:**: The actual comment to add or the filename inside the
optional workspace `comment-file` containing comment to post. _e.g:_ `don't forget to eat your vegetables before commiting.` _or_ `input.txt`
- **COMMENT_TAG:**: An invisible tag to be added into the comment. The tag is
made invisible by embedding in an an HTML comment. The tag allows
for later retrieval of the comment, and it allows replacing an existing comment. _e.g._ `myservice.[commit-sha]`. (_default:_ `""`).
- **REPLACE:**: When a tag is specified, and `REPLACE` is `true`, look for a
comment with a matching tag and replace it with the new comment. (_default:_ `false`).
- **DELETE:**: When a tag is specified and `DELETE` is true, look for a comment
with a matching tag and delete it, instead of adding a new comment. (_default:_ `false`).
- **GITHUB_TOKEN_SECRET_NAME**: The name of the Kubernetes Secret that
contains the GitHub token. (_default:_ `github`).
- **GITHUB_TOKEN_SECRET_KEY**: The key within the Kubernetes Secret that contains the GitHub token. (_default:_ `token`).

## Results

- **OLD_COMMENT:**: The old text of the comment, if any.
- **NEW_COMMENT:**: The new text of the comment, if any.

## Workspaces

- **comment-file**: The optional workspace containing comment file to be posted.

## Platforms

The Task can be run on `linux/amd64`, `linux/s390x` and `linux/ppc64le` platforms.

## Usage

This TaskRun add a comment to an issue.

```yaml
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
labels:
tekton.dev/task: github-add-comment
name: github-add-comment-to-pr-22
spec:
taskRef:
kind: Task
name: github-add-comment
params:
- name: REQUEST_URL
value: https://github.com/chmouel/scratchpad/pull/46
- name: COMMENT_OR_FILE
value: |
The cat went here and there
And the moon spun round like a top,
And the nearest kin of the moon,
The creeping cat, looked up.
Black Minnaloushe stared at the moon,
For, wander and wail as he would,
The pure cold light in the sky
Troubled his animal blood.
```
### When passing a comment via file
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: comment-cm
data:
input.txt: |
This is the sample input comment via file.
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
labels:
tekton.dev/task: github-add-comment
name: github-add-comment-to-pr-22
spec:
taskRef:
kind: Task
name: github-add-comment
workspace:
- name: comment-file
configMap:
name: comment-cm
params:
- name: REQUEST_URL
value: https://github.com/chmouel/scratchpad/pull/46
- name: COMMENT_OR_FILE
value: "input.txt"
```
### This TaskRun replaces a comment in an issue
```yaml
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: github-add-comment-to-pr-22
spec:
taskRef:
kind: Task
name: github-add-comment
params:
- name: REQUEST_URL
value: https://github.com/chmouel/scratchpad/pull/46
- name: COMMENT_TAG
value: catalog-sha123abc
- name: REPLACE
value: "true"
- name: COMMENT_OR_FILE
value: |
The cat went here and there
And the moon spun round like a top,
And the nearest kin of the moon,
The creeping cat, looked up.
Black Minnaloushe stared at the moon,
For, wander and wail as he would,
The pure cold light in the sky
Troubled his animal blood.
216 changes: 216 additions & 0 deletions task/github-add-comment/0.8/github-add-comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: github-add-comment
labels:
app.kubernetes.io/version: "0.8"
annotations:
tekton.dev/categories: Git
tekton.dev/pipelines.minVersion: "0.17.0"
tekton.dev/tags: github
tekton.dev/displayName: "add github comment"
tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le"
spec:
description: >-
This Task will add a comment to a pull request or an issue.
It can take either a filename or a comment as input and can
post the comment back to GitHub accordingly.
workspaces:
- name: comment-file
optional: true
description: The optional workspace containing comment file to be posted.

results:
- name: OLD_COMMENT
description: The old text of the comment, if any.

- name: NEW_COMMENT
description: The new text of the comment, if any.

params:
- name: GITHUB_HOST_URL
description: |
The GitHub host, adjust this if you run a GitHub enteprise.
default: "api.github.com"
type: string

- name: API_PATH_PREFIX
description: |
The API path prefix, GitHub Enterprise has a prefix e.g. /api/v3
default: ""
type: string

- name: REQUEST_URL
description: |
The GitHub issue or pull request URL where we want to add a new
comment.
type: string

- name: COMMENT_OR_FILE
description: |
The actual comment to add or the filename containing comment to post.
type: string

- name: GITHUB_TOKEN_SECRET_NAME
description: |
The name of the Kubernetes Secret that contains the GitHub token.
type: string
default: github

- name: GITHUB_TOKEN_SECRET_KEY
description: |
The key within the Kubernetes Secret that contains the GitHub token.
type: string
default: token

- name: AUTH_TYPE
description: |
The type of authentication to use. You could use the less secure "Basic" for example
type: string
default: Bearer

- name: COMMENT_TAG
description: |
An invisible tag to be added into the comment. The tag is made
invisible by embedding in an an HTML comment. The tag allows for later
retrieval of the comment, and it allows replacing an existing comment.
type: string
default: ""

- name: REPLACE
description: |
When a tag is specified, and `REPLACE` is `true`, look for a comment
with a matching tag and replace it with the new comment.
type: string
default: "false" # Alternative value: "true"

- name: DELETE
description: |
When a tag is specified, and `DELETE` is `true`, look for a comment
with a matching tag and delete it instead of adding a new comment.
type: string
default: "false" # Alternative value: "true"

steps:
- name: post-comment
workingDir: $(workspaces.comment-file.path)
env:
- name: GITHUBTOKEN
valueFrom:
secretKeyRef:
name: $(params.GITHUB_TOKEN_SECRET_NAME)
key: $(params.GITHUB_TOKEN_SECRET_KEY)

image: registry.access.redhat.com/ubi8/ubi-minimal:8.2
script: |
#!/usr/libexec/platform-python
import json
import os
import http.client
import sys
import urllib.parse
authHeader = "$(params.AUTH_TYPE) " + os.environ["GITHUBTOKEN"]
split_url = urllib.parse.urlparse(
"$(params.REQUEST_URL)").path.split("/")
# This will convert https://github.com/foo/bar/pull/202 to
# api url path /repos/foo/issues/
api_url = "{base}/repos/{package}/issues/{id}".format(
base="$(params.API_PATH_PREFIX)", package="/".join(split_url[1:3]), id=split_url[-1])
commentParamValue = """$(params.COMMENT_OR_FILE)"""
# check if workspace is bound and parameter passed is a filename or not
if "$(workspaces.comment-file.bound)" == "true" and os.path.exists(commentParamValue):
commentParamValue = open(commentParamValue, "r").read()
# If a tag was specified, append it to the comment
if "$(params.COMMENT_TAG)":
commentParamValue += "<!-- {tag} -->".format(tag="$(params.COMMENT_TAG)")
data = {
"body": commentParamValue,
}
# This is for our fake github server
if "$(params.GITHUB_HOST_URL)".startswith("http://"):
conn = http.client.HTTPConnection("$(params.GITHUB_HOST_URL)".replace("http://", ""))
else:
conn = http.client.HTTPSConnection("$(params.GITHUB_HOST_URL)")
# If REPLACE is true, we need to search for comments first
matching_comment = ""
if "$(params.REPLACE)" == "true" or "$(params.DELETE)" == "true":
if not "$(params.COMMENT_TAG)":
print("REPLACE or DELETE requested but no COMMENT_TAG specified")
sys.exit(1)
r = conn.request(
"GET",
api_url + "/comments",
headers={
"User-Agent": "TektonCD, the peaceful cat",
"Authorization": authHeader,
})
resp = conn.getresponse()
if not str(resp.status).startswith("2"):
print("Error: %d" % (resp.status))
print(resp.read())
sys.exit(1)
print(resp.status)
comments = json.loads(resp.read())
print(comments)
# If more than one comment is found take the last one
matching_comment = [x for x in comments if '$(params.COMMENT_TAG)' in x['body']][-1:]
if matching_comment:
with open("$(results.OLD_COMMENT.path)", "w") as result_old:
result_old.write(str(matching_comment[0]))
matching_comment = matching_comment[0]['url']
if matching_comment:
if "$(params.DELETE)" == "true":
method = "DELETE"
else:
method = "PATCH"
target_url = urllib.parse.urlparse(matching_comment).path
else:
method = "POST"
target_url = api_url + "/comments"
# if DELETE is true, don't send any data.
if method == "DELETE":
print("Deleting comment on GitHub at {}".format(target_url))
r = conn.request(
method,
target_url,
headers={
"User-Agent": "TektonCD, the peaceful cat",
"Authorization": authHeader,
})
else:
print("Sending this data to GitHub with {}: ".format(method))
print(data)
r = conn.request(
method,
target_url,
body=json.dumps(data),
headers={
"User-Agent": "TektonCD, the peaceful cat",
"Authorization": authHeader,
})
resp = conn.getresponse()
if not str(resp.status).startswith("2"):
print("Error: %d" % (resp.status))
print(resp.read())
sys.exit(1)
elif method != "DELETE:
with open("$(results.NEW_COMMENT.path)", "wb") as result_new:
result_new.write(resp.read())
print("a GitHub comment has been {} to $(params.REQUEST_URL)".format(
"updated" if matching_comment else "added"))
Loading

0 comments on commit 71d9c82

Please sign in to comment.