Git
Chapters ▾ 2nd Edition

6.5 GitHub - GitHub 스크립팅

GitHub 스크립팅

지금까지 GitHub의 주요기능과 워크플로를 모두 살펴봤다. 프로젝트가 크거나 그룹이 크면 매우 꼼꼼하게 설정하거나 다른 서비스를 통합시켜야 할 필요도 있다.

다행히 GitHub에는 해커들에게 제공하는 방법이 있다. 이 절에서는 GitHub 훅과 API을 사용하는 법을 설명한다.

서비스와 훅

GitHub 저장소 관리의 훅과 서비스 절에 보면 다른 시스템과 연동하는 가장 쉬운 방법이 나온다.

서비스

GitHub 서비스부터 살펴보자. 훅과 서비스는 저장소의 설정 페이지에서 연동할 수 있다. 이전에 동료를 추가하거나 기본 브랜치를 설정하던 그곳이다. “Webhooks와 Services” 탭은 서비스와 훅 설정 화면.처럼 생겼다.

서비스와 훅
Figure 130. 서비스와 훅 설정 화면.

CI, 버그 트래커, 이슈 트래커, 채팅, 문서 시스템 등과 연동하는 데 사용하는 서비스가 수십 개 준비돼 있다. 여기서는 가장 단순한 Email 훅을 살펴본다. “Add Service” 메뉴에서 “email” 을 선택하면 Email 서비스 설정. 같은 설정 화면으로 이동한다.

Email 서비스
Figure 131. Email 서비스 설정.

이메일을 입력하고 “Add service” 버튼을 누르면 누군가 저장소에 Push 할 때마다 이메일이 날아간다. 서비스는 다양한 이벤트를 처리할 수 있지만, 보통은 Push 할 때 그 데이터를 가지고 뭔가를 한다.

연동하려는 시스템을 지원하는 서비스가 이미 있는지 GitHub에서 먼저 찾아봐야 한다. 예를 들어, Jenkins를 사용해서 코드 테스트할 계획이라면 Jenkins 서비스를 이용해서 연동한다. 누군가 저장소에 Push 할 때마다 테스트를 수행되도록 할 수 있다.

GitHub 서비스에 없는 사이트나 외부 서비스와 연동하고 싶거나 좀 더 세세한 설정을 하고 싶으면 GitHub 훅을 이용한다. GitHub 저장소의 훅은 단순하다. URL을 하나 주면 그 URL로 HTTP 페이로드를 보내준다.

GitHub 훅 페이로드를 처리하는 간단한 웹 서비스를 하나 만들고 그 서비스에 원하는 동작을 구현하는 것이 일반적이다.

서비스와 훅 설정 화면.의 “Add webhook” 버튼을 클릭하면 아래와 같은 웹훅 설정. 페이지로 이동한다.

웹훅
Figure 132. 웹훅 설정.

웹훅 설정은 매우 간단하다. URL와 보안 키를 입력하고 “Add webhook” 버튼을 클릭한다. 어떤 이벤트의 페이로드가 필요한 것인지도 선택할 수 있지만 push 이벤트의 페이로드만 보내는 것이 기본이다. 그래서 누군가 아무 브랜치에나 코드를 Push 하면 HTTP 페이로드가 전송된다.

웹훅을 처리하는 간단한 웹 서비스 예제를 하나 살펴보자. 이 웹서비스는 Ruby 웹 프레임워크인 Sinatra를 사용했다. 간략하기 때문에 무엇을 하는 웹 서비스인지 쉽게 이해할 수 있을 것이다.

이메일을 보내는 서비스를 만들어 보자. 이 서비스는 누가 어느 브랜치에 어떤 파일을 Push 했는지를 알려준다. 이런 서비스는 매우 간단하게 만들 수 있다.

require 'sinatra'
require 'json'
require 'mail'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON

  # gather the data we're looking for
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # get a list of all the files touched
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # check for our criteria
  if pusher == 'schacon' &&
     branch == 'ref/heads/special-branch' &&
     files.include?('special-file.txt')

    Mail.deliver do
      from     'tchacon@example.com'
      to       'tchacon@example.com'
      subject  'Scott Changed the File'
      body     "ALARM"
    end
  end
end

GitHub는 누가 Push 했는지, 어느 브랜치에 Push 했는지, Push 한 커밋에서 어떤 파일을 수정했는지에 대한 정보를 JSON 페이로드에 담아서 보낸다. 여기서는 특정 조건을 검사해서 만족할 때만 이메일을 보낸다.

GitHub는 개발하고 테스트할 때 사용하는 개발자 콘솔도 제공한다. 이 콘솔은 혹을 설정한 페이지에 있다. 콘솔에서 해당 웹훅의 최근 히스토리 몇 개를 확인할 수 있다. 어떤 데이터가 전송됐는지 확인할 수 있다. 만약 전송에 성공했으면 요청과 응답의 바디와 헤더를 모두 확인할 수 있다. 이것으로 훅을 쉽게 테스트하고 디버깅할 수 있다.

웹훅 디버그
Figure 133. 웹훅 디버깅 정보.

서비스를 테스트할 수 있도록 히스토리에 있는 페이로드를 재전송할 수 있다.

어떤 이벤트가 있고 각각 어떻게 웹훅을 만드는지가 자세히 알고 싶다면 GitHub 개발 문서를 참고하라. (https://developer.github.com/webhooks/)

GitHub API

서비스와 훅은 저장소에서 발생한 이벤트의 알림을 받는 방법이다. 그런데 이벤트의 정보를 좀 더 자세히 알고 싶으면, 자동으로 동료를 추가하거나 이슈에 레이블을 달도록 하고 싶으면, 뭐 좋은 방법이 없을까?

이런 일을 위해서 GitHub API가 준비돼 있다. GitHub가 제공하는 API Endpoint는 매우 많아서 웹사이트에서 하는 웬만한 일은 자동화할 수 있다. 이 절에서는 인증하고 API에 연결하고, 이슈에 코멘트하고, Pull Request의 상태를 변경하는 법을 배운다.

기본 사용법

인증이 필요하지 않은 API Endpoint에 GET 요청을 보내기가 가장 쉽다. 사용자 정보나 오픈 소스 프로젝트의 정보를 읽어오는 것들이 이에 해당한다. 아래처럼 요청을 보내면 “schacon” 이라는 사용자에 대해 자세히 알 수 있다.

$ curl https://api.github.com/users/schacon
{
  "login": "schacon",
  "id": 70,
  "avatar_url": "https://avatars.githubusercontent.com/u/70",
# …
  "name": "Scott Chacon",
  "company": "GitHub",
  "following": 19,
  "created_at": "2008-01-27T17:19:28Z",
  "updated_at": "2014-06-10T02:37:23Z"
}

이렇게 Organization, 프로젝트, 이슈, 커밋 정보를 가져오는 Endpoint가 많이 있다. GitHub 페이지에서 볼 수 있는 것은 다 된다. 심지어 Markdown을 렌더링하거나 .gitignore 템플릿을 제공하는 API도 있다.

$ curl https://api.github.com/gitignore/templates/Java
{
  "name": "Java",
  "source": "*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

이슈에 코멘트하기

이슈나 Pull Request에 코멘트를 달거나 공개하지 않은 정보를 얻으려고 할 때는 인증이 필요하다.

몇 가지 방법으로 인증할 수 있다. 사용자이름과 암호가 필요한 Basic 인증도 가능하지만, 개인 엑세스 토큰을 사용하는 게 낫다. 설정 페이지의 “Applications” 탭에서 생성할 수 있다.

엑세스 토큰
Figure 134. 설정 페이지의 “Applications” 탭에서 엑세스 토큰을 생성.

토큰을 어디에 쓸지 범위를 선택하고 설명을 입력한다. 나중에 스크립트나 애플리케이션을 더이상 사용하지 않게 되었을 때, 삭제를 편히 할 수 있도록 설명 이해하기 쉽게 다는 게 좋다.

토큰이 생성되면 복사해서 사용한다. 이제 스크립트에서 사용자이름과 암호를 사용하지 않고 이 토큰을 사용할 수 있다. 토큰은 허용하는 범위가 제한돼 있고 언제든지 폐기할 수 있어서 좋다.

인증을 하지 않으면 API 사용 횟수 제한이 매우 낮다. 인증을 하지 않으면 한 시간에 60번만 허용되지만, 인증을 하면 한 시간에 5,000번까지 허용된다.

이제 이슈에 코멘트를 달아보자. #6 이슈에 코멘트를 달 거다. repos/<user>/<repo>/issues/<num>/comments 형식의 URL로 POST 요청을 보내는데 Authorization 헤더에 생성한 토큰을 넣어서 함께 보낸다.

$ curl -H "Content-Type: application/json" \
       -H "Authorization: token TOKEN" \
       --data '{"body":"A new comment, :+1:"}' \
       https://api.github.com/repos/schacon/blink/issues/6/comments
{
  "id": 58322100,
  "html_url": "https://github.com/schacon/blink/issues/6#issuecomment-58322100",
  ...
  "user": {
    "login": "tonychacon",
    "id": 7874698,
    "avatar_url": "https://avatars.githubusercontent.com/u/7874698?v=2",
    "type": "User",
  },
  "created_at": "2014-10-08T07:48:19Z",
  "updated_at": "2014-10-08T07:48:19Z",
  "body": "A new comment, :+1:"
}

해당 이슈 페이지에 가면 코멘트를 확인할 수 있다. GitHub API로 쓴 코멘트.처럼 잘 써진다.

API 코멘트
Figure 135. GitHub API로 쓴 코멘트.

웹사이트에서 할 수 있는 일은 전부 API로도 할 수 있다. 마일스톤을 만들고 설정하기, 사람들에게 이슈나 Pull Request를 할당하기, 레이블을 만들고 수정하기, 커밋 데이터 사용하기, 커밋을 하거나 브랜치 만들기, Pull Request를 만들고 닫고 Merge 하기, 팀을 만들고 수정하기, Pull Request 코드에 코멘트하기, 사이트에서 검색하기 등 다 된다.

Pull Request의 상태 변경하기

우리가 살펴볼 마지막 예제는 Pull Request에 관한 것인데 굉장히 유용하다. 커밋은 하나 이상의 상태를 가질 수 있는데 API를 통해서 상태를 추가하거나 조회할 수 있다.

대부분의 CI나 테스팅 서비스들은 코드가 푸시되면 바로 테스트를 하고 나서 이 API를 사용한다. 커밋이 모든 테스트를 통과하면 리포트한다. 이 API로 커밋 메시지가 규칙에 맞게 작성됐지 리포트할 수 있다. 코드를 보낸 사람이 제대로 가이드라인을 지켰는지나 커밋에 제대로 서명했는지도 기록할 수 있다.

커밋 메시지에 Signed-off-by 라는 스트링이 있는지 검사하는 웹 서비스를 만들어 보자. 먼저 저장소에 이 웹 서비스를 호출하는 웹훅을 등록한다.

require 'httparty'
require 'sinatra'
require 'json'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON
  repo_name = push['repository']['full_name']

  # look through each commit message
  push["commits"].each do |commit|

    # look for a Signed-off-by string
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # post status to GitHub
    sha = commit["id"]
    status_url = "https://api.github.com/repos/#{repo_name}/statuses/#{sha}"

    status = {
      "state"       => state,
      "description" => description,
      "target_url"  => "http://example.com/how-to-signoff",
      "context"     => "validate/signoff"
    }
    HTTParty.post(status_url,
      :body => status.to_json,
      :headers => {
        'Content-Type'  => 'application/json',
        'User-Agent'    => 'tonychacon/signoff',
        'Authorization' => "token #{ENV['TOKEN']}" }
    )
  end
end

이 웹훅 서비스는 별로 어렵지 않다. 누군가 Push 하면 모든 커밋을 훑는데, 커밋 메시지에서 Signed-off-by 스트링을 찾는다. 그 결과의 상태를 `/repos/<user>/<repo>/statuses/<commit_sha>`라는 Endpoint 주소에 POST 요청으로 보낸다.

커밋의 상태는 success, failure, 'error’일 수 있다. 커밋의 상태(state)와 설명(description), 자세한 정보를 확인할 수 있는 URL(target_url), 상태를 구분하는 “컨텍스트(context)” 를 함께 전송한다. 단일 커밋에서도 다양한 경우가 있기 때문에, 컨텍스트가 필요하다. 예를 들어 유효성을 검증하거나 상태값을 제공해 주는 테스팅 서비스의 경우 상태값을 제공해야 하는데, “컨텍스트” 필드를 통해 어떻게 상태가 변화했는지를 알 수 있다.

이 훅을 적용하고 나서 누군가 Pull Request를 새로 열면 API로 표기한 커밋 상태.같은 상태 메시지를 보게 된다.

커밋 상태
Figure 136. API로 표기한 커밋 상태.

“Signed-off-by” 스트링이 있는 커밋 메시지에는 녹색 체크 아이콘이 달리고 그렇지 않은 커밋에는 빨간 X 표시가 달린다. 그리고 Pull Request의 상태는 마지막 커밋의 상태를 보여주는데 상태가 'failure’면 경고해준다. 이 API를 사용해서 테스트 결과를 Pull Request에 리포트하는 것은 매우 유용하다. 테스트에 실패하는 커밋을 Merge 하는 일을 미연에 방지할 수 있다.

Octokit

이 책에서는 단순한 HTTP 요청을 보냈기 때문에 curl 만 사용했다. 하지만, 더 편리하게 API를 사용할 수 있게 해주는 오픈소스 라이브러리가 있다. 이 책을 쓰는 시점에서는 Go와 Objective-C, Ruby, .NET을 지원한다. 자세한 정보는 http://github.com/octokit에 가서 확인하면 되고 이미 많은 기능을 지원한다.

이 도구로 프로젝트가 요구하는 대로 GitHub의 워크플로를 최적화할 수 있다. 전체 API에 대한 구체적인 문서와 상황별 가이드는 https://developer.github.com에서 확인해야 한다.