Skip to content

Commit 54dcedd

Browse files
committed
Merge pull request github#473 from github/update-1396645501
Sync changes from upstream repository
2 parents 08295e1 + a8703c6 commit 54dcedd

File tree

7 files changed

+368
-6
lines changed

7 files changed

+368
-6
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
---
2+
title: Building a CI server | GitHub API
3+
---
4+
5+
# Building a CI server
6+
7+
* TOC
8+
{:toc}
9+
10+
The [Status API][status API] is responsible for tying together commits with
11+
a testing service, so that every push you make can be tested and represented
12+
in a GitHub pull request.
13+
14+
This guide will use that API to demonstrate a setup that you can use.
15+
In our scenario, we will:
16+
17+
* Run our CI suite when a Pull Request is opened (we'll set the CI status to pending).
18+
* When the CI is finished, we'll set the Pull Request's status accordingly.
19+
20+
Our CI system and host server will be figments of our imagination. They could be
21+
Travis, Jenkins, or something else entirely. The crux of this guide will be setting up
22+
and configuring the server managing the communication.
23+
24+
If you haven't already, be sure to [download ngrok][ngrok], and learn how
25+
to [use it][using ngrok]. We find it to be a very useful tool for exposing local
26+
connections.
27+
28+
Note: you can download the complete source code for this project
29+
[from the platform-samples repo][platform samples].
30+
31+
## Writing your server
32+
33+
We'll write a quick Sinatra app to prove that our local connections are working.
34+
Let's start with this:
35+
36+
#!ruby
37+
require 'sinatra'
38+
require 'json'
39+
40+
post '/event_handler' do
41+
payload = JSON.parse(params[:payload])
42+
"Well, it worked!"
43+
end
44+
45+
46+
(If you're unfamiliar with how Sinatra works, we recommend [reading the Sinatra guide][Sinatra].)
47+
48+
Start this server up. By default, Sinatra starts on port `9393`, so you'll want
49+
to configure ngrok to start listening for that, too.
50+
51+
In order for this server to work, we'll need to set a repository up with a webhook.
52+
The webhook should be configured to fire whenever a Pull Request is created, or merged.
53+
Go ahead and create a repository you're comfortable playing around in. Might we
54+
suggest [@octocat's Spoon/Knife repository](https://github.com/octocat/Spoon-Knife)?
55+
After that, you'll create a new webhook in your repository, feeding it the URL
56+
that ngrok gave you:
57+
58+
![A new ngrok URL](/images/webhook_sample_url.png)
59+
60+
Click **Update webhook**. You should see a body response of `Well, it worked!`.
61+
Great! Click on **Let me select individual events.**, and select the following:
62+
63+
* Status
64+
* Pull Request
65+
66+
These are the events GitHub will send to our server whenever the relevant action
67+
occurs. Let's update our server to *just* handle the Pull Request scenario right now:
68+
69+
#!ruby
70+
post '/event_handler' do
71+
@payload = JSON.parse(params[:payload])
72+
73+
case request.env['HTTP_X_GITHUB_EVENT']
74+
when "pull_request"
75+
if @payload["action"] == "opened"
76+
process_pull_request(@payload["pull_request"])
77+
end
78+
end
79+
end
80+
81+
helpers do
82+
def process_pull_request(pull_request)
83+
puts "It's #{pull_request['title']}"
84+
end
85+
end
86+
87+
What's going on? Every event that GitHub sends out attached a `X-GitHub-Event`
88+
HTTP header. We'll only care about the PR events for now. From there, we'll
89+
take the payload of information, and return the title field. In an ideal scenario,
90+
our server would be concerned with every time a pull request is updated, not just
91+
when it's updated. That would make sure that every new push passes the CI tests.
92+
But for this demo, we'll just worry about when it's opened.
93+
94+
To test out this proof-of-concept, make some changes in a branch in your test
95+
repository, and open a pull request. Your server should respond accordingly!
96+
97+
## Working with statuses
98+
99+
With our server in place, we're ready to start our first requirement, which is
100+
setting (and updating) CI statuses. Note that at any time you update your server,
101+
you can click **Redeliver** to send the same payload. There's no need to make a
102+
new pull request every time you make a change!
103+
104+
Since we're interacting with the GitHub API, we'll use [Octokit.rb][octokit.rb]
105+
to manage our interactions. We'll configure that client with
106+
[a personal access token][access token]:
107+
108+
#!ruby
109+
# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
110+
# Instead, set and test environment variables, like below
111+
ACCESS_TOKEN = ENV['MY_PERSONAL_TOKEN']
112+
113+
before do
114+
@client ||= Octokit::Client.new(:access_token => ACCESS_TOKEN)
115+
end
116+
117+
After that, we'll just need to update the pull request on GitHub to make clear
118+
that we're processing on the CI:
119+
120+
#!ruby
121+
def process_pull_request(pull_request)
122+
puts "Processing pull request..."
123+
@client.create_status(pull_request['head']['repo']['full_name'], pull_request['head']['sha'], 'pending')
124+
end
125+
126+
We're doing three very basic things here:
127+
128+
* we're looking up the full name of the repository
129+
* we're looking up the last SHA of the pull request
130+
* we're setting the status to "pending"
131+
132+
That's it! From here, you can run whatever process you need to in order to execute
133+
your test suite. Maybe you're going to pass off your code to Jenkins, or call
134+
on another web service via its API, like [Travis][travis api]. After that, you'd
135+
be sure to update the status once more. In our example, we'll just set it to `"success"`:
136+
137+
#!ruby
138+
def process_pull_request(pull_request)
139+
@client.create_status(pull_request['head']['repo']['full_name'], pull_request['head']['sha'], 'pending')
140+
sleep 2 # do busy work...
141+
@client.create_status(pull_request['head']['repo']['full_name'], pull_request['head']['sha'], 'success')
142+
puts "Pull request processed!"
143+
end
144+
145+
## Conclusion
146+
147+
At GitHub, we've used a version of [Janky][janky] to manage our CI for years.
148+
The basic flow is essentially the exact same as the server we've built above.
149+
At GitHub, we:
150+
151+
* Fire to Jenkins when a pull request is created or updated (via Janky)
152+
* Wait for a response on the state of the CI
153+
* If the code is green, we merge the pull request
154+
155+
All of this communication is funneled back to our chat rooms. You don't need to
156+
build your own CI or deployment setup to use this example.
157+
You can always rely on [third-party services][integrations].
158+
159+
[deploy API]: /v3/repos/deployments/
160+
[status API]: /v3/repos/statuses/
161+
[ngrok]: https://ngrok.com/
162+
[using ngrok]: /webhooks/configuring/#using-ngrok
163+
[platform samples]: https://github.com/github/platform-samples/tree/master/api/ruby/building-a-ci-server
164+
[Sinatra]: http://www.sinatrarb.com/
165+
[webhook]: /webhooks/
166+
[octokit.rb]: https://github.com/octokit/octokit.rb
167+
[access token]: https://help.github.com/articles/creating-an-access-token-for-command-line-use
168+
[travis api]: https://api.travis-ci.org/docs/
169+
[janky]: https://github.com/github/janky
170+
[heaven]: https://github.com/atmos/heaven
171+
[hubot]: https://github.com/github/hubot
172+
[integrations]: https://github.com/integrations
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
title: Delivering deployments | GitHub API
3+
---
4+
5+
# Delivering deployments
6+
7+
* TOC
8+
{:toc}
9+
10+
The [Deployments API][deploy API] provides your projects hosted on GitHub with
11+
the capability to launch them on a production server that you own. Combined with
12+
[the Status API][status API], you'll be able to coordinate your deployments
13+
the moment your code lands on `master`.
14+
15+
This guide will use that API to demonstrate a setup that you can use.
16+
In our scenario, we will:
17+
18+
* Merge a Pull Request
19+
* When the CI is finished, we'll set the Pull Request's status accordingly.
20+
* When the Pull Request is merged, we'll run our deployment to our server.
21+
22+
Our CI system and host server will be figments of our imagination. They could be
23+
Heroku, Amazon, or something else entirely. The crux of this guide will be setting up
24+
and configuring the server managing the communication.
25+
26+
If you haven't already, be sure to [download ngrok][ngrok], and learn how
27+
to [use it][using ngrok]. We find it to be a very useful tool for exposing local
28+
connections.
29+
30+
Note: you can download the complete source code for this project
31+
[from the platform-samples repo][platform samples].
32+
33+
## Writing your server
34+
35+
We'll write a quick Sinatra app to prove that our local connections are working.
36+
Let's start with this:
37+
38+
#!ruby
39+
require 'sinatra'
40+
require 'json'
41+
42+
post '/event_handler' do
43+
payload = JSON.parse(params[:payload])
44+
"Well, it worked!"
45+
end
46+
47+
48+
(If you're unfamiliar with how Sinatra works, we recommend [reading the Sinatra guide][Sinatra].)
49+
50+
Start this server up. By default, Sinatra starts on port `9393`, so you'll want
51+
to configure ngrok to start listening for that, too.
52+
53+
In order for this server to work, we'll need to set a repository up with a webhook.
54+
The webhook should be configured to fire whenever a Pull Request is created, or merged.
55+
Go ahead and create a repository you're comfortable playing around in. Might we
56+
suggest [@octocat's Spoon/Knife repository](https://github.com/octocat/Spoon-Knife)?
57+
After that, you'll create a new webhook in your repository, feeding it the URL
58+
that ngrok gave you:
59+
60+
![A new ngrok URL](/images/webhook_sample_url.png)
61+
62+
Click **Update webhook**. You should see a body response of `Well, it worked!`.
63+
Great! Click on **Let me select individual events.**, and select the following:
64+
65+
* Deployment
66+
* Deployment status
67+
* Pull Request
68+
69+
These are the events GitHub will send to our server whenever the relevant action
70+
occurs. We'll configure our server to *just* handle when Pull Requests are merged
71+
right now:
72+
73+
#!ruby
74+
post '/event_handler' do
75+
@payload = JSON.parse(params[:payload])
76+
77+
case request.env['HTTP_X_GITHUB_EVENT']
78+
when "pull_request"
79+
if @payload["action"] == "closed" && @payload["pull_request"]["merged"]
80+
puts "A pull request was merged! A deployment should start now..."
81+
end
82+
end
83+
end
84+
85+
What's going on? Every event that GitHub sends out attached a `X-GitHub-Event`
86+
HTTP header. We'll only care about the PR events for now. When a pull request is
87+
merged (its state is `closed`, and `merged` is `true`), we'll kick off a deployment.
88+
89+
To test out this proof-of-concept, make some changes in a branch in your test
90+
repository, open a pull request, and merge it. Your server should respond accordingly!
91+
92+
## Working with deployments
93+
94+
With our server in place, the code being reviewed, and our pull request
95+
is merged, we want our project to be deployed to the production server.
96+
97+
We'll start by modifying our event listener to process pull requests when they're
98+
merged, and start paying attention to deployments:
99+
100+
#!ruby
101+
when "pull_request"
102+
if @payload["action"] == "closed" && @payload["pull_request"]["merged"]
103+
start_deployment(@payload["pull_request"])
104+
end
105+
when "deployment"
106+
process_deployment(@payload)
107+
when "deployment_status"
108+
update_deployment_status
109+
end
110+
111+
Based on the information from the pull request, we'll start by filling out the
112+
`start_deployment` method:
113+
114+
#!ruby
115+
def start_deployment(pull_request)
116+
user = pull_request['user']['login']
117+
payload = JSON.generate(:environment => 'production', :deploy_user => user)
118+
@client.create_deployment(pull_request['head']['repo']['full_name'], pull_request['head']['sha'], {:payload => payload, :description => "Deploying my sweet branch"})
119+
end
120+
121+
Deployments can have some metadata attached to them, in the form of a `payload`
122+
and a `description`. Although these values are optional, it's helpful to use
123+
for logging and representing information.
124+
125+
When a new deployment is created, a completely separate event is trigged. That's
126+
why we have a new `switch` case in the event handler for `deployment`. You can
127+
use this information to be notified when a deployment has been triggered.
128+
129+
Deployments can take a rather long time, so we'll want to listen for various events,
130+
such as when the deployment was created, and what state it's in.
131+
132+
Let's simulate a deployment that does some work, and notice the effect it has on
133+
the output. First, let's complete our `process_deployment` method:
134+
135+
#!ruby
136+
def process_deployment
137+
payload = JSON.parse(@payload['payload'])
138+
# you can send this information to your chat room, monitor, pager, e.t.c.
139+
puts "Processing '#{@payload['description']}' for #{payload['deploy_user']} to #{payload['environment']}"
140+
sleep 2 # simulate work
141+
@client.create_deployment_status("repos/#{@payload['repository']['full_name']}/deployments/#{@payload['id']}", 'pending')
142+
sleep 2 # simulate work
143+
@client.create_deployment_status("repos/#{@payload['repository']['full_name']}/deployments/#{@payload['id']}", 'success')
144+
end
145+
146+
Finally, we'll simulate storing the status information as console output:
147+
148+
#!ruby
149+
def update_deployment_status
150+
puts "Deployment status for #{@payload['id']} is #{@payload['state']}"
151+
end
152+
153+
Let's break down what's going on. A new deployment is created by `start_deployment`,
154+
which triggers the `deployment` event. From there, we call `process_deployment`
155+
to simulate work that's going on. During that processing, we also make a call to
156+
`create_deployment_status`, which lets a receiver know what's going on, as we
157+
switch the status to `pending`.
158+
159+
After the deployment is finished, we set the status to `success`. You'll notice
160+
that this pattern is the exact same as when we you your CI statuses.
161+
162+
## Conclusion
163+
164+
At GitHub, we've used a version of [Heaven][heaven] to manage
165+
our deployments for years. The basic flow is essentially the exact same as the
166+
server we've built above. At GitHub, we:
167+
168+
* Wait for a response on the state of the CI
169+
* If the code is green, we merge the pull request
170+
* Heaven takes the merged code, and deploys it to our production servers
171+
* In the meantime, Heaven also notifies everyone about the build, via [Hubot][hubot] sitting in our chat rooms
172+
173+
That's it! You don't need to build your own CI or deployment setup to use this example.
174+
You can always rely on [third-party services][integrations].
175+
176+
[deploy API]: /v3/repos/deployments/
177+
[status API]: /guides/building-a-ci-server
178+
[ngrok]: https://ngrok.com/
179+
[using ngrok]: /webhooks/configuring/#using-ngrok
180+
[platform samples]: https://github.com/github/platform-samples/tree/master/api/ruby/delivering-deployments
181+
[Sinatra]: http://www.sinatrarb.com/
182+
[webhook]: /webhooks/
183+
[octokit.rb]: https://github.com/octokit/octokit.rb
184+
[access token]: https://help.github.com/articles/creating-an-access-token-for-command-line-use
185+
[travis api]: https://api.travis-ci.org/docs/
186+
[janky]: https://github.com/github/janky
187+
[heaven]: https://github.com/atmos/heaven
188+
[hubot]: https://github.com/github/hubot
189+
[integrations]: https://github.com/integrations

content/guides/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ stored and documented in our public
1515

1616
Feel free to fork, clone, and improve these guides.
1717

18-
![The Professorcat](/images/professorcat.png)
18+
![The Professorcat](/images/electrocat.png)

content/webhooks/creating/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,3 @@ it's time to set up our local server to test the webhook. Head on over to
5858

5959
[webhook-api]: /v3/repos/hooks/
6060
[hooks-api]: /webhooks/#events
61-

layouts/guides.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ <h2><a href="/v3/">API</a></h2>
2424
<ul>
2525
<li><h3><a href="/guides/">Overview</a></h3></li>
2626
<li><h3><a href="/guides/getting-started/">Getting Started</a></h3></li>
27-
<li><h3><a href="/guides/basics-of-authentication/">Basics of Authentication</a></h3></li>
28-
<li><h3><a href="/guides/rendering-data-as-graphs/">Rendering Data as Graphs</a></h3></li>
29-
<li><h3><a href="/guides/working-with-comments/">Working with Comments</a></h3></li>
30-
<li><h3><a href="/guides/traversing-with-pagination/">Traversing with Pagination</a></h3></li>
27+
<li><h3><a href="/guides/basics-of-authentication/">Basics of authentication</a></h3></li>
28+
<li><h3><a href="/guides/rendering-data-as-graphs/">Rendering data as graphs</a></h3></li>
29+
<li><h3><a href="/guides/working-with-comments/">Working with comments</a></h3></li>
30+
<li><h3><a href="/guides/traversing-with-pagination/">Traversing with pagination</a></h3></li>
31+
<li><h3><a href="/guides/building-a-ci-server/">Building a CI server</a></h3></li>
32+
<li><h3><a href="/guides/delivering-deployments/">Delivering deployments</a></h3></li>
3133
</ul>
3234
</div>
3335

static/images/electrocat.png

25.4 KB
Loading
61.9 KB
Loading

0 commit comments

Comments
 (0)