Programming/DevOps

Github PR 자동 슬랙 알림 기능 구축기 (w AWS Lambda, Gateway)

R.i.c.K.y 2023. 12. 13. 03:18

첫 Slack-Ops 구축해 보기!

새로운 사이드 프로젝트를 시작하면서, 슬랙 채널에 깃허브 레포지토리 알림 연동작업을 진행하려던 차.
문득 이전 프로젝트들을 돌이켜보니, 슬랙에 노션 알림과 깃허브 알림이 남발하는 게 보기 싫었던 기억이 난다.

나와 관련없는 작업들도 모두 봐야한다는 번거러움


모든 알림 들을 다 받게 되면 슬랙은 마치 양치기 소년과 같아지는 것 같다.
한두 번은 알림 왔을 때 보게 되지만, 어느 순간부터는 _'아 또 노션이겠지~'_하고선 안 보게 되기 때문이다.

깃허브 알림 또한 마찬가지다. 슬랙 앱 내에 있는 Github Application을 설치해서 알림을 받게 되면 특정 채널에 모든 알림 들을 받게 되므로, 같은 채널에 속해있지만 나랑 관련 없는 알림까지 봐야 한다는 단점이 있다.

그래서 이번엔 특정 사용자에게 멘션이 된 알림만 뜨도록 Slack 자동화를 구현해 보았다.
이렇게 하면 평상시에는 해당 채널 알림을 비활성화하고, 나에게 멘션 되었을 때 필요한 알림만 받아볼 수 있게 된다.

구글링 하던 중, 해당 기능에 대한 좋은 글이 있어 참고하며 진행하였다.
참고 링크

파이프라인 구축 과정

전반적인 아키텍처는 다음과 같다.

  1. Assignees 지정과 함께 Pull Request 요청 시, Github Webhook이 요청 이벤트를 캐치한다.
  2. Github Webhook이 AWS API gateway로 해당 PR이 담긴 JSON Request를 보낸다.
  3. 요청을 받은 API gateway는 해당 메시지를 고대로 AWS Lambda함수에게 POST 요청을 보낸다.
  4. 건너 건너 결국엔 Lambda 함수에 PR 정보가 들어오게 되고, Lambda함수는 Slack Webhook API에 PR 알림 등록을 위한 POST 요청을 보낸다.
  5. PR 알림 요청을 받은 Slack Webhook이 해당 채널에 사용자들을 멘션함으로써 선택한 사용자만 알림을 볼 수 있게 된다.

순서대로 구현하기엔 API gateway, Lambda를 먼저 구축해야 하므로, 역순으로 구현해야 한다.

1. Slack Webhook 설정

Webhook이란?

A webhook in web development is a method of augmenting or altering the behavior of a web page or web application with custom callbacks.
-Wikipedia-

 

hook(갈고리)라는 단어에서 알 수 있듯이, Webhook은 지정해 놨던 이벤트가 발생했을 때 자동으로 일련의 행동들을 하게끔 Trigger해주는 기능이라고 비유할 수 있다.
정확히 말하자면, 웹페이지나 웹앱의 지정된 이벤트들을 커스텀 된 콜백으로 전환해 주는 기능을 Webhook이라고 한다.
기존의 관습대로 하면, 클라이언트가 서버에게 요청을 보내야만 서버의 응답을 받아낼 수 있었다.(Pooling)
하지만 Webhook은 서버에서 이벤트 발생 시 클라이언트에게 알려주고, 콜백 url을 통해 클라이언트가 서버에 요청을 보낼 수 있게 해주는 똑똑한 친구다!
그래서 Webhook은 _역방향 API_라고도 불린다고 한다.

서버 : 이벤트 발생!

  1. 서버 -> 클라이언트 : "[자동 알림] 나 이벤트 발생함!"
  2. 클라이언트 -> 서버 : "네가 보낸 알림 받고 요청한다! 응답값 있는 거 다 아니까 내.놔"
  3. 서버 -> 클라이언트 : 응답. 클라이언트는 원하는 결과 받음.

Incoming Webhook 설치

Slack App에서 설치 가능하다.


먼저, Slack에서 지원해주는 웹훅 프로그램인 'Incoming Webhook'을 설치해야한다.
알림받고 싶은 채널의 채널 상세정보 보기 -> 통합 -> 에서 해당 프로그램을 검색 후 설치한다.


정상적으로 설치되었으면 위와 같은 페이지로 리다이렉트된다.
알림을 받을 서버와 이름, 아이콘을 자유롭게 커스텀한 후 저장!
나는 토토로 아이콘으로 봇을 꾸며보았다.

이렇게 뜨면 성공

아 벌써 귀엽다.

다음으로 넘어가기 전에, POSTMAN을 이용하여 Webhook URL에 샘플 페이로드를 POST 했을 때 알림이 정상적으로 뜨는 지를 테스트해보고 넘어가자.

2. AWS Lambda로 POST 요청 함수 만들기

Slack Webhook을 이용하여 일련의 이벤트가 발생했을 때 Slack으로 알림 POST 요청을 보낼 수 있다는 건 확인했다. 근데..Webhook은 어떻게 이벤트가 발생한걸 알지?
이에 대해 알려주는 기능을 AWS Lambda로 구현한 함수에서 해준다.

AWS Lambda 함수 생성

  • 함수 이름 : 원하는 대로 작명하자.
  • 런타임 : 자신이 코드 작성에 사용할 언어를 기반으로 하는 런타임을 선택해주자.
    나는 Python이 젤 편하기에 Python 3.10 환경을 선택했다.

AWS Lambda는 Layers 추가를 통해 외부 라이브러리를 추가할 수도 있지만, 혹시 모를 과금 문제와 웬지 모를 복잡함때문에 최대한 지양하고, 기본 라이브러리로 작성하였다.
처음에는 lambda_function.py라는 Default 파일과 기본 코드가 적혀있을 것이다.
lambda_function.py 내에서 WebHook으로의 POST 요청 메서드를 작성해보자.

import json
import urllib.request

SLACK_SERVER_CH_URI = 'https://hooks.slack.com/services/[생략]'
GITHUB_SLACK_MAP = {
    "SeungHo0422": "[SLACK memberID]",
    "kaley0421": "[SLACK memberID]",
    ...
    "Github ID": "Slack memberID"
}

Slack Mentioning의 핵심 과정

Request에는 해당 PR에 대한 정보들이 담겨 있다. 그 중에는 PR에 할당된 Asignees member들의 GithubID도 들어있는데, 코드의 GITHUB_SLACK_MAP처럼 Github ID와 Slack ID를 Key:Value 쌍으로 매핑함으로써 슬랙 채널의 Asignee들을 멘션할 수 있게한다.
물론 좋은 코드라고 할 순 없다. (사용자가 추가/수정/삭제되면 하드코딩 해야 하므로..) 시간이 된다면 이 부분도 리팩토링 하고 싶다.

def lambda_handler(event, context):
    pr_contents = event["pull_request"]
    slack_message = ''
    if not pr_contents['assignees']:
        slack_message = f'''
            [PR] Assignee: <@{GITHUB_SLACK_MAP[pr_contents['assignee']['login']]}>
            '''
    else:
        slack_message += '[PR] Assignees: <@'
        for i in range(len(pr_contents['assignees'])):
            slack_message += GITHUB_SLACK_MAP[pr_contents['assignees'][i]['login']] + '>'
            if i == len(pr_contents['assignees'])-1:
                break
            else:
                slack_message += ', <@'


    payload = {
        "channel": "dev-server", #웹훅이나 슬랙에서 지정한 채널이 무엇이든, 여기 적힌 채널로 알림간다.
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": slack_message
                },
            }],
        "attachments": [
            {
                "fallback": "[LINKIT] New Pull Request",
                "color": "#00E000",
                "author_name": '작성자: '+pr_contents['user']['login'],
                "title": pr_contents['title'],
                "title_link": pr_contents['url'],
                "text": f'```{pr_contents["body"]}```',
                "short": False,
            }
        ]
    }

    headers = {
        "Content-Type": "application/json"
    }

    try:
        data = json.dumps(payload).encode('utf-8')
        request = urllib.request.Request(SLACK_SERVER_CH_URI, data=data, headers=headers, method='POST')
        with urllib.request.urlopen(request) as response:
            response_text = response.read().decode('utf-8')
            print('Message sent to Slack successfully:', response_text)
            return {
                'statusCode': response.status,
                'body': 'Message sent to Slack successfully!\nevent: ' + str(event)
            }
    except Exception as e:
        print('Error sending message to Slack:', e)
        return {
            'statusCode': 500,
            'body': 'Error sending message to Slack'
        }

코드를 보면 알 수 있듯이, Github PR을 등록했을 때의 정보들이 AWS Lambda로 전송된다면 Slack Webhook으로 보내주는 역할을 하고 있다.
하지만 문제점이 있다. 람다가 동작할 수 있는 '트리거'가 현재까지는 없다. 따라서 AWS Gateway를 통해 Lambda로 request할 수 있도록 만들어보자.

3. AWS API Gateway로 Github와 Lambda 연결해주기


AWS Lambda 함수 개요에는 사진과 같이 트리거를 추가할 수 있다. 해당 버튼을 눌러 API Gateway를 추가해주자.
트리거 구성은 어렵지 않다. REST API 타입으로 지정한 후, Security는 Open으로 지정했다. (우리 프로젝트는 보안적으로 굳이 IAM이나 다른 것들을 등록할 필요를 못느꼈다.) API name까지 지정해준 후 완료!


완료 후 해당 API Gateway 화면으로 넘어가보면, Default로 ANY 요청이 만들어져있는걸 확인할 수 있다.
우리는 Lambda에 POST요청을 보내야 하므로, 새로운 라우터를 만들어보자.

POST 요청 만들기

작업 -> 메서드 생성을 누른 후, 드롭박스에서 POST를 선택해준다.


POST 리소스 설정을 해줘야 하는데, 우리는 Lambda에 보내줄거니까 Lambda 함수를 지정해주고, 보내줘야하는 Lambda 함수의 리전과 ARN 주소를 잘 작성해준다. ARN 주소 정보는 Lambda 함수 정보에 있다.

이로써 API Gateway를 통해 POST 요청을 보낼 준비까지 완료되었다. 배포를 통해 Request URL을 만들어보자.


이렇게 작업 -> API 배포 버튼을 누른 뒤,


새로운 스테이지를 생성한 후, 해당 스테이지에 배포해보자. 나는 Version1 스테이지에 배포하였다.
그 후, 스테이지 탭으로 접속하고 나서 임의로 만든 스테이지를 클릭하면 호출 URL 정보를 조회할 수 있다. 후에 Github Webhook 등록 과정에서 쓰일 주소다.

거의 끝났다. 이제 만들어놓은 API를 마지막으로 Github PR 등록 시 Github Webhook을 통해 해당 Payload를 API Gateway로 보내주는 트리거를 만들어보자!

4. Github Webhook 설정

연결하고자 하는 Repository 페이지에 들어간 후, Settings -> Webhooks를 통해 Webhooks 설정 페이지로 접속한다. Add webhook을 통해 새로운 웹훅을 만들어보자.

  • Payload URL : API Gateway에 보내기 위한 url이므로, API Gateway를 통해 배포한 url 주소를 넣어줘야 한다. 만약 에러가 난다면 이곳 주소를 제대로 적지 않았을 가능성이 매우 높다.
  • Content type : 우리는 JSON 형식으로 보낼 것이니 application/json으로 지정
  • Secret : 공백으로 처리한다. (혹시 여러분들의 프로젝트가 보안성을 가져야 한다면 토큰 처리를 해줘야한다.)
  • Which events would you like to trigger this webwook?
    : Webhook을 통해 어떤 이벤트를 전송하고 싶은 지 선택하는 곳이다. Let me select individual events를 클릭 한 후, 하위 항목 중 Pull requests만을 클릭한다.
  • 마지막으로 Active, 즉 트리거 활성화 여부을 체크한 후 Update Webhook을 누르면 완성!

회고

서버리스 서비스들을 직접 이용해보며 불편한 점을 뚫어내 본 경험은 처음이였는데, 이렇게도 쉽게 연결이 될 수 있구나..!라는 것을 느끼게 된 시간이였다. 다음에는 Cloudwatch 같은 로그 모니터링 서비스들을 통해 트리거 파이프라인을 정교하게 구현해보고 싶다.

혹여 위 순서대로 따라했는데 안되시는 분들은 댓글로 문의해주세요!