처리되지 않은 깃허브 PR을 공휴일이 아닌 월-금 오전 9시에 자동으로 슬랙 보내는 봇 만들기

나고수·2024년 5월 30일
0

1일1공부

목록 보기
68/68
post-thumbnail

원하는것

깃허브에 남은 PR을 공휴일이 아닌 월-금 동안 매일 오전 9시에 자동으로 슬랙으로 알려주는 봇을 만들고 싶었습니다.

방법

  1. 슬랙봇을 만듭니다.
    권한은 이렇게 두개 넣었는데 chat:write만 넣어도 되는 것 같습니다.

  2. 깃허브 토큰을 얻어옵니다.
    저는 토큰 종류는 클래식 버전으로 만들었고 권한은 repo, user만 체크해줬습니다.

  3. 공공데이터포털 토큰을 얻어옵니다.

  4. 아까 만든 슬랙봇을 슬랙에 추가합니다.
    슬랙 하단에 '앱'부분에 '앱 추가'를 눌러서 아까 만든 봇을 추가합니다.

    봇 이름 우클릭 > 세부정보 보기 > 이앱을 채널에 추가를 클릭해 원하는 채널에 봇을 추가합니다.

  5. 슬랙 채널 아이디를 얻습니다. 하단에 앱 채널 아이디가 아니라 봇이 추가되길 원하는 채널의 아이디 입니다.
    채널 우클릭 > 채널 세부정보 보기 > 하단에 채널 ID를 확인합니다.

  6. 컴퓨터 아무폴더에 해당 파이썬 파일을 만듭니다.

#공휴일 체크 해서 공휴일 아닌 경우에만 슬랙봇 실행하는 파일 
from datetime import datetime, timedelta, timezone
import requests
import json
from pandas import json_normalize
from github import Github

class KoreaHolidays:
    def __init__(self):
        pass

    def get_holidays(self):
        today_year = datetime.today().year

        KEY = "3번에서 얻은 공공데이터포털 토큰"
        url = (
            "http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?_type=json&numOfRows=50&solYear="
            + str(today_year)
            + "&ServiceKey="
            + str(KEY)
        )
        response = requests.get(url)
        if response.status_code == 200:
            json_ob = json.loads(response.text)
            holidays_data = json_ob["response"]["body"]["items"]["item"]
            dataframe = json_normalize(holidays_data)
            return dataframe["locdate"].to_list()
        else:
            return []

    def today_is_holiday(self):
        _today = datetime.now().strftime("%Y%m%d")
        holidays = self.get_holidays()
        is_holiday = False
        if int(_today) in holidays:
            is_holiday = True
        return is_holiday

def _get_total_pull_requests(repo):
    count = 0
    pull_requests_list = []

    # 현재 열려있는 PR 목록들을 가져온다.
    for pull in repo.get_pulls(state="opened", sort="updated"):
        pr_comments_count = pull.review_comments
        if pr_comments_count != 0:
            # 리뷰가 진행중인 PR인 경우는 목록에서 제외
            pass
        else:
            count += 1
            pull_requests_list.append((repo.name, pull))
    return count, pull_requests_list

def _send_slack(msg: str):
    response = requests.post(
        "https://slack.com/api/chat.postMessage",
        headers={"Authorization": "Bearer 1번에서 얻은 슬랙봇 토큰"},
        data={"channel": "5번에서 얻은 채널아이디", "text": msg},
    )
    ###    print(response.json())  # Slack API 응답 출력해서 확인해보기

def _make_pr_link_with_no(repo_name: str, pr_no: int) -> str:
    link = f"https://github.com/{repo_name}/pull/{pr_no}"
    return link

def set_pull_requests_tags():
    g = Github("2번에서 얻은 깃헙 토큰")
    repos = [repo for repo in g.get_user().get_repos()]
    
    total_cnt = 0
    total_pulls = []

    for repo in repos:  # 모든 저장소에 대해 실행
        cnt, pulls = _get_total_pull_requests(repo) 
        total_cnt += cnt
        total_pulls.extend(pulls)

    # 모든 저장소의 PR을 집계한 후 메시지를 생성
    pr_msg_to_slack = (
        f"<!here> 👋🏻 총 {total_cnt}개의 Pull Request가 리뷰를 기다리고 있어요! :eyes:\n"
    )
    
    today = datetime.now(timezone.utc)

    for repo_name, pull in total_pulls:
        pr_link = _make_pr_link_with_no(repo_name, pull.number)
        pr_title = pull.title
        pr_created_at = pull.created_at
        days_diff = (today - pr_created_at).days
        if days_diff >= 2:
            pr_msg_to_slack += f"🚨"
        pr_msg_to_slack += f"D+{days_diff} [{repo_name}] {pr_title}: {pr_link}\n"       

    _send_slack(pr_msg_to_slack)  # 집계된 PR 정보를 슬랙에 전송

if __name__ == "__main__":
    koreaHolidays = KoreaHolidays()
    if not koreaHolidays.today_is_holiday():
        set_pull_requests_tags()
    else:
        print("Today is a holiday. The script will not run.")

만약 파일 이름이 pr_bot.py 라고 한다면 터미널에 'python3 pr_bot.py'를 입력해 실행해봅니다.
이때 오늘이 공휴일이 아니라면 슬랙에 pr내용이 잘 담겨서 알림이 잘 가야합니다.

  1. 만약 슬랙을 수동으로 파일을 실행하여 보내고 싶다면 6번에서 멈춥니다.
    하지만 저는 자동화를 원했기 때문에 더 진행해보겠습니다. 이제 깃헙액션을 이용해 자동화를 해보겠습니다.

on:
  workflow_dispatch: //이 코드를 넣으면 깃헙액션 페이지에서 테스트 가능 
  schedule:
    - cron: "0 0 * * * MON-FRI" //크론 돌릴 시간 --금 오전 9시

jobs:
  run_script:
    name: Run Script
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
      
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.12.3'  //파이썬 버전 

    - name: Install dependencies
      run: pip install -r requirements.txt  //필요한 디펜던시 버전을 정의한 파일 
      //이 파일은 루트 디렉토리에 저장
      //파일 얻는 법 > 해당 파이썬 스크립트가 있는 폴더에서 이 명령어 수행 sudo python3 -m pip freeze > requirements.txt
      //파일에는 버전만 정의되어있어야한다.(아래참고)

      
    - name: Run script
      run: python pr_bot.py //루트 디렉토리에 저장 

이 파일을 .github/workflows/manual.yml (파일 이름은 마음대로)에 직접 만들거나
깃헙 페이지 내 Actions 버튼 클릭 > New workflow > Manual workflow를 클릭해 생성합니다.

  1. main브랜치 루트 디렉토리에 실행시키고자 하는 파이썬 스크립트를 푸시합니다.
    여기서는 6번 코드인 pr_bot.py가 됩니다.

  2. main브랜치 루트 디렉토리에 실행시키고자 하는 파이썬 스크립트가 필요로하는 디펜던시와 버전을 정의한 파일을 푸시합니다.
    파일이름은 'requirements.txt'로 합니다.
    이파일을 얻는 방법은 6번 위치로 가서 터미널에 'pip freeze > requirements.txt'를 실행시킵니다. 만약 pip not found 에러가 발생하면 'sudo python3 -m pip freeze > requirements.txt'로 진행합니다.
    해당 명령어를 실행하면 requirements파일이 얻어집니다.
    이 파일에는 아래 코드조각처럼 디펜던시와 버전만 정의되어야 합니다.

certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.7
Deprecated==1.2.14
idna==3.7
numpy==1.26.4
pandas==2.2.2
pycparser==2.22
PyGithub==2.3.0
PyJWT==2.8.0
PyNaCl==1.5.0
python-dateutil==2.9.0.post0
pytz==2024.1
requests==2.32.2
six==1.16.0
typing_extensions==4.12.0
tzdata==2024.1
urllib3==2.2.1
wheel==0.43.0
wrapt==1.16.0
  1. 이제 깃헙액션이 잘 설정되었는지 테스트해봅시다.
    아까 액션을 트리거하는 yml파일에 실행조건을 'workflow_dispatch' 넣어줬기 때문에
    깃헙 페이지에 액션 버튼을 눌러서 사진처럼 테스트 해볼 수 있습니다.

    만약 실패한다면, 어디서 실패했는지 로그가 남으니 지피티와 잘 해결해보세요 👀
    성공한다면 사진과 같이 슬랙에 알림이 잘 와야합니다.

참고로 공휴일체크하지 않고 슬랙에 pr내용 보내는 파이썬 스크립트 버전은 아래와 같습니다.

from github import Github
import requests

# First create a Github instance:
# using an access token
g = Github("깃헙토큰") 

# Then play with your Github objects:
repos = [repo for repo in g.get_user().get_repos()]

#레포지토리 이름 프린트해서 깃헙 잘 연동되었나 확인
#for repo in repos:
#    print(repo.name)

def _get_total_pull_requests(repo):
    count = 0
    pull_requests_list = []

    # 현재 열려있는 PR 목록들을 가져온다.
    for pull in repo.get_pulls(state="open", sort="updated"):
        pr_comments_count = pull.review_comments
        if pr_comments_count != 0:
            # 리뷰가 진행중인 PR인 경우는 목록에서 제외
            pass
        else:
            count += 1
            pull_requests_list.append((repo.name,pull))
    return count, pull_requests_list

def _send_slack(msg: str):
    response = requests.post(
        "https://slack.com/api/chat.postMessage",
        headers={"Authorization": "Bearer 슬랙토큰"},
        data={"channel": "채널아이디", "text": msg},
    )
###    print(response.json())  # Slack API 응답 출력해서 확인해보기

def _make_pr_link_with_no(repo_name:str, pr_no: int) -> str:
    link = f"https://github.com/{repo_name}/pull/{pr_no}"
    return link

def set_pull_requests_tags():
    total_cnt = 0
    total_pulls = []

    for repo in repos:  # 모든 저장소에 대해 실행
        cnt, pulls = _get_total_pull_requests(repo)
        total_cnt += cnt
        total_pulls.extend(pulls)

    # 모든 저장소의 PR을 집계한 후 메시지를 생성
    pr_msg_to_slack = (
        f"<!here> 👋🏻 총 {total_cnt}개의 Pull Request가 리뷰를 기다리고 있어요! :eyes:\n"
    )

    for repo_name, pull in total_pulls:
        pr_link = _make_pr_link_with_no(repo_name, pull.number)
        pr_title = pull.title
        pr_msg_to_slack += f"[{repo_name}] {pr_title}:{pr_link}\n"

    _send_slack(pr_msg_to_slack)  # 집계된 PR 정보를 슬랙에 전송

set_pull_requests_tags()

참고 블로그
https://devocean.sk.com/blog/techBoardDetail.do?ID=165255
https://tasddc.tistory.com/160
https://uipath.tistory.com/200

profile
되고싶다

0개의 댓글