Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

Probot: Commands를 사용해서 GitHub 댓글로 봇 실행하기

Probot에 대한 글을 썼는데 Probot에는 몇 가지 확장 프로그램을 제공하고 있다. 이 확장을 통해서 GitHub App을 만들 때 공통으로 사용할 만한 기능을 제공하고 있다.

Probot: Commands

Probot: Commands는 PR이나 이슈에서 특정 명령어를 통해 App이 동작하도록 하는 기능이다. Probot의 기본 앱은 이전 글에서 살펴봤는데 이때 index.js는 아래와 같이 생겼다.

/**
 * This is the entry point for your Probot App.
 * @param {import('probot').Application} app - Probot's Application class.
 */
module.exports = app => {
  // Your code here
  app.log('Yay, the app was loaded!')

  app.on('issues.opened', async context => {
    const issueComment = context.issue({ body: 'Thanks for opening this issue!' })
    return context.github.issues.createComment(issueComment)
  })

  // For more information on building apps:
  // https://probot.github.io/docs/

  // To get your app running against GitHub, see:
  // https://probot.github.io/docs/development/
}

probot-commands는 GitHub 이슈나 PR에서 /로 시작하는 명령어를 받아서 해당 명령어를 실행하게 된다. 위 index.js를 아래와 같이 변경한다.

const commands = require('probot-commands')

module.exports = app => {
  app.log('Yay, the app was loaded!')

  commands(app, 'echo', (context, command) => {
    const issueComment = context.issue({ body: command.arguments })
    return context.github.issues.createComment(issueComment)
  });
}

commands(app, 'echo', (context, command) => {})에서 echo라는 명령어가 댓글로 남겨지면 이 콜백 함수가 실행된다. 여기서 다른 API를 호출하거나 원하는 작업을 수행할 수 있는데 여기서는 예제이므로 사용자가 /echo contents라고 댓글로 남기면 이 contents라는 문자열(command.arguments)을 받아서 다시 댓글로 남겨주도록 작성했다.

이를 위해서 GitHub App이 댓글 이벤트도 웹 훅으로 받도록 App의 이벤트 목록을 수정한다.

GitHub App의 이벤트 설정

앱 설정에서 Issue comment 이벤트도 설정했다.

GitHub 댓글에 Probot이 댓글을 남긴 모습

이제 GitHub 저장소의 댓글에서 /echo 명령어로 댓글을 남기면 Probot이 받아서 해당 내용을 다시 올리는 것을 볼 수 있다.

2018/07/30 23:18 2018/07/30 23:18

Probot으로 GitHub App 만들기

ProbotGitHub App을 쉽게 만들 수 있는 프레임워크다. 기존에 사용하던 GitHub Service는 2018년 10월 1일부로 더는 지원하지 않기 때문에 앞으로 GitHub에서 기능을 확장하거나 통합하려면 GitHub App을 사용해야 한다.

Probot 로고

Probot은 Node.js로 만들어진 프레임워크인데 알게 된 지는 좀 되었지만 몇 달 전부터 사용을 해보고 있다. 사용해 보니 GitHub App을 쉽게 만들 수 있게 인터페이스나 도구가 잘 갖추어져 있다. 만들기는 쉬운데 뭘 만들지가 더 어렵다.

Probot으로 만든 앱 목록을 보면 GitHub App으로 어떤 기능을 구현할 수 있는지 알 수 있고 전부 오픈소스이므로 비슷한 기능을 개발할 때 참고해서 사용할 수 있다.

Stale이 동작한 댓글

Stalemocha에서처럼 이슈에 일정 시간 동안 활동이 없으면 더는 활동이 없으면 닫겠다고 알린 뒤에 해당 시간이 지나면 이슈를 닫는다.

Lock Threads가 잠근 댓글

Henry Zhu가 An Issue with Issues에서 이미 닫힌 이슈에 사람들이 글을 계속 남겨서 관리 부담이 커지는 것을 막기 위해서 Babel에서 Lock Threads를 이용해서 이슈를 잠갔다.

Probot으로 GitHub App 만들기

최초 앱 구조를 만들기 쉽도록 create-probot-app을 제공하고 있다.

$ npx create-probot-app ping-app
npx: 227개의 패키지를 4.522초만에 설치했습니다.
Let's create a Probot app!
? App name: ping-app
? Description of app: An example app
? Author's full name: Outsider
? Author's email address: 
? Homepage:
? GitHub user or org name: outsideris
? Repository name: ping-app
created file: /Users/outsider/example/ping-app/.env.example
created file: /Users/outsider/example/ping-app/.gitignore
created file: /Users/outsider/example/ping-app/.travis.yml
created file: /Users/outsider/example/ping-app/CODE_OF_CONDUCT.md
created file: /Users/outsider/example/ping-app/CONTRIBUTING.md
created file: /Users/outsider/example/ping-app/LICENSE
created file: /Users/outsider/example/ping-app/README.md
created file: /Users/outsider/example/ping-app/index.js
created file: /Users/outsider/example/ping-app/package.json
created file: /Users/outsider/example/ping-app/.github/CODEOWNERS
created file: /Users/outsider/example/ping-app/test/index.test.js
created file: /Users/outsider/example/ping-app/test/fixtures/issues.opened.json
Finished scaffolding files!

Installing Node dependencies!

npm notice created a lockfile as package-lock.json. You should commit this file.
added 995 packages from 533 contributors in 31.587s
[+] no known vulnerabilities found [22357 packages audited]


Done! Enjoy building your Probot app!

위 명령어로 필요한 모든 파일을 만들어주고 필요한 npm 모듈도 설치해 준다.

GitHub App은 보통 개발자가 앱을 GitHub에 등록하고 이 앱을 저장소에 설치하면 해당 저장소에서 이벤트를 받아서 Probot이 처리하게 된다. 서버를 공개적으로 배포했다면 상관없지만, 로컬에서 개발할 때는 GitHub이 로컬 Probot 서버에 웹 훅을 바로 보낼 수 없으므로 프락시 서버를 두어야 한다. Probot 팀에서는 smee.io라는 서비스를 만들어서 제공하고 있다. 이를 사용하면 GitHub의 웹 훅을 smee.io가 대신 받아서 로컬 서버에 보내주기 때문에 개발을 편하게 할 수 있다.

앞에서 만들어진 .env.example 파일을 .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에서 "Start a new channel"을 누르면 아래처럼 새로운 프락시 URL을 만들어 준다.

smee.io에서 새로 만든 채널

위의 .env 파일의 WEBHOOK_PROXY_URLsmee.io의 URL을 지정한다.

WEBHOOK_PROXY_URL=https://smee.io/Sc5IsuyKfj06PeqM

Developer settings 아래 Register new GitHub App에서 새로운 GitHub 앱을 등록할 수 있다.

GitHub App 등록화면

앱의 이름을 지정하고 Webhook URL에 위의 smee.io URL을 입력한다.. Webhook secret에는 .envWEBHOOK_SECRET에 있는 development를 입력한다.

GitHub App의 권한 설정

하단 Permissions 부분에서는 사용할 권한을 지정한다. 이는 나중에 앱을 설치할 때 설치하는 사람에게 어떤 권한을 쓰는지 알려주게 된다. 예를 들어 이슈 내용을 가져올 것이면 Issues를 "Read-only"로 지정하면 되고 앱이 댓글도 남기려면 "Read & write"로 지정하면 된다.

GitHub App의 이벤트 구독 설정

지정한 권한에 따라 웹 훅을 받을 이벤트를 체크한다. 여기선 예제로 Issues 이벤트만 받도록 지정했다. 마지막으로 아직 테스트 중이므로 "Only on this account"를 선택해서 내 계정에서만 사용할 수 있게 지정했다.

등록한 앱의 ID 확인과 키 생성

앱을 등록하고 나면 Basic information 중간에서 등록된 앱 ID를 볼 수 있다. 이 ID를 .env 파일의 APP_ID=에 지정한다. Generate a private key를 클릭하면 ping-example-app.2018-07-29.private-key.pem와 같은 키 파일이 다운로드되는데 이를 프로젝트 루트 위치로 이동시킨다.

GitHub 앱 설치

등록한 앱을 GitHub 저장소에 설치해야 사용할 수 있다.

GitHub 앱 설치화면

현재는 내 계정에서만 설치할 수 있으므로 앱 설정 화면에서 설치할 수 있다.

GitHub 앱 설치화면

사용할 저장소를 지정한 뒤에 설치한다.

Probot 서버 테스트

앞에서 만들어진 index.js의 내용은 다음과 같다.

/**
 * This is the entry point for your Probot App.
 * @param {import('probot').Application} app - Probot's Application class.
 */
module.exports = app => {
  // Your code here
  app.log('Yay, the app was loaded!')

  app.on('issues.opened', async context => {
    app.log('hello');
    const issueComment = context.issue({ body: 'Thanks for opening this issue!' })
    return context.github.issues.createComment(issueComment)
  })

  // For more information on building apps:
  // https://probot.github.io/docs/

  // To get your app running against GitHub, see:
  // https://probot.github.io/docs/development/
}

app.on('issues.opened', async context => {})부분이 GitHub의 이벤트를 받는 부분이다. 이슈가 열렸을 때 발생하는 이벤트인 issues.opened가 웹 훅으로 오면 이 함수가 실행된다. 여러 이벤트를 받을 때는 app.on(['issues.opened', 'issues.closed'], callback)처럼 받으면 된다. 이벤트 이름은 GitHub 문서에서 확인할 수 있는데 이벤트 이름과 종류를 조합해서 사용하면 된다.

그리고 context.github.issues.createComment는 댓글을 남기는 함수이다. 즉, 이슈가 만들었지만 앱이 Thanks for opening this issue!라는 댓글을 남기게 된다. 여기서 context.github@octokit/rest의 객체이므로 GitHub API에 필요한 부분은 Octokit 문서를 확인하면 된다.

$ npm start

> ping-app@1.0.0 start /Users/outsider/Dropbox/projects/temp/ping-app
> probot run ./index.js

13:15:03.322Z  INFO probot: Yay, the app was loaded!
13:15:03.439Z  INFO probot: Forwarding https://smee.io/Sc5IsuyKfj06PeqM to http://localhost:3000/
13:15:03.442Z  INFO probot: Listening on http://localhost:3000
13:15:04.239Z  INFO probot: Connected https://smee.io/Sc5IsuyKfj06PeqM
13:15:04.546Z DEBUG github: GitHub request: GET /app/installations - 200 OK (installation=undefined)
  params: {
    "per_page": 100,
    "baseUrl": "https://api.github.com",
    "request": {
      "timeout": 0
    }
  }
13:15:05.101Z DEBUG github: GitHub request: POST /installations/:installation_id/access_tokens - 201 Created (installation=264944)
  params: {
    "installation_id": "264944",
    "baseUrl": "https://api.github.com",
    "request": {
      "timeout": 0
    }
  }
13:15:06.194Z DEBUG github: GitHub request: GET /installation/repositories - 200 OK (installation=264944)
  params: {
    "per_page": 100,
    "baseUrl": "https://api.github.com",
    "request": {
      "timeout": 0
    }
  }

위처럼 npm start로 서버를 시작하고 GitHub에서 새로운 이슈를 만들면 smee.io를 통해 아래처럼 웹 훅을 받게 된다.

13:15:47.256Z DEBUG probot: Webhook received
  event: {
    "event": "issues.opened",
    "id": "816ee850-9331-11e8-847b-59be5276fdff",
    "installation": 264944,
    "repository": "outsideris/cla-checker"
  }
13:15:47.265Z  INFO probot: hello
13:15:47.922Z DEBUG github: GitHub request: POST /repos/:owner/:repo/issues/:number/comments - 201 Created (id=816ee850-9331-11e8-847b-59be5276fdff, installation=264944)
  params: {
    "number": 17,
    "owner": "outsideris",
    "repo": "cla-checker",
    "body": "Thanks for opening this issue!",
    "baseUrl": "https://api.github.com",
    "request": {
      "timeout": 0
    }
  }
13:15:47.925Z  INFO http: POST / 200 - 670.04 ms (id=70fc935d-fab6-4af4-8f83-20f4de2a7e2d)
13:15:47.929Z  INFO probot: POST http://localhost:3000/ - 200

만든 probot이 댓글을 남긴 이슈

ping-example-app에 아이콘을 올리지 않아서 내 아이콘이 사용되었지만 새로 만든 이슈에 제대로 달린 것을 볼 수 있다.

smee.io에 저장된 웹훅 페이로드

smee.io를 보면 전달된 웹 훅이 다 기록되어 있고 이를 재전송할 수 있다. GitHub과 연동해서 만들어야 하므로 이슈를 등록했다가 오류가 있으면 이슈를 다시 등록해서 테스트해야 하므로 smee.io의 이런 기능은 개발할 때 무척 편리하다.

설치과정이 좀 복잡했지만, 내용은 아주 어렵지 않다. 특히 인터페이스가 잘 되어 있어서 아주 쉽게 GitHub에서 필요한 부분을 자동화할 수 있다.

2018/07/29 22:34 2018/07/29 22:34