probot으로 github comment bot 만들기

cozzin·2023년 1월 31일
0
post-thumbnail

지난번에 github action으로 명령어 인식하는 방법을 만들었었다. 하지만 여러 레포에 적용하려다보니 코드가 분산되고 관리비용이 더 증가하는 것을 느꼈다. 이번에는 github app을 만들어보겠다. 나도 처음 해보는거라 최대한 자세히 작성하겠다. probot은 Node.js 기반으로 Github App을 만들 수 있도록 도와주는 프레임워크 라고 한다. JS가 손에 익진 않지만 많은 사람들이 이걸 쓰니까 한번 사용해보겠다. 기본적으로 아웃사이더님의 블로그 글을 따른다.

로컬 설치

로컬에서 먼저 설치하고 개발한다. 한번에 잘 되나 싶었는데 역시나 에러 발생.

 ernest.hong@Ernest  ~/Dev  npx create-probot-app my-first-app
Need to install the following packages:
  create-probot-app@5.0.10
Ok to proceed? (y) y
npm WARN deprecated cross-spawn-async@2.2.5: cross-spawn no longer requires a build toolchain, use it instead
/Users/ernest.hong/.npm/_npx/ac7da0f8d6e6b5c7/node_modules/npm/index.js:4
  throw new Error('The programmatic API was removed in npm v8.0.0')
  ^

Error: The programmatic API was removed in npm v8.0.0
    at Object.<anonymous> (/Users/ernest.hong/.npm/_npx/ac7da0f8d6e6b5c7/node_modules/npm/index.js:4:9)
    at Module._compile (node:internal/modules/cjs/loader:1239:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1293:10)
    at Module.load (node:internal/modules/cjs/loader:1096:32)
    at Module._load (node:internal/modules/cjs/loader:935:12)
    at Module.require (node:internal/modules/cjs/loader:1120:19)
    at require (node:internal/modules/helpers:112:18)
    at Object.<anonymous> (/Users/ernest.hong/.npm/_npx/ac7da0f8d6e6b5c7/node_modules/create-probot-app/bin/helpers/run-npm.js:7:31)
    at Module._compile (node:internal/modules/cjs/loader:1239:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1293:10)
    at Module.load (node:internal/modules/cjs/loader:1096:32)
    at Module._load (node:internal/modules/cjs/loader:935:12)
    at Module.require (node:internal/modules/cjs/loader:1120:19)
    at require (node:internal/modules/helpers:112:18)
    at Object.<anonymous> (/Users/ernest.hong/.npm/_npx/ac7da0f8d6e6b5c7/node_modules/create-probot-app/bin/create-probot-app.js:15:19)
    at Module._compile (node:internal/modules/cjs/loader:1239:14)

Node.js v19.4.0
npm notice
npm notice New minor version of npm available! 9.2.0 -> 9.4.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.4.0
npm notice Run npm install -g npm@9.4.0 to update!
npm notice

아직 문제가 해소되지 않은 것으로 보인다. https://github.com/probot/create-probot-app/issues/808 대신 이전 버전으로 고정해서 실행하는 방안이 있다.

npx create-probot-app@v5.0.9

기본으로 제공되는 템플릿이 몇 가지 있다. Type Script도 써보고 싶었으니까 basic-ts를 선택했다.

? Which template would you like to use?
  basic-js => Comment on new issues
❯ basic-ts => Comment on new issues - written in TypeScript
  checks-js => Handle check_suite request and rerequested actions
  deploy-js => Creates a deployment on a pull request event
  git-data-js => Opens a PR every time someone installs your app for the first time

역시나 한번에 안된다

 ✘ ernest.hong@Ernest  ~/Dev  npx create-probot-app@v5.0.9 my-first-app

Let's create a Probot app!
Hit enter to accept the suggestion.

? App name: my-first-app
? Description of app: A Probot app
? Author's full name: Ernest Hong
? Which template would you like to use? basic-ts => Comment on new issues - written in TypeScript
created file: /Users/ernest.hong/Dev/my-first-app/.dockerignore
created file: /Users/ernest.hong/Dev/my-first-app/.env.example
created file: /Users/ernest.hong/Dev/my-first-app/.gitignore
created file: /Users/ernest.hong/Dev/my-first-app/CODE_OF_CONDUCT.md
created file: /Users/ernest.hong/Dev/my-first-app/CONTRIBUTING.md
created file: /Users/ernest.hong/Dev/my-first-app/Dockerfile
created file: /Users/ernest.hong/Dev/my-first-app/LICENSE
created file: /Users/ernest.hong/Dev/my-first-app/README.md
created file: /Users/ernest.hong/Dev/my-first-app/app.yml
created file: /Users/ernest.hong/Dev/my-first-app/jest.config.js
created file: /Users/ernest.hong/Dev/my-first-app/package.json
created file: /Users/ernest.hong/Dev/my-first-app/tsconfig.json
created file: /Users/ernest.hong/Dev/my-first-app/src/index.ts
created file: /Users/ernest.hong/Dev/my-first-app/test/index.test.ts
created file: /Users/ernest.hong/Dev/my-first-app/test/fixtures/issues.opened.json
created file: /Users/ernest.hong/Dev/my-first-app/test/fixtures/mock-cert.pem

Finished scaffolding files!

Installing dependencies. This may take a few minutes...

Error: ⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⸩ ⠦ idealTree:v8-to-istanbul: timing idealTree:node_modules/v8-to-istanbul Completed in 0ms

Could not install npm dependencies.
Try running npm install yourself.

해당 폴더로 이동한 뒤 npm install 해봤지만 안된다

 ✘ ernest.hong@Ernest  ~/Dev/my-first-app  npm install
npm ERR! code ERR_SOCKET_TIMEOUT
npm ERR! errno ERR_SOCKET_TIMEOUT
npm ERR! network Invalid response body while trying to fetch https://registry.npmjs.org/ts-jest: Socket timeout
npm ERR! network This is a problem related to network connectivity.
npm ERR! network In most cases you are behind a proxy or have bad network settings.
npm ERR! network
npm ERR! network If you are behind a proxy, please make sure that the
npm ERR! network 'proxy' config is set properly.  See: 'npm help config'

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/ernest.hong/.npm/_logs/2023-01-30T08_05_23_055Z-debug-0.log

비슷한 사례가 있는데 내 환경에서도 ts-jest가 말썽인 것 같다. npm uninstall ts-jest 해준다.

 ✘ ernest.hong@Ernest  ~/Dev/my-first-app  npm uninstall ts-jest
npm WARN deprecated @types/pino-std-serializers@4.0.0: This is a stub types definition. pino-std-serializers provides its own type definitions, so you do not need this installed.
npm WARN deprecated @types/pino-pretty@5.0.0: This is a stub types definition. pino-pretty provides its own type definitions, so you do not need this installed.

added 590 packages, and audited 591 packages in 19s

41 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

드디어 설치가 되었다.

 ernest.hong@Ernest  ~/Dev/my-first-app  npm install

up to date, audited 591 packages in 500ms

41 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Github App 생성, 로컬 테스트 환경 세팅

.env.example 파일은 자동으로 만들어지는데, 일단 이걸 .env로 복사해준다.

cp .env.example .env

.env 파일 내용

# The ID of your GitHub App
APP_ID=
WEBHOOK_SECRET=development

# Use `trace` to get verbose logging or `info` to show less
LOG_LEVEL=debug

# Go to https://smee.io/new set this to the URL that you are redirected to.
WEBHOOK_PROXY_URL=

smee.io가 대신 Github web hook을 받아서 로컬 서버에 전달해준다.

간단히 비교해보면 이렇다.

  • Remote 서버: Github 웹훅 -> Remote Server가 감지
  • Local 서버: Github 웹훅 -> smee.io Server가 감지 -> 로컬 서버에 전송

Start a new channel 누르면 된다. 근데 내 환경에서는 되게 오래 걸렸다.

# Go to https://smee.io/new set this to the URL that you are redirected to.
WEBHOOK_PROXY_URL=https://smee.io/T9qC0jBHvqRCzAzY

여기까지 해놓고 Github Apps에 App을 새로 등록해준다.

App이 만들어졌다.

# The ID of your GitHub App
APP_ID={App ID를 여기에 채운다}

my-first-app.2023-01-30.private-key.pem 이런게 다운 받아지는데 이걸 프로젝트 루트로 이동시켜야 한다.

Github App 설치

테스트용 레포를 하나 만들어서 거기에만 설치했다.

Probot 서버 테스트

 ernest.hong@Ernest  ~/Dev/my-first-app  npm start

> my-first-app@1.0.0 start
> probot run ./lib/index.js

INFO (probot):
INFO (probot): Welcome to Probot!
INFO (probot): Probot is in setup mode, webhooks cannot be received and
INFO (probot): custom routes will not work until APP_ID and PRIVATE_KEY
INFO (probot): are configured in .env.
INFO (probot): Please follow the instructions at http://localhost:3000 to configure .env.
INFO (probot): Once you are done, restart the server.
INFO (probot):
INFO (server): Running Probot v12.3.0 (Node.js: v19.4.0)
INFO (server): Listening on http://localhost:3000

실수로 .env 파일의 App_ID가 입력되지 않은채로 저장되어서 web hook이 연결되지 않았다. .env를 업데이트하고 다시 실행했더니 에러가 뜬다.

 ernest.hong@Ernest  ~/Dev/my-first-app  npm start

> my-first-app@1.0.0 start
> probot run ./lib/index.js

/Users/ernest.hong/Dev/my-first-app/node_modules/resolve/lib/sync.js:111
    var err = new Error("Cannot find module '" + x + "' from '" + parent + "'");
              ^

Error: Cannot find module './lib/index.js' from '/Users/ernest.hong/Dev/my-first-app'
    at resolveSync (/Users/ernest.hong/Dev/my-first-app/node_modules/resolve/lib/sync.js:111:15)
    at resolveAppFunction (/Users/ernest.hong/Dev/my-first-app/node_modules/probot/lib/helpers/resolve-app-function.js:34:23)
    at combinedApps (/Users/ernest.hong/Dev/my-first-app/node_modules/probot/lib/run.js:101:79)
    at async Server.load (/Users/ernest.hong/Dev/my-first-app/node_modules/probot/lib/server/server.js:60:9)
    at async run (/Users/ernest.hong/Dev/my-first-app/node_modules/probot/lib/run.js:105:9) {
  code: 'MODULE_NOT_FOUND'
}

Node.js v19.4.0

빌드하고 서버 시작해줘야한다

npm run build
npm start
 ernest.hong@Ernest  ~/Dev/my-first-app  npm start

> my-first-app@1.0.0 start
> probot run ./lib/index.js

INFO (server): Running Probot v12.3.0 (Node.js: v19.4.0)
INFO (server): Forwarding https://smee.io/T9qC0jBHvqRCzAzY to http://localhost:3000/
INFO (server): Listening on http://localhost:3000
INFO (server): Connected

커밋 추가해보기

PR Comment 추가되었을 떄만 작동하도록 했더니 PR 코드에 어떤 변경사항을 코멘트 하는 부분에만 작동하나보다. Issue comment에도 이벤트를 받도록 체크했다.

그랬더니 콘솔에서 이렇게 응답한다...

INFO (http): POST / 400 - 4ms
ERROR (server): Bad Request
    Error: Bad Request
        at Request.callback (/Users/ernest.hong/Dev/my-first-app/node_modules/superagent/lib/node/index.js:921:17)
        at /Users/ernest.hong/Dev/my-first-app/node_modules/superagent/lib/node/index.js:1165:20
        at IncomingMessage.<anonymous> (/Users/ernest.hong/Dev/my-first-app/node_modules/superagent/lib/node/parsers/json.js:22:7)
        at IncomingMessage.emit (node:events:525:35)
        at IncomingMessage.emit (node:domain:489:12)
        at endReadableNT (node:internal/streams/readable:1359:12)
        at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
status: 400

알고보니 WEBHOOK_SECRET를 40자 String으로 입력했어야 했는데 그냥 development 입력해서 이슈가 생긴것이다. https://github.com/probot/probot/issues/1713#issuecomment-1207235337 아래 명령어를 입력하면 랜덤한 글자가 생성된다.

ruby -rsecurerandom -e 'puts SecureRandom.hex(20)'

이 부분도 업데이트 해주자.

index.ts 파일을 다시 보면 이런식으로 구성해뒀다. 이슈에 코멘트가 달리면 bot이 새로운 코멘트를 추가하는 방식이다.

import { Probot } from "probot";

export = (app: Probot) => {
  app.log('Yay, the app was loaded!')

  app.on("issue_comment.created", async (context) => {
    app.log("hey");
    const issueComment = context.issue({
      body: "Pong",
    });
    await context.octokit.issues.createComment(issueComment);
  });
};

실행해봤더니 잘 된다!! 하지만 bot이 추가한 코멘트도 새로 추가된 코멘트로 인식되어서 무한 루프에 빠져버린다. (영원히 추가되는 코멘트...)

이 경우 특정 명령어가 있는 코멘트만 인식하고 싶은거라서 probot-commands을 사용하면 된다.

다시 index.ts로 돌아와서 다음과 같이 변경했다.

npm install --save probot-commands
import { Probot } from "probot";

const commands = require("probot-commands");

export = (app: Probot) => {
  app.log('Yay, the app was loaded!')
  
  commands(app, 'bot', (context: any, command: any) => {
    const issueComment = context.issue({
      body: "Pong",
    });
    return context.octokit.issues.createComment(issueComment);
  });
};

(context: any, command: any) 부분의 파라미터 타입을 구체적인 타입으로 정의하고 싶은데 어떤걸로 정의해야 할지 모르겠다. (혹시 아시는 분 있다면 댓글 부탁드려요 🙏)

command 없이 그냥 새로운 코멘트를 입력하면 특정 동작으로 라우팅 되지 않는 것을 볼 수 있다. /bot을 했을 때만 작동한다.

참고로 node.js 기반으로 되어 있기 때문에 API 응답도 가능하다. https://stackoverflow.com/a/72033201

다음으로

그 다음으로 명령어를 통해서 특정 CircleCI Pipeline을 작동시키고 싶다. 이전에 Github Comment로 CircleCI 실행시키기 이 글에서 적용했던 내용이다. 이 때는 Github Action으로 구성해둔 것이 차이점이고, 앞서 말한것 처럼 여러 Repo에 적용하기에는 확장성이 떨어진다. 과연 여러 Repo에 적용하기에 더 편한게 맞는지 검증해봐야 한다. 그리고 remote 환경에서도 잘 작동하는지도 체크해보려한다. 아마도 AWS Lambda에 배포하게 될 것 같다.

참고

profile
Software Engineer

1개의 댓글

comment-user-thumbnail
2024년 3월 13일

위의 정보 덕분에 문제를 해결했습니다. 내 지식이 그렇게 좋지 않아서 이것을 감지할 수 없습니다. run 3

답글 달기