Skip to content

Commit

Permalink
ci: notify with GH issue + project item on e2e failure (#2607)
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Meyer <[email protected]>
Co-authored-by: Paul Meyer <[email protected]>
  • Loading branch information
elchead and katexochen authored Nov 22, 2023
1 parent 284c7e9 commit ed22137
Show file tree
Hide file tree
Showing 3 changed files with 358 additions and 10 deletions.
64 changes: 64 additions & 0 deletions .github/actions/gh_create_issue/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Create a GitHub issue
description: "Create an issue on GitHub, and optionally add it to a project board."

inputs:
title:
description: "The title of the issue."
required: true
owner:
description: "The owner of the repository to create the issue in."
required: false
default: ${{ github.repository_owner }}
repo:
description: "The repository to create the issue in."
required: false
default: ${{ github.repository }}
token:
description: "The GitHub token to use to authenticate."
required: false
default: ${{ github.token }}
body:
description: "The body of the issue."
required: false
body-file:
description: "The absolute path to a file containing the body of the issue."
required: false
assignee:
description: "The GitHub username to assign the issue to."
required: false
label:
description: "A comma-separated list of labels to add to the issue."
required: false
milestone:
description: "The milestone to add the issue to."
required: false
project:
description: "Number of the project to add the issue to."
required: false
template:
description: "The template to use for the issue."
required: false
fields:
description: "A JSON object containing the fields to use for the issue."
required: false

outputs:
issue-url:
description: "The URL of the created issue."
value: ${{ steps.run.outputs.issue-url }}

runs:
using: "composite"
steps:
- name: Run create_issue.sh
id: run
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
run: |
set -x
cat << EOF | tee inputs.json
${{ toJSON(inputs) }}
EOF
out=$(./.github/actions/gh_create_issue/create_issue.sh inputs.json)
echo "issue-url=${out}" | tee -a "$GITHUB_OUTPUT"
248 changes: 248 additions & 0 deletions .github/actions/gh_create_issue/create_issue.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#!/usr/bin/env bash

set -euo pipefail

function debug() {
echo "DEBUG: $*" >&2
}

function warn() {
echo "WARN: $*" >&2
}

function inputs() {
name="${1}"
local val
val=$(jq -r ".\"${name}\"" "${inputFile}")
if [[ ${val} == "null" ]]; then
warn "Input ${name} not found in ${inputFile}"
return
fi
echo "${val}"
}

function flagsFromInput() {
flagNames=("${@}")
for name in "${flagNames[@]}"; do
val=$(inputs "${name}")
if [[ -n ${val} ]]; then
echo "--${name}=${val}"
fi
done
}

function createIssue() {
flags=(
"assignee"
"body"
"body-file"
"label"
"milestone"
"project"
"template"
"title"
)
readarray -t flags <<< "$(flagsFromInput "${flags[@]}")"
flags+=("--repo=$(inputs owner)/$(inputs repo)")
debug gh issue create "${flags[@]}"
gh issue create "${flags[@]}"
}

function listProjects() {
flags=(
"owner"
)
readarray -t flags <<< "$(flagsFromInput "${flags[@]}")"
flags+=("--format=json")
debug gh project list "${flags[@]}"
gh project list "${flags[@]}" >> projects.json
}

function findProjectID() {
project=$(inputs "project")
out="$(
jq -r \
--arg project "${project}" \
'.projects[]
| select(.title == $project)
| .id' \
projects.json
)"
debug "Project ID: ${out}"
echo "${out}"
}

function findProjectNo() {
project=$(inputs "project")
out="$(
jq -r \
--arg project "${project}" \
'.projects[]
| select(.title == $project)
| .number' \
projects.json
)"
debug "Project Number: ${out}"
echo "${out}"
}

function listItems() {
local projectNo="${1}"
flags=(
"owner"
)
readarray -t flags <<< "$(flagsFromInput "${flags[@]}")"
flags+=("--limit=1000")
flags+=("--format=json")
debug gh project item-list "${flags[@]}" "${projectNo}"
gh project item-list "${flags[@]}" "${projectNo}" >> issues.json
}

function findIssueItemID() {
local issueURL="${1}"
out="$(
jq -r \
--arg issueURL "${issueURL}" \
'.items[]
| select(.content.url == $issueURL)
| .id' \
issues.json
)"
debug "Issue Item ID: ${out}"
echo "${out}"
}

function listFields() {
local projectNo="${1}"
flags=(
"owner"
)
readarray -t flags <<< "$(flagsFromInput "${flags[@]}")"
flags+=("--limit=1000")
flags+=("--format=json")
debug gh project field-list "${flags[@]}" "${projectNo}"
gh project field-list "${flags[@]}" "${projectNo}" >> fields.json
}

function findFieldID() {
local fieldName="${1}"
out="$(
jq -r \
--arg fieldName "${fieldName}" \
'.fields[]
| select(.name == $fieldName)
| .id' \
fields.json
)"
debug "Field ID of '${fieldName}': ${out}"
echo "${out}"
}

function findSelectFieldID() {
local fieldName="${1}"
local fieldValue="${2}"
out="$(
jq -r \
--arg fieldName "${fieldName}" \
--arg fieldValue "${fieldValue}" \
'.fields[]
| select(.name == $fieldName)
| .options[]
| select(.name == $fieldValue)
| .id' \
fields.json
)"
debug "Field ID of '${fieldName}': ${out}"
echo "${out}"
}

function findFieldType() {
local fieldName="${1}"
out="$(
jq -r \
--arg fieldName "${fieldName}" \
'.fields[]
| select(.name == $fieldName)
| .type' \
fields.json
)"
debug "Field type of '${fieldName}': ${out}"
echo "${out}"
}

function editItem() {
local projectID="${1}"
local itemID="${2}"
local id="${3}"
local value="${4}"
flags=(
"--project-id=${projectID}"
"--id=${itemID}"
"--field-id=${id}"
"--text=${value}"
)
debug gh project item-edit "${flags[@]}"
gh project item-edit "${flags[@]}" > /dev/null
}

function setFields() {
local projectID="${1}"
local itemID="${2}"

fieldsLen="$(jq -r '.fields' "${inputFile}" | yq 'length')"
debug "Number of fields in input: ${fieldsLen}"
for ((i = 0; i < fieldsLen; i++)); do
name="$(jq -r '.fields' "${inputFile}" |
yq "to_entries | .[${i}].key")"
value="$(jq -r '.fields' "${inputFile}" |
yq "to_entries | .[${i}].value")"
debug "Field ${i}: ${name} = ${value}"
type=$(findFieldType "${name}")

case "${type}" in
"ProjectV2Field")
id=$(findFieldID "${name}")
;;
"ProjectV2SingleSelectField")
id=$(findSelectFieldID "${name}" "${value}")
;;
*)
warn "Unknown field type: ${type}"
return 1
;;
esac

editItem "${projectID}" "${itemID}" "${id}" "${value}"
done
}

function main() {
inputFile="$(realpath "${1}")"

workdir=$(mktemp -d)
pushd "${workdir}" > /dev/null
trap 'debug "not cleaning up, working directory at: ${workdir}"' ERR

issueURL=$(createIssue)
echo "${issueURL}"

project=$(inputs "project")
if [[ -z ${project} ]]; then
return
fi

listProjects
projectNo=$(findProjectNo)
projectID=$(findProjectID)

listItems "${projectNo}"
issueItemID=$(findIssueItemID "${issueURL}")
listFields "${projectNo}"

setFields "${projectID}" "${issueItemID}"

popd > /dev/null
rm -rf "${workdir}"
}

main "${@}"
56 changes: 46 additions & 10 deletions .github/actions/notify_e2e_failure/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,52 @@ runs:
id: pick-assignee
uses: ./.github/actions/pick_assignee

- name: Get the current date
id: date
shell: bash
run: echo "CURRENT_DATE=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV

- name: Create body template
id: body-template
run: |
# TODO(katexochen): add job number when possible
jobURL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
# TODO(msanft): Add Self-managed param once logcollection is fixed.
opensearchURL="https://search-e2e-logs-y46renozy42lcojbvrt3qq7csm.eu-central-1.es.amazonaws.com/_dashboards/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-7d,to:now))&_a=(columns:!(metadata.name,systemd.unit,kubernetes.pod_name,message),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',key:metadata.github.e2e-test-provider,negate:!f,params:(query:${{ inputs.provider }}),type:phrase),query:(match_phrase:(metadata.github.e2e-test-provider:${{ inputs.provider }}))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',key:metadata.github.run-id,negate:!f,params:(query:${{ github.run_id }}),type:phrase),query:(match_phrase:(metadata.github.run-id:${{ github.run_id }}))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',key:metadata.github.ref-stream.keyword,negate:!f,params:(query:'${{ inputs.refStream }}'),type:phrase),query:(match_phrase:(metadata.github.ref-stream.keyword:'${{ inputs.refStream }}'))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',key:metadata.github.kubernetes-version.keyword,negate:!f,params:(query:'${{ inputs.kubernetesVersion }}'),type:phrase),query:(match_phrase:(metadata.github.kubernetes-version.keyword:'${{ inputs.kubernetesVersion }}'))),('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',key:metadata.github.e2e-test-payload,negate:!f,params:(query:'${{ inputs.test }}'),type:phrase),query:(match_phrase:(metadata.github.e2e-test-payload:'${{ inputs.test }}')))),index:'74517cf0-6442-11ed-acf1-47dda8fdfbbb',interval:auto,query:(language:kuery,query:''),sort:!())"
cat << EOF > header.md
## Metadata
* [Job URL](${jobURL})
* [OpenSearch URL](${opensearchURL// /%20})
EOF
cat header.md .github/failure_project_template.md > body.md
echo "BODY_PATH=$(pwd)/body.md" >> $GITHUB_ENV
- uses: ./.github/actions/gh_create_issue
id: gh_create_issue
with:
title: "${{ env.CURRENT_DATE }}"
body-file: ${{ env.BODY_PATH }}
repo: issues
label: "e2e failure"
assignee: ${{ steps.pick-assignee.outputs.assignee }}
project: Constellation bugs
fields: |
Status: New failures
workflow: ${{ github.workflow }}
kubernetesVersion: ${{ inputs.kubernetesVersion }}
cloudProvider: ${{ inputs.provider }}
test: ${{ inputs.test }}
refStream: ${{ inputs.refStream }}
token: ${{ inputs.projectWriteToken }}

- name: Issue URL ${{ steps.gh_create_issue.outputs.issue-url }}
shell: bash
run: echo ${{ steps.gh_create_issue.outputs.issue-url }}

- name: Create project card in case of failure
id: create-project-card
continue-on-error: true
Expand Down Expand Up @@ -112,13 +158,3 @@ runs:
echo "additionalFields=$(cat facts.json)" | tee -a "$GITHUB_OUTPUT"
echo "additionalButtons=$buttons" | tee -a "$GITHUB_OUTPUT"
- name: Notify teams channel
continue-on-error: true
uses: ./.github/actions/notify_teams
with:
teamsWebhookURI: ${{ inputs.teamsWebhookUri }}
title: "Constellation E2E test failed"
assignee: ${{ steps.pick-assignee.outputs.assignee }}
additionalFields: ${{ steps.create-fields.outputs.additionalFields }}
additionalButtons: ${{ steps.create-fields.outputs.additionalButtons }}

0 comments on commit ed22137

Please sign in to comment.