From 650c704c1d3accf972c81f62c23abdc715904d26 Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Mon, 27 Feb 2023 14:35:50 +0000 Subject: [PATCH] breaking(sync_issue_to_jira.yaml): Comment Jira issue URL on GitHub issue (#15) * Comment Jira issue URL on GitHub issue * Use Jira API instead of Jira automation * Add support for multiple Jira components --- .github/workflows/sync_issue_to_jira.md | 27 ++++ .github/workflows/sync_issue_to_jira.yaml | 185 ++++++++++++++++++---- README.md | 2 +- 3 files changed, 181 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/sync_issue_to_jira.md diff --git a/.github/workflows/sync_issue_to_jira.md b/.github/workflows/sync_issue_to_jira.md new file mode 100644 index 00000000..225734f9 --- /dev/null +++ b/.github/workflows/sync_issue_to_jira.md @@ -0,0 +1,27 @@ +Workflow file: [sync_issue_to_jira.yaml](sync_issue_to_jira.yaml) + +## Usage +Add `.yaml` file to `.github/workflows/` +```yaml +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +name: Sync issue to Jira + +on: + issues: + types: [opened, reopened, closed] + +jobs: + sync: + name: Sync GitHub issue to Jira + uses: canonical/data-platform-workflows/.github/workflows/sync_issue_to_jira.yaml@v2 + with: + jira-base-url: https://warthogs.atlassian.net + jira-project-key: DPE + jira-component-names: mysql-k8s,mysql-router-k8s + secrets: + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + jira-user-email: ${{ secrets.JIRA_USER_EMAIL }} + permissions: + issues: write # Needed to create GitHub issue comment +``` \ No newline at end of file diff --git a/.github/workflows/sync_issue_to_jira.yaml b/.github/workflows/sync_issue_to_jira.yaml index 25a88bcd..1cafc034 100644 --- a/.github/workflows/sync_issue_to_jira.yaml +++ b/.github/workflows/sync_issue_to_jira.yaml @@ -1,54 +1,175 @@ -# Copyright 2022 Canonical Ltd. +# Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. -# Context: https://support.atlassian.com/cloud-automation/docs/jira-automation-triggers/#Incoming-webhook -# Source: https://github.com/beliaev-maksim/github-to-jira-automation +# Usage documentation: sync_issue_to_jira.md on: workflow_call: inputs: - jira-component-name: - description: Name of Jira component (e.g. mysql-k8s) + jira-base-url: + description: URL of Jira instance (e.g. "https://warthogs.atlassian.net"). Do not include trailing slash required: true type: string + jira-project-key: + description: Jira project key (e.g. "DPE") + required: true + type: string + jira-component-names: + description: Comma separated list of Jira component names (e.g. "mysql-k8s,mysql-router-k8s") + required: false + type: string secrets: - jira-webhook-url: - description: Jira webhook URL + jira-api-token: + # https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/ + description: Jira API token + required: true + jira-user-email: + description: User email address for Jira API token required: true - jobs: - sync: - name: Sync issue + create-jira-issue: + name: Create Jira issue + if: ${{ github.event.action == 'opened' }} runs-on: ubuntu-latest steps: - - name: Update Jira ticket + - name: Login to Jira API + uses: atlassian/gajira-login@v3 env: - # put into env vars to properly escape special bash chars - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_DESCRIPTION: ${{ github.event.issue.body }} + JIRA_BASE_URL: ${{ inputs.jira-base-url }} + JIRA_USER_EMAIL: ${{ secrets.jira-user-email }} + JIRA_API_TOKEN: ${{ secrets.jira-api-token }} + - name: Determine Jira issue type + id: issue-type run: | - if ${{ contains(github.event.issue.labels.*.name, 'bug') }}; then - ISSUE_TYPE=bug + if ${{ contains(github.event.issue.labels.*.name, 'bug') }} + then + echo "type=Bug" >> $GITHUB_OUTPUT else - ISSUE_TYPE=story + echo "type=Story" >> $GITHUB_OUTPUT fi + - name: Create components JSON + id: components + shell: python + run: | + import json + import os + + components = [ + {"name": component} + for component in "${{ inputs.jira-component-names }}".split(",") + if component + ] + output = f"components={json.dumps(components)}" + print(output) + output_file = os.environ["GITHUB_OUTPUT"] + with open(output_file, "a") as file: + file.write(output) + - name: Create Jira issue + id: create + uses: atlassian/gajira-create@v3 + with: + project: ${{ inputs.jira-project-key }} + issuetype: ${{ steps.issue-type.outputs.type }} + summary: ${{ github.event.issue.title }} + fields: '{"components": ${{ steps.components.outputs.components }}, "assignee": null}' + - name: Add GitHub issue URL to Jira issue + run: | + curl --request POST \ + --url "${{ inputs.jira-base-url }}/rest/api/3/issue/${{ steps.create.outputs.issue }}/remotelink" \ + --user "${{ secrets.jira-user-email }}:${{ secrets.jira-api-token }}" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data '{"object": {"url": "${{ github.event.issue.html_url }}", "title": "Issue #${{ github.event.issue.number }} ยท ${{ github.repository }}"}}' + - name: Comment Jira issue URL on GitHub issue + run: gh issue comment ${{ github.event.issue.number }} --body "${{ inputs.jira-base-url }}/browse/${{ steps.create.outputs.issue }}" --repo ${{ github.repository }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # send Jira request + update-jira-issue: + name: Update Jira issue + if: ${{ github.event.action != 'opened' }} + runs-on: ubuntu-latest + steps: + - name: Get Jira issue key + id: get-jira-issue-key + shell: python + run: | + import json + import os + import subprocess - # Canonical doesn't encourage discussions in Jira on public project issues. - # Thus, it is NOT recommended to put description in Jira in order to push conversation to GitHub - # if strongly required, then replace '--arg body ""' with the next line - # --arg body "$ISSUE_DESCRIPTION" - data=$( jq -n \ - --arg title "$ISSUE_TITLE" \ - --arg url '${{ github.event.issue.html_url }}' \ - --arg submitter '${{ github.event.issue.user.login }}' \ - --arg body "" \ - --arg type "$ISSUE_TYPE" \ - --arg action '${{ github.event.action }}' \ - --arg component '${{ inputs.jira-component-name }}' \ - '{title: $title, url: $url, submitter: $submitter, body: $body, type: $type, action: $action, component: $component}' ) + def run_gh_cli(*args): + """Run command with GitHub CLI""" + output = subprocess.check_output( + [ + "gh", + "api", + "-H", + "Accept: application/vnd.github+json", + "-H", + "X-GitHub-Api-Version: 2022-11-28", + *args, + ] + ) + return json.loads(output) - curl -X POST -H 'Content-type: application/json' --data "${data}" "${{ secrets.jira-webhook-url }}" + + # First five issue comments + github_issue_comments = run_gh_cli( + "${{ github.event.issue.comments_url }}", + "--method", + "GET", + "--raw-field", + "per_page=5", + ) + for comment in github_issue_comments: + user = comment["user"] + if user["login"] == "github-actions[bot]": + assert user["id"] == 41898282 + assert user["type"] == "Bot" + # Example: https://warthogs.atlassian.net/browse/DPE-994 + jira_issue_link = comment["body"] + if not jira_issue_link.startswith("${{ inputs.jira-base-url }}"): + continue + # Example: DPE-994 + jira_issue_key = jira_issue_link.split("/")[-1] + output_file = os.environ["GITHUB_OUTPUT"] + with open(output_file, "a") as file: + file.write(f"jira_issue_key={jira_issue_key}") + break + else: + print("Jira issue not found") + exit(1) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Determine Jira issue transition ID + id: transition-id + run: | + if ${{ github.event.action == 'closed' && github.event.issue.state_reason == 'completed' }} + then + echo "name=Done" >> $GITHUB_OUTPUT + echo "id=61" >> $GITHUB_OUTPUT + elif ${{ github.event.action == 'closed' && github.event.issue.state_reason == 'not_planned' }} + then + echo "name=Rejected" >> $GITHUB_OUTPUT + echo "id=71" >> $GITHUB_OUTPUT + elif ${{ github.event.action == 'reopened'}} + then + echo "name=Untriaged" >> $GITHUB_OUTPUT + echo "id=81" >> $GITHUB_OUTPUT + else + echo "github.event.action=${{ github.event.action }}" + echo "github.event.issue.state_reason=${{ github.event.issue.state_reason }}" + echo "Unknown transition" + exit 1 + fi + - name: Transition issue to ${{ steps.transition-id.outputs.name }} + run: | + curl --request POST \ + --url "${{ inputs.jira-base-url }}/rest/api/3/issue/${{ steps.get-jira-issue-key.outputs.jira_issue_key }}/transitions" \ + --user "${{ secrets.jira-user-email }}:${{ secrets.jira-api-token }}" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data '{"transition": {"id": "${{ steps.transition-id.outputs.id }}"}}' diff --git a/README.md b/README.md index 55805189..37761f88 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## Usage -[Reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows) are located at [.github/workflows](.github/workflows) +[Reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows) (and documentation) are located at [.github/workflows](.github/workflows) Workflows that do **not** begin with an underscore (e.g. `foo.yaml`) may be called outside this repository.