diff --git a/.github/actions/create-jira-ticket/action.yml b/.github/actions/create-jira-ticket/action.yml index f8400fbc1..9dc82bcea 100644 --- a/.github/actions/create-jira-ticket/action.yml +++ b/.github/actions/create-jira-ticket/action.yml @@ -14,295 +14,167 @@ inputs: module_name: required: true description: module name - ticket_labels: + ticket_reason_for_creation: required: true - description: ticket labels, usually Pipeline + Nightly/Veracode + x - default: 'Pipeline' - ticket_squad: - required: true - description: id of the squad to assign the ticket to - default: 'DevSecOps' - + description: reason for ticket creation + default: "Nightly" runs: using: "composite" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 + with: + node-version: 20 + + - name: Install DayJS + run: npm install dayjs + shell: bash + - name: Get ticket elements from context + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + GH_TOKEN: ${{ github.token }} id: get_context - run: | - # Safely set/unset IFS in order to properly parse the table of labels - [ -n "${IFS+set}" ] && saved_IFS=$IFS - IFS=', ' read -a ticket_labels <<< $(echo "${{ inputs.ticket_labels }}" | tr -d "[],'") - unset IFS - [ -n "${saved_IFS+set}" ] && { IFS=$saved_IFS; unset saved_IFS; } + with: + script: | + const fs = require('fs'); + const { execSync } = require('child_process'); + const dayjs = require('dayjs'); + const isoWeek = require('dayjs/plugin/isoWeek'); + dayjs.extend(isoWeek); - # Change the context elements (summary, parent epic, etc.) that are checked depending on these ticket labels - if [[ "${ticket_labels[@]}" =~ "Nightly" ]]; then - parent_epic_id=206242 - parent_epic_key="MON-151547" - ticket_summary="$(date '+%Y-%m-%d') ${{ inputs.module_name }}-${{ github.ref_name }} nightly build failure" + const parent_epic_id = 206242 + const parent_epic_key = "MON-151547" + const JSON_TEMPLATE_FILE = "./.github/actions/create-jira-ticket/nightly-ticket-template.json"; + const date = dayjs().format('YYYY-MM-DD'); + const path = './ticket-payload.json'; + let contents, data, ticket_summary, ticket_squad_id, ticket_board_id, squad_name, project_name, ticket_labels, current_sprint, ticket_data, ticket_key; - JSON_TEMPLATE_FILE="./.github/actions/create-jira-ticket/nightly-ticket-template.json" - sed -i \ - -e 's|@MODULE_NAME@|${{ inputs.module_name }}|g' \ - -e "s|@DATE@|$(date '+%Y-%m-%d')|g" $JSON_TEMPLATE_FILE + let failed_jobs = JSON.parse(execSync( + `gh run view ${{ github.run_id }} --json jobs -q '[.jobs[] | select(.conclusion == "failure") | .name | split(" ")[0] ] | unique'`, + { encoding: 'utf8' } + )); + console.log(failed_jobs); - else - echo "::error::Cannot find a valid labelling option for the ticket." - exit 1 - fi + for (const failed_job of failed_jobs) { + ticket_labels = ["Pipeline", "${{ inputs.ticket_reason_for_creation }}" ] + console.log(`Failed job: ${failed_job}`); + if (ticket_labels.includes("Nightly")) { + ticket_summary = `${date} ${{ inputs.module_name }}-${{ github.base_ref || github.ref_name }} nightly build failure on ${failed_job}` - case "${{ inputs.ticket_squad }}" in - "DevSecOps") - ticket_squad_id=10524 - ticket_board_id=184 - squad_name="DEVSECOPS" - project_name="MON" - ;; - "Connectors") - ticket_squad_id=10504 - ticket_board_id=222 - squad_name="CONNECTORS" - project_name="CTOR" - *) - echo "::error::Cannot find a valid squad for value ${{ inputs.ticket_squad }}." - exit 1 - ;; - esac + data = fs.readFileSync(JSON_TEMPLATE_FILE, { encoding: 'utf8' }); + data = data.replace(/@MODULE_NAME@/g, '${{ inputs.module_name }}'); + data = data.replace(/@DATE@/g, date); - echo "Ticket will be assigned to the $squad_name team." + data = data.replace(/@GITHUB_BRANCH@/g, '${{ github.base_ref || github.ref_name }}'); + data = data.replace(/@GITHUB_SERVER_URL@/g, '${{ github.server_url }}'); + data = data.replace(/@FAILED_JOB@/g, failed_job); + data = data.replace(/@GITHUB_REPOSITORY@/g, '${{ github.repository }}'); + data = data.replace(/@GITHUB_RUN_ID@/g, '${{ github.run_id }}'); + data = data.replace(/@GITHUB_RUN_ATTEMPT@/g, '${{ github.run_attempt }}'); + } else { + throw new Error(`❌ Could not find any labelling option for the ticket.`); + } - current_sprint=$(curl --request GET \ - --url ${{ inputs.jira_base_url }}/rest/agile/1.0/board/$ticket_board_id/sprint?state=active \ - --user "${{ inputs.jira_user_email }}:${{ inputs.jira_api_token }}" \ - --header "Accept: application/json" | jq --arg squad_name "$squad_name" '.values[] | select(.name | test($squad_name; "i")) | .id') + console.log(data); - echo "[DEBUG] current_sprint: $current_sprint" + if (["get-environment", "get-plugins", "package", "deliver-packages"].includes(failed_job)) { + ticket_squad_id = 10524 + ticket_board_id = 184 + squad_name = "DEVSECOPS" + project_name = "MON" + } else if (["unit-tests", "fatpacker", "test-plugins"].includes(failed_job)) { + ticket_squad_id = 10504 + ticket_board_id = 222 + squad_name = "COLLECT" + project_name = "CTOR" + } else { + core.setFailed(`Not finding any squad association with job ${failed_job}.`); + continue; + } - # General updates on all template files - sed -i \ - -e 's|@GITHUB_BRANCH@|${{ github.base_ref || github.ref_name }}|g' \ - -e 's|@GITHUB_SERVER_URL@|${{ github.server_url }}|g' \ - -e 's|@GITHUB_REPOSITORY@|${{ github.repository }}|g' \ - -e 's|@GITHUB_RUN_ID@|${{ github.run_id }}|g' \ - -e 's|@GITHUB_RUN_ATTEMPT@|${{ github.run_attempt }}|g' $JSON_TEMPLATE_FILE + console.log(`Ticket summary: ${ticket_summary}`); + console.log(`Ticket will be attributed to the ${squad_name} team. (Squad ID ${ticket_squad_id}, Board ID ${ticket_board_id})`) - echo "parent_epic_id=$parent_epic_id" >> $GITHUB_OUTPUT - echo "parent_epic_key=$parent_epic_key" >> $GITHUB_OUTPUT - echo "ticket_summary=$ticket_summary" >> $GITHUB_OUTPUT - echo "ticket_board_id=$ticket_board_id" >> $GITHUB_OUTPUT - echo "ticket_squad_id=$ticket_squad_id" >> $GITHUB_OUTPUT - echo "project_name=$project_name" >> $GITHUB_OUTPUT - echo "current_sprint=$current_sprint" >> $GITHUB_OUTPUT - echo "json_template_file=$JSON_TEMPLATE_FILE" >> $GITHUB_OUTPUT + current_sprint = execSync( + `curl --request GET \ + --url ${{ inputs.jira_base_url }}/rest/agile/1.0/board/${ticket_board_id}/sprint?state=active \ + --user "${{ inputs.jira_user_email }}:${{ inputs.jira_api_token }}" \ + --header "Accept: application/json" | jq --arg squad_name "$squad_name" '.values[] | select(.name | test($squad_name; "i")) | .id' + `, + { encoding: 'utf8' } + ); - cat $JSON_TEMPLATE_FILE - cat $GITHUB_OUTPUT - shell: bash - env: - GH_TOKEN: ${{ github.token }} + console.log(`Current sprint: ${current_sprint}`); - - name: Check if the ticket already exists - id: check_ticket - run: | - # Checking if an incident ticket already exists - response=$(curl \ - --write-out "%{http_code}" \ - --request POST \ - --url "${{ inputs.jira_base_url }}/rest/api/3/search" \ - --user "${{ inputs.jira_user_email }}:${{ inputs.jira_api_token }}" \ - --header "Accept:application/json" \ - --header "Content-Type:application/json" \ - --data '{ - "fields": ["summary"], - "jql": "project = ${{ steps.get_context.outputs.project_name }} AND parentEpic = ${{ steps.get_context.outputs.parent_epic_key }} AND issueType = Technical AND summary ~ \"${{ steps.get_context.outputs.ticket_summary }}\" AND component = \"${{ inputs.module_name }}\" AND resolution = unresolved ORDER BY key ASC", - "maxResults": 1 - }' - ) - echo "[DEBUG] $response" - - if [[ $(echo "$response" | tr -d '\n' | tail -c 3) -ne 200 ]]; then - echo "::error:: Jira API request was not completed properly." - fi - - ticket_key=$(echo "$response" | head -c -4 | jq .issues[0].key | xargs) - if [[ "$ticket_key" != "null" ]]; then - echo "abort_ticket_creation=true" >> $GITHUB_ENV - echo "ticket_key=$ticket_key" >> $GITHUB_ENV - echo "::notice::ticket found as $ticket_key aborting ticket creation" - fi - shell: bash - - - name: Update existing nightly Jira ticket - if: | - env.abort_ticket_creation == 'true' && - contains(steps.get_context.outputs.parent_epic_key, 'MON-151547') - run: | - # Adding failed job labels for already existing ticket - [ -n "${IFS+set}" ] && saved_IFS=$IFS - IFS=', ' read -a ticket_labels <<< $(echo "${{ inputs.ticket_labels }}" | tr -d "[],'") - unset IFS - [ -n "${saved_IFS+set}" ] && { IFS=$saved_IFS; unset saved_IFS; } - - for label in ${ticket_labels[@]}; do - response=$(curl \ - --request PUT \ - --url "${{ inputs.jira_base_url }}/rest/api/3/issue/${{ env.ticket_key }}" \ - --user "${{ inputs.jira_user_email }}:${{ inputs.jira_api_token }}" \ - --header 'Accept: application/json' \ - --header 'Content-Type: application/json' \ - --data "{ \"update\": { \"labels\": [ { \"add\": \"$label\" } ] } }" - ) - done - - ticket_description=$(curl --request GET \ - --url "${{ inputs.jira_base_url }}/rest/api/3/issue/${{ env.ticket_key }}" \ - --user "${{ inputs.jira_user_email }}:${{ inputs.jira_api_token }}" \ - --header "Accept: application/json" | jq '.fields.description') - - mapfile -t jobs_failed < <(gh run view ${{ github.run_id }} --json jobs -q '.jobs[] | select(.conclusion == "failure") | .name') - echo "[DEBUG] - jobs failed for component ${FAILED_COMPONENTS[index]}: $jobs_failed" - - new_list_of_failed_jobs=$(for job in "${jobs_failed[@]}"; do - cat <