[Feature] 지라 연동 중복 해결 최종 확인 #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync GitHub Issue To Jira | |
| on: | |
| issues: | |
| types: [labeled] | |
| permissions: | |
| issues: write | |
| jobs: | |
| create-jira-issue: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Decide whether to create a Jira issue | |
| id: prepare | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const validTypes = { | |
| bug: "Bug", | |
| feature: "Story", | |
| maintenance: "Task" | |
| }; | |
| const issue = context.payload.issue; | |
| const labels = issue.labels.map((label) => label.name.toLowerCase()); | |
| const issueTypeCandidates = [ | |
| issue.issue_type?.name, | |
| issue.issueType?.name, | |
| issue.type?.name, | |
| issue.type_name, | |
| ] | |
| .filter(Boolean) | |
| .map((name) => String(name).toLowerCase()); | |
| let matchedLabel = null; | |
| if (context.payload.action === "labeled") { | |
| const currentLabel = context.payload.label.name.toLowerCase(); | |
| if (validTypes[currentLabel]) { | |
| matchedLabel = currentLabel; | |
| } | |
| } | |
| if (!matchedLabel) { | |
| matchedLabel = Object.keys(validTypes).find((label) => labels.includes(label)) ?? null; | |
| } | |
| if (!matchedLabel) { | |
| matchedLabel = | |
| Object.keys(validTypes).find((label) => issueTypeCandidates.includes(label)) ?? null; | |
| } | |
| if (!matchedLabel) { | |
| core.info(`No matching label or issue type found. labels=${labels.join(",")} issueTypes=${issueTypeCandidates.join(",")}`); | |
| core.setOutput("should_create", "false"); | |
| return; | |
| } | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| per_page: 100, | |
| }); | |
| const existingJiraComment = comments.find((comment) => | |
| comment.body.includes("Jira issue:") | |
| ); | |
| if (existingJiraComment) { | |
| core.info("Jira issue already linked. Skipping."); | |
| core.setOutput("should_create", "false"); | |
| return; | |
| } | |
| core.setOutput("should_create", "true"); | |
| core.setOutput("jira_issue_type", validTypes[matchedLabel]); | |
| - name: Resolve Jira project | |
| id: resolve_project | |
| if: steps.prepare.outputs.should_create == 'true' | |
| env: | |
| JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| JIRA_PROJECT_KEY: ${{ secrets.JIRA_PROJECT_KEY }} | |
| run: | | |
| if [ -z "$JIRA_BASE_URL" ]; then echo "Error: JIRA_BASE_URL is empty"; exit 1; fi | |
| if [ -z "$JIRA_PROJECT_KEY" ]; then echo "Error: JIRA_PROJECT_KEY is empty"; exit 1; fi | |
| response=$(mktemp) | |
| http_code=$(curl -sS -o "$response" -w "%{http_code}" \ | |
| --request GET \ | |
| --url "$JIRA_BASE_URL/rest/api/2/project/$JIRA_PROJECT_KEY" \ | |
| --user "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| --header "Accept: application/json") | |
| if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then | |
| echo "Failed to resolve Jira project '$JIRA_PROJECT_KEY' with status $http_code" | |
| cat "$response" | |
| exit 1 | |
| fi | |
| jira_project_id=$(jq -r '.id' "$response") | |
| jira_project_key=$(jq -r '.key' "$response") | |
| if [ -z "$jira_project_id" ] || [ "$jira_project_id" = "null" ]; then | |
| echo "Jira project id not found while resolving project '$JIRA_PROJECT_KEY'" | |
| cat "$response" | |
| exit 1 | |
| fi | |
| echo "Resolved Jira project key: $jira_project_key" | |
| echo "jira_project_id=$jira_project_id" >> "$GITHUB_OUTPUT" | |
| - name: Create Jira issue | |
| id: create_jira | |
| if: steps.prepare.outputs.should_create == 'true' | |
| env: | |
| JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| JIRA_PROJECT_ID: ${{ steps.resolve_project.outputs.jira_project_id }} | |
| JIRA_ISSUE_TYPE: ${{ steps.prepare.outputs.jira_issue_type }} | |
| GITHUB_REPOSITORY_NAME: ${{ github.repository }} | |
| GITHUB_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| GITHUB_ISSUE_TITLE: ${{ github.event.issue.title }} | |
| GITHUB_ISSUE_BODY: ${{ github.event.issue.body }} | |
| GITHUB_ISSUE_URL: ${{ github.event.issue.html_url }} | |
| GITHUB_ISSUE_AUTHOR: ${{ github.event.issue.user.login }} | |
| run: | | |
| normalized_issue_body=$(printf '%s\n' "$GITHUB_ISSUE_BODY" | perl -ne ' | |
| BEGIN { | |
| $in_code_block = 0; | |
| } | |
| if (/^```([[:alnum:]_+-]+)?\s*$/) { | |
| if (!$in_code_block) { | |
| $language = defined $1 ? $1 : ""; | |
| if ($language ne "") { | |
| print "{code:$language}\n"; | |
| } else { | |
| print "{code}\n"; | |
| } | |
| $in_code_block = 1; | |
| } else { | |
| print "{code}\n"; | |
| $in_code_block = 0; | |
| } | |
| next; | |
| } | |
| if (!$in_code_block) { | |
| s/^##### /h4. /; | |
| s/^#### /h3. /; | |
| s/^### /h2. /; | |
| s/^## /h1. /; | |
| s/^# /h1. /; | |
| } | |
| print; | |
| ') | |
| description=$(printf '%s\n' \ | |
| "|| 항목 || 내용 ||" \ | |
| "| 저장소 | $GITHUB_REPOSITORY_NAME |" \ | |
| "| 이슈 번호 | #$GITHUB_ISSUE_NUMBER |" \ | |
| "| 작성자 | $GITHUB_ISSUE_AUTHOR |" \ | |
| "" \ | |
| "$normalized_issue_body") | |
| payload=$(jq -n \ | |
| --arg projectId "$JIRA_PROJECT_ID" \ | |
| --arg issueType "$JIRA_ISSUE_TYPE" \ | |
| --arg summary "$GITHUB_ISSUE_TITLE" \ | |
| --arg description "$description" \ | |
| '{ | |
| fields: { | |
| project: { id: $projectId }, | |
| issuetype: { name: $issueType }, | |
| summary: $summary, | |
| description: $description, | |
| labels: ["Server"] | |
| } | |
| }') | |
| response=$(mktemp) | |
| http_code=$(curl -sS -o "$response" -w "%{http_code}" \ | |
| --request POST \ | |
| --url "$JIRA_BASE_URL/rest/api/2/issue" \ | |
| --user "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| --header "Accept: application/json" \ | |
| --header "Content-Type: application/json" \ | |
| --data "$payload") | |
| if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then | |
| echo "Jira issue creation failed with status $http_code" | |
| cat "$response" | |
| exit 1 | |
| fi | |
| jira_key=$(jq -r '.key' "$response") | |
| if [ -z "$jira_key" ] || [ "$jira_key" = "null" ]; then | |
| echo "Jira issue key not found in response" | |
| cat "$response" | |
| exit 1 | |
| fi | |
| echo "jira_key=$jira_key" >> "$GITHUB_OUTPUT" | |
| echo "jira_url=$JIRA_BASE_URL/browse/$jira_key" >> "$GITHUB_OUTPUT" | |
| - name: Add GitHub issue web link to Jira | |
| if: steps.create_jira.outputs.jira_key != '' | |
| env: | |
| JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| JIRA_KEY: ${{ steps.create_jira.outputs.jira_key }} | |
| GITHUB_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| GITHUB_ISSUE_URL: ${{ github.event.issue.html_url }} | |
| run: | | |
| payload=$(jq -n \ | |
| --arg url "$GITHUB_ISSUE_URL" \ | |
| --arg title "GitHub Issue #$GITHUB_ISSUE_NUMBER" \ | |
| '{ | |
| object: { | |
| url: $url, | |
| title: $title | |
| } | |
| }') | |
| response=$(mktemp) | |
| http_code=$(curl -sS -o "$response" -w "%{http_code}" \ | |
| --request POST \ | |
| --url "$JIRA_BASE_URL/rest/api/2/issue/$JIRA_KEY/remotelink" \ | |
| --user "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| --header "Accept: application/json" \ | |
| --header "Content-Type: application/json" \ | |
| --data "$payload") | |
| if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then | |
| echo "Failed to add Jira remote link with status $http_code" | |
| cat "$response" | |
| exit 1 | |
| fi | |
| - name: Comment Jira link on GitHub issue | |
| if: steps.create_jira.outputs.jira_key != '' | |
| uses: actions/github-script@v7 | |
| env: | |
| JIRA_KEY: ${{ steps.create_jira.outputs.jira_key }} | |
| JIRA_URL: ${{ steps.create_jira.outputs.jira_url }} | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `Jira issue: [${process.env.JIRA_KEY}](${process.env.JIRA_URL})`, | |
| }); |