Skip to content

Commit c7ea108

Browse files
author
Tom Osowski
authored
Merge pull request #140 from osowskit/app-issue-creator
Adding GitHub App example server
2 parents b278780 + 504e49f commit c7ea108

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed

app/ruby/app-issue-creator/Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source "http://rubygems.org"
2+
3+
gem "json", "~> 1.8"
4+
gem 'sinatra', '~> 1.3.5'
5+
gem 'octokit'
6+
gem 'jwt'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
GEM
2+
remote: http://rubygems.org/
3+
specs:
4+
addressable (2.5.1)
5+
public_suffix (~> 2.0, >= 2.0.2)
6+
faraday (0.12.1)
7+
multipart-post (>= 1.2, < 3)
8+
json (1.8.6)
9+
jwt (1.5.6)
10+
multipart-post (2.0.0)
11+
octokit (4.7.0)
12+
sawyer (~> 0.8.0, >= 0.5.3)
13+
public_suffix (2.0.5)
14+
rack (1.6.8)
15+
rack-protection (1.5.3)
16+
rack
17+
sawyer (0.8.1)
18+
addressable (>= 2.3.5, < 2.6)
19+
faraday (~> 0.8, < 1.0)
20+
sinatra (1.3.6)
21+
rack (~> 1.4)
22+
rack-protection (~> 1.3)
23+
tilt (~> 1.3, >= 1.3.3)
24+
tilt (1.4.1)
25+
26+
PLATFORMS
27+
ruby
28+
29+
DEPENDENCIES
30+
json (~> 1.8)
31+
jwt
32+
octokit
33+
sinatra (~> 1.3.5)
34+
35+
BUNDLED WITH
36+
1.15.1

app/ruby/app-issue-creator/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# app-issue-creator
2+
3+
This is the sample project that walks through creating a GitHub App and configuring a server to listen to [`installation` events](https://developer.github.com/v3/activity/events/types/#installationevent). When an App is added to an account, it will create an issue in each repository with a message, "added new app!".
4+
5+
## Requirements
6+
7+
* Ruby installed
8+
* [Bundler](http://bundler.io/) installed
9+
* [ngrok](https://ngrok.com/) or [localtunnel](https://localtunnel.github.io/www/) exposing port `4567` to allow GitHub to access your server
10+
11+
## Set up a GitHub App
12+
13+
* [Set up and register GitHub App](https://developer.github.com/apps/building-integrations/setting-up-and-registering-github-apps/)
14+
* [Enable `issue` write permissions](https://developer.github.com/v3/apps/permissions/#permission-on-issues)
15+
* If not running on a public-facing IP, use ngrok to generate a URL as [documented here](https://developer.github.com/v3/guides/building-a-ci-server/#writing-your-server)
16+
17+
## Install and Run project
18+
19+
Install the required Ruby Gems by entering `bundle install` on the command line.
20+
21+
Set environment variables `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`. For example, run the following to store the private key to an environment variable: `export GITHUB_APP_PRIVATE_KEY="$(less private-key.pem)"`
22+
23+
To start the server, type `ruby server.rb` on the command line.
24+
25+
The [sinatra server](http://www.sinatrarb.com/) will be running at `localhost:4567`.
26+
27+
[basics of auth]: http://developer.github.com/guides/basics-of-authentication/

app/ruby/app-issue-creator/server.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require 'sinatra'
2+
require 'jwt'
3+
require 'json'
4+
require 'active_support/all'
5+
require 'octokit'
6+
7+
begin
8+
GITHUB_APP_ID = ENV.fetch("GITHUB_APP_ID")
9+
GITHUB_PRIVATE_KEY = ENV.fetch("GITHUB_APP_PRIVATE_KEY")
10+
rescue KeyError
11+
$stderr.puts "To run this script, please set the following environment variables:"
12+
$stderr.puts "- GITHUB_APP_ID: GitHub App ID"
13+
$stderr.puts "- GITHUB_APP_PRIVATE_KEY: GitHub App Private Key"
14+
exit 1
15+
end
16+
@client = nil
17+
18+
# Webhook listener
19+
post '/payload' do
20+
github_event = request.env['HTTP_X_GITHUB_EVENT']
21+
if github_event == "installation"
22+
parse_installation_payload(request.body.read)
23+
else
24+
puts "New event #{github_event}"
25+
end
26+
end
27+
28+
# To authenticate as a GitHub App, generate a private key. Use this key to sign
29+
# a JSON Web Token (JWT), and encode using the RS256 algorithm. GitHub checks
30+
# that the request is authenticated by verifying the token with the
31+
# integration's stored public key. https://git.io/vQOLW
32+
def get_jwt_token
33+
private_key = OpenSSL::PKey::RSA.new(GITHUB_PRIVATE_KEY)
34+
35+
payload = {
36+
# issued at time
37+
iat: Time.now.to_i,
38+
# JWT expiration time (10 minute maximum)
39+
exp: 5.minutes.from_now.to_i,
40+
# GitHub App's identifier
41+
iss: GITHUB_APP_ID
42+
}
43+
44+
JWT.encode(payload, private_key, "RS256")
45+
end
46+
47+
# A GitHub App is installed by a user on one or more repositories.
48+
# The installation ID is passed in the webhook event. This returns all
49+
# repositories this installation has access to.
50+
def get_app_repositories
51+
json_response = @client.list_installation_repos
52+
53+
repository_list = []
54+
if json_response.count > 0
55+
json_response["repositories"].each do |repo|
56+
repository_list.push(repo["full_name"])
57+
end
58+
else
59+
puts json_response
60+
end
61+
62+
repository_list
63+
end
64+
65+
# For each repository that has Issues enabled, create an issue stating that a
66+
# GitHub App was installed
67+
def create_issues(repositories, sender_username)
68+
repositories.each do |repo|
69+
begin
70+
@client.create_issue(repo, "#{sender_username} added new app!", "Added GitHub App")
71+
rescue
72+
puts "Issues is disabled for this repository"
73+
end
74+
end
75+
end
76+
77+
# When an App is added by a user, it will generate a webhook event. Parse an
78+
# `installation` webhook event, list all repositories this App has access to,
79+
# and create an issue.
80+
def parse_installation_payload(json_body)
81+
webhook_data = JSON.parse(json_body)
82+
if webhook_data["action"] == "created" || webhook_data["action"] == "added"
83+
installation_id = webhook_data["installation"]["id"]
84+
85+
# Get JWT for App and get access token for an installation
86+
jwt_client = Octokit::Client.new(:bearer_token => get_jwt_token)
87+
jwt_client.default_media_type = "application/vnd.github.machine-man-preview+json"
88+
app_token = jwt_client.create_installation_access_token(installation_id)
89+
90+
# Create octokit client that has access to installation resources
91+
@client = Octokit::Client.new(access_token: app_token[:token] )
92+
@client.default_media_type = "application/vnd.github.machine-man-preview+json"
93+
94+
# List all repositories this installation has access to
95+
repository_list = []
96+
if webhook_data["installation"].key?("repositories_added")
97+
webhook_data["installation"]["repositories_added"].each do |repo|
98+
repository_list.push(repo["full_name"])
99+
end
100+
else
101+
# Get repositories by query
102+
repository_list = get_app_repositories
103+
end
104+
105+
# Create an issue in each repository stating an App has been given added
106+
create_issues(repository_list, webhook_data["sender"]["login"])
107+
end
108+
end

0 commit comments

Comments
 (0)