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

ci: notify with GH issue + project item on e2e failure #2607

Merged
merged 5 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
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"
249 changes: 249 additions & 0 deletions .github/actions/gh_create_issue/create_issue.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#!/usr/bin/env bash

set -euo pipefail
set -x
elchead marked this conversation as resolved.
Show resolved Hide resolved

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 }}