새로운 팀 프로젝트를 들어가며 Jira로 프로젝트를 관리해보자는 의견을 내었다.
감사하게도 그 의견이 받아들어져 깃헙과 지라를 연동하는 작업을 맡게 되었는데 은근히 번거로운 과정들이 많았다.
공수를 들이기에는 번거롭고 귀찮은 과정들이 많았기에 연동 작업 삽질기를 정리해보고자 한다.
간단하게 깃헙 이슈 템플릿을 통해 이슈를 만들고, 그 이슈를 jira와 연동하여 이슈 티켓을 생성하는 방식으로 하기로 하였다.
(해당 이슈 티켓에서 브랜치 생성 및 풀 리퀘스트 등이 가능하다.)
사전 작업
1. Secrets 변수 설정
프로젝트 Settings에서 secrets 변수들을 설정해준다.
JIRA_BASE_URL은 프로젝트의 .net까지의 부분을 입력해주면 되고, JIRA_API_TOKEN의 경우에는 따로 발급받아야 한다.
2. Workflow Permissions
프로젝트의 Settings->Actions->General에 있는 워크플로우 허용을 Read and Write Permission으로 수정해준다.
액션 워크플로우 작성
Actions로 들어가 새로운 워크플로우를 만들어 수행할 내용을 입력한다. (확장자는 yml로 한다)
https://github.com/atlassian/gajira-login
GitHub - atlassian/gajira-login: Jira Login GitHub Action
Jira Login GitHub Action. Contribute to atlassian/gajira-login development by creating an account on GitHub.
github.com
아틀라시안에서 제공하는 액션 리포지토리이다. (GitHub Actions에서 Jira Cloud와의 연동을 위해 사용되는 액션들)
현재 Deprecated 되어있기는 하지만 Jira Cloud 한정 아직 잘 작동하고 있기에 적용해보였다. 사실 Deprecated된 것들이 몇몇 있어서 워크플로우 코드의 일부는 Jira API를 통해 작성하였다. GitHub Actions에서 Jira Cloud와의 연동을 위해 사용되는 액션들이다.
1. 레이블 분기 처리
가장 처음 수행될 Job으로는 "determine-label"으로 설정하였다.
팀에서 이슈 레이블을 별도 지정하여 관리하기로 했는데 해당 레이블명에 따른 이슈 템플릿을 지정하는 부분이다. 기존에 등록해둔 이슈 템플릿은 ISSUE_TEMPLATE 디렉토리에서 확인할 수 있다.
name: Sync issues to jira
on:
issues:
types: // Actions가 트리거되는 시점 (github 이슈가 생성되었을 때, 이슈 내용이 수정되었을 때)
- opened
- edited
jobs:
determine-label:
runs-on: ubuntu-latest
outputs:
issue_type: ${{ steps.set-label.outputs.issue_type }}
template_path: ${{ steps.set-label.outputs.template_path }}
steps:
- name: Determine issue type
id: set-label
run: |
RAW_LABELS=$(echo '${{ toJson(github.event.issue.labels) }}' | jq -r '.[].name')
TEMPLATE_PATH=""
if echo "$RAW_LABELS" | grep -q "새로운 기능"; then
echo "issue_type=feature" >> "$GITHUB_ENV"
echo "issue_type=feature" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/feature_issues_template.yml"
elif echo "$RAW_LABELS" | grep -q "버그"; then
echo "issue_type=bug" >> "$GITHUB_ENV"
echo "issue_type=bug" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/bug_issues_template.yml"
elif echo "$RAW_LABELS" | grep -q "요청"; then
echo "issue_type=request" >> "$GITHUB_ENV"
echo "issue_type=request" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/request_issues_template.yml"
else
echo "issue_type=none" >> "$GITHUB_ENV"
echo "issue_type=none" >> "$GITHUB_OUTPUT"
fi
shell: bash
2. 이슈 연동
create-issue 라는 이름의 job을 등록한다. (다시보니 저 이름이 조금 애매하다.. 큰 의미는 없기에 그냥 냅뒀다.) permissions는 아래와 같이 설정해준다.
create-issue:
name: Create Jira issue
runs-on: ubuntu-latest
needs: determine-label
permissions:
contents: write # 저장소 콘텐츠 접근 권한
issues: write # 이슈 생성 및 수정 권한
pull-requests: write # PR 생성 및 수정 권한
create-issue 의 steps를 지정해주는데 먼저 Jira에 로그인을 하도록 한다. 처음 등록한 secrets 변수들이 적용되는 부분이다.
steps:
- name: Login
uses: atlassian/gajira-login@v3
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
3. 중복 이슈 확인
Jira에 등록되어 있는 이슈와 중복되있는 지를 확인한다. 중복이라면 해당 이슈 내용을 수정하고, 그렇지 않으면 새로운 이슈를 생성하도록 한다.
- name: Get existing Jira issue
id: get-jira-issue
run: |
# GitHub Issue 제목 가져오기
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
# JQL 쿼리를 동적으로 설정
JQL_QUERY="project=BIN AND summary~\"$(echo $ISSUE_TITLE | sed 's/[][():]/ /g')\""
echo "JQL Query: $JQL_QUERY"
# Jira API 호출하여 기존 이슈 검색
RESPONSE=$(curl -s -u "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
-X GET -H "Content-Type: application/json" \
"${{ secrets.JIRA_BASE_URL }}/rest/api/2/search?jql=$(echo $JQL_QUERY | jq -sRr @uri)")
# Jira 이슈 키 추출
JIRA_ISSUE_KEY=$(echo "$RESPONSE" | jq -r '.issues[0].key // empty')
if [[ -n "$JIRA_ISSUE_KEY" ]]; then
echo "기존 Jira Issue 발견: $JIRA_ISSUE_KEY"
else
echo "기존 Jira Issue 없음, 새로 생성 예정"
fi
# 환경 변수 설정
echo "JIRA_ISSUE_KEY=$JIRA_ISSUE_KEY" >> $GITHUB_ENV
shell: bash
4. 이슈 갱신
중복 확인을 통해 기존의 이슈를 갱신하는 부분이다.
Github 이슈의 레이블을 추출하여 레이블의 수정도 지라와 연동되도록 한다.
맨 위의 gajira-login 액션 문서에서 이슈 갱신하는 부분이 없기에 "curl -s --request PUT ... "을 통해 해당 이슈의 내용을 갱신하였다.
- name: Extract Labels
id: extract-labels
run: |
RAW_LABELS=$(cat <<EOF
${{ toJson(github.event.issue.labels) }}
EOF
)
CLEAN_LABELS=$(echo "$RAW_LABELS" | jq -c '[.[].name | gsub(" "; "-")]')
echo "JIRA_LABELS=$CLEAN_LABELS" >> $GITHUB_ENV
echo "Extracted Labels: $CLEAN_LABELS"
shell: bash
- name: Debug Labels
run: echo "JIRA_LABELS=${{ env.JIRA_LABELS }}"
- name: Create or Update Jira Issue
run: |
echo "🔍 Raw JIRA_LABELS: '${{ env.JIRA_LABELS }}'"
# JIRA_LABELS 값이 이미 JSON 배열이면 그대로 사용
if echo '${{ env.JIRA_LABELS }}' | jq -e . >/dev/null 2>&1; then
JIRA_LABELS_JSON='${{ env.JIRA_LABELS }}'
else
# JSON이 아니면 강제로 JSON 배열로 변환
JIRA_LABELS_JSON=$(echo '${{ env.JIRA_LABELS }}' | sed 's/^\[\(.*\)\]$/\1/' | jq -R 'split(",") | map(gsub("^ *"; "") | gsub(" *$"; "") | tostring)')
fi
# JSON 유효성 검사
if ! echo "$JIRA_LABELS_JSON" | jq empty >/dev/null 2>&1; then
echo "Invalid JSON detected in JIRA_LABELS_JSON! Using default empty array."
JIRA_LABELS_JSON='[]'
fi
echo "Converted JIRA_LABELS_JSON: $JIRA_LABELS_JSON"
if [ -n "$JIRA_ISSUE_KEY" ]; then
echo "기존 Jira Issue($JIRA_ISSUE_KEY) 업데이트 중..."
# JSON 요청 바디 생성 (jq로 안전하게 처리)
JSON_PAYLOAD=$(jq -n \
--arg summary "${{ github.event.issue.title }}" \
--argjson labels "$JIRA_LABELS_JSON" \
'{
fields: {
summary: $summary,
labels: $labels,
}
}')
echo "Final JSON Payload: $JSON_PAYLOAD"
RESPONSE=$(curl -s --request PUT \
--url "${{ secrets.JIRA_BASE_URL }}/rest/api/3/issue/$JIRA_ISSUE_KEY" \
--user "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "$JSON_PAYLOAD")
echo "Jira API Response: $RESPONSE"
fi
shell: bash
5. 이슈 생성
중복된 이슈가 없을 경우, 해당 이슈가 실행된다.
이를 통해 Jira에 새로운 이슈 티켓이 생성되게 된다.
- name: Create Issue
if: env.JIRA_ISSUE_KEY == ''
id: create
uses: atlassian/gajira-create@v3
with:
project: {JIRA 프로젝트 키}
issuetype: Task
summary: "${{ github.event.issue.title }}"
description: "${{ steps.md2jira.outputs.output-text }}"
fields: |
{
"labels": ${{ env.JIRA_LABELS }}
}
- name: Log created issue
run: echo "Jira Issue ${{ steps.issue-parser.outputs.parentKey }}/${{ steps.create.outputs.issue }} was created"
전체 코드
name: Sync issues to jira
on:
issues:
types:
- opened
- edited
jobs:
determine-label:
runs-on: ubuntu-latest
outputs:
issue_type: ${{ steps.set-label.outputs.issue_type }}
template_path: ${{ steps.set-label.outputs.template_path }}
steps:
- name: Determine issue type
id: set-label
run: |
RAW_LABELS=$(echo '${{ toJson(github.event.issue.labels) }}' | jq -r '.[].name')
TEMPLATE_PATH=""
if echo "$RAW_LABELS" | grep -q "새로운 기능"; then
echo "issue_type=feature" >> "$GITHUB_ENV"
echo "issue_type=feature" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/feature_issues_template.yml"
elif echo "$RAW_LABELS" | grep -q "버그"; then
echo "issue_type=bug" >> "$GITHUB_ENV"
echo "issue_type=bug" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/bug_issues_template.yml"
elif echo "$RAW_LABELS" | grep -q "요청"; then
echo "issue_type=request" >> "$GITHUB_ENV"
eecho "issue_type=request" >> "$GITHUB_OUTPUT"
TEMPLATE_PATH=".github/ISSUE_TEMPLATE/request_issues_template.yml"
else
echo "issue_type=none" >> "$GITHUB_ENV"
echo "issue_type=none" >> "$GITHUB_OUTPUT"
fi
shell: bash
create-issue:
name: Create Jira issue
runs-on: ubuntu-latest
needs: determine-label
# if: needs.determine-label.outputs.issue_type != 'none'
permissions:
contents: write # 저장소 콘텐츠 접근 권한
issues: write # 이슈 생성 및 수정 권한
pull-requests: write # PR 생성 및 수정 권한
steps:
- name: Login
uses: atlassian/gajira-login@v3
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
- name: Checkout main code
uses: actions/checkout@v4
with:
ref: master
- name: Issue Parser
if: env.SKIP != 'true'
uses: stefanbuck/github-issue-praser@v3
id: issue-parser
with:
template-path: ${{ needs.determine-label.outputs.template_path }}
- name: Log Issue Parser
run: |
echo '${{ steps.issue-parser.outputs.jsonString }}'
- name: Convert markdown to Jira Syntax
uses: peter-evans/jira2md@v1
id: md2jira
with:
input-text: |
### Github Issue Link
- ${{ github.event.issue.html_url }}
${{ github.event.issue.body }}
mode: md2jira
- name: Extract Labels
id: extract-labels
run: |
RAW_LABELS=$(cat <<EOF
${{ toJson(github.event.issue.labels) }}
EOF
)
CLEAN_LABELS=$(echo "$RAW_LABELS" | jq -c '[.[].name | gsub(" "; "-")]')
echo "JIRA_LABELS=$CLEAN_LABELS" >> $GITHUB_ENV
echo "Extracted Labels: $CLEAN_LABELS"
shell: bash
- name: Debug Labels
run: echo "JIRA_LABELS=${{ env.JIRA_LABELS }}"
- name: Get existing Jira issue
id: get-jira-issue
run: |
# GitHub Issue 제목 가져오기
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
# JQL 쿼리를 동적으로 설정
JQL_QUERY="project=BIN AND summary~\"$(echo $ISSUE_TITLE | sed 's/[][():]/ /g')\""
echo "JQL Query: $JQL_QUERY"
# Jira API 호출하여 기존 이슈 검색
RESPONSE=$(curl -s -u "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
-X GET -H "Content-Type: application/json" \
"${{ secrets.JIRA_BASE_URL }}/rest/api/2/search?jql=$(echo $JQL_QUERY | jq -sRr @uri)")
# Jira 이슈 키 추출
JIRA_ISSUE_KEY=$(echo "$RESPONSE" | jq -r '.issues[0].key // empty')
if [[ -n "$JIRA_ISSUE_KEY" ]]; then
echo "기존 Jira Issue 발견: $JIRA_ISSUE_KEY"
else
echo "기존 Jira Issue 없음, 새로 생성 예정"
fi
# 환경 변수 설정
echo "JIRA_ISSUE_KEY=$JIRA_ISSUE_KEY" >> $GITHUB_ENV
shell: bash
- name: Create or Update Jira Issue
run: |
echo "🔍 Raw JIRA_LABELS: '${{ env.JIRA_LABELS }}'"
# JIRA_LABELS 값이 이미 JSON 배열이면 그대로 사용
if echo '${{ env.JIRA_LABELS }}' | jq -e . >/dev/null 2>&1; then
JIRA_LABELS_JSON='${{ env.JIRA_LABELS }}'
else
# JSON이 아니면 강제로 JSON 배열로 변환
JIRA_LABELS_JSON=$(echo '${{ env.JIRA_LABELS }}' | sed 's/^\[\(.*\)\]$/\1/' | jq -R 'split(",") | map(gsub("^ *"; "") | gsub(" *$"; "") | tostring)')
fi
# JSON 유효성 검사
if ! echo "$JIRA_LABELS_JSON" | jq empty >/dev/null 2>&1; then
echo "Invalid JSON detected in JIRA_LABELS_JSON! Using default empty array."
JIRA_LABELS_JSON='[]'
fi
echo "Converted JIRA_LABELS_JSON: $JIRA_LABELS_JSON"
if [ -n "$JIRA_ISSUE_KEY" ]; then
echo "기존 Jira Issue($JIRA_ISSUE_KEY) 업데이트 중..."
# JSON 요청 바디 생성 (jq로 안전하게 처리)
JSON_PAYLOAD=$(jq -n \
--arg summary "${{ github.event.issue.title }}" \
--argjson labels "$JIRA_LABELS_JSON" \
'{
fields: {
summary: $summary,
labels: $labels,
}
}')
echo "Final JSON Payload: $JSON_PAYLOAD"
RESPONSE=$(curl -s --request PUT \
--url "${{ secrets.JIRA_BASE_URL }}/rest/api/3/issue/$JIRA_ISSUE_KEY" \
--user "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "$JSON_PAYLOAD")
echo "Jira API Response: $RESPONSE"
fi
shell: bash
- name: Create Issue
if: env.JIRA_ISSUE_KEY == ''
id: create
uses: atlassian/gajira-create@v3
with:
project: BIN
issuetype: Task
summary: "${{ github.event.issue.title }}"
description: "${{ steps.md2jira.outputs.output-text }}"
fields: |
{
"labels": ${{ env.JIRA_LABELS }}
}
- name: Log created issue
run: echo "Jira Issue ${{ steps.issue-parser.outputs.parentKey }}/${{ steps.create.outputs.issue }} was created"
- name: Checkout develop code
uses: actions/checkout@v4
with:
ref: develop
... 단순히 이슈를 연동하여 생성하는게 아니라 중복 이슈 검사도 해야하고, 레이블 등 바뀐 부분 등을 지라 이슈 티켓에도 반영해야 했기 때문에 코드가 조금 난해해졌다..
다른 실험용 레포지토리 또는 쉘로 테스트를 했었는데 꽤나 번거로웠다. 이와 관련해서 테스트용 툴이 존재한다는걸 뒤늦게 알았는데 다음 번에 쓰기로..
'ETC' 카테고리의 다른 글
VIM 플러그인/자동완성 설정 (0) | 2024.03.25 |
---|