{"meta":{"title":"Building a CI server","intro":"Build your own CI system using the Status API.","product":"REST API","breadcrumbs":[{"href":"/en/enterprise-server@3.22/rest","title":"REST API"},{"href":"/en/enterprise-server@3.22/rest/guides","title":"Guides"},{"href":"/en/enterprise-server@3.22/rest/guides/building-a-ci-server","title":"Building a CI server"}],"documentType":"article"},"body":"# Building a CI server\n\nBuild your own CI system using the Status API.\n\nYou can use the REST API to tie together commits with\na testing service, so that every push you make can be tested and represented\nin a GitHub pull request. For more information about the relevant endpoints, see [REST API endpoints for commit statuses](/en/enterprise-server@3.22/rest/commits/statuses).\n\nThis guide will use that API to demonstrate a setup that you can use.\nIn our scenario, we will:\n\n* Run our CI suite when a Pull Request is opened (we'll set the CI status to pending).\n* When the CI is finished, we'll set the Pull Request's status accordingly.\n\nOur CI system and host server will be figments of our imagination. They could be\nTravis, Jenkins, or something else entirely. The crux of this guide will be setting up\nand configuring the server managing the communication.\n\nIf you haven't already, [download `ngrok`](https://ngrok.com/), and learn how\nto [use it](https://ngrok.com/docs/getting-started/). We find it to be a very useful tool for exposing local\napplications to the internet.\n\nNote: you can download the complete source code for this project\n[from the platform-samples repo](https://github.com/github/platform-samples/tree/master/api/ruby/building-a-ci-server).\n\n## Writing your server\n\nWe'll write a quick Sinatra app to prove that our local connections are working.\nLet's start with this:\n\n```ruby\nrequire 'sinatra'\nrequire 'json'\n\npost '/event_handler' do\n  payload = JSON.parse(params[:payload])\n  \"Well, it worked!\"\nend\n```\n\n(If you're unfamiliar with how Sinatra works, we recommend [reading the Sinatra guide](http://www.sinatrarb.com/).)\n\nStart this server up. By default, Sinatra starts on port `4567`, so you'll want\nto configure `ngrok` to start listening for that, too.\n\nIn order for this server to work, we'll need to set a repository up with a webhook. The webhook should be configured to fire whenever a pull request is created, or merged.\n\nGo ahead and create a repository you're comfortable playing around in. Might we suggest [@octocat's Spoon/Knife repository](https://github.com/octocat/Spoon-Knife)?\n\nAfter that, you'll create a new webhook in your repository, feeding it the URL that `ngrok` gave you, and choosing `application/x-www-form-urlencoded` as the content type.\n\nClick **Update webhook**. You should see a body response of `Well, it worked!`.\nGreat! Click on **Let me select individual events**, and select the following:\n\n* Status\n* Pull Request\n\nThese are the events GitHub will send to our server whenever the relevant action\noccurs. Let's update our server to *just* handle the Pull Request scenario right now:\n\n```ruby\npost '/event_handler' do\n  @payload = JSON.parse(params[:payload])\n\n  case request.env['HTTP_X_GITHUB_EVENT']\n  when \"pull_request\"\n    if @payload[\"action\"] == \"opened\"\n      process_pull_request(@payload[\"pull_request\"])\n    end\n  end\nend\n\nhelpers do\n  def process_pull_request(pull_request)\n    puts \"It's #{pull_request['title']}\"\n  end\nend\n```\n\nWhat's going on? Every event that GitHub sends out attached a `X-GitHub-Event`\nHTTP header. We'll only care about the PR events for now. From there, we'll\ntake the payload of information, and return the title field. In an ideal scenario,\nour server would be concerned with every time a pull request is updated, not just\nwhen it's opened. That would make sure that every new push passes the CI tests.\nBut for this demo, we'll just worry about when it's opened.\n\nTo test out this proof-of-concept, make some changes in a branch in your test\nrepository, and open a pull request. Your server should respond accordingly!\n\n## Working with statuses\n\nWith our server in place, we're ready to start our first requirement, which is\nsetting (and updating) CI statuses. Note that at any time you update your server,\nyou can click **Redeliver** to send the same payload. There's no need to make a\nnew pull request every time you make a change!\n\nSince we're interacting with the GitHub API, we'll use [Octokit.rb](https://github.com/octokit/octokit.rb)\nto manage our interactions. We'll configure that client with\n[a personal access token](/en/enterprise-server@3.22/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens):\n\n```ruby\n# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!\n# Instead, set and test environment variables, like below\nACCESS_TOKEN = ENV['MY_PERSONAL_TOKEN']\n\nbefore do\n  @client ||= Octokit::Client.new(:access_token => ACCESS_TOKEN)\nend\n```\n\nAfter that, we'll just need to update the pull request on GitHub to make clear\nthat we're processing on the CI:\n\n```ruby\ndef process_pull_request(pull_request)\n  puts \"Processing pull request...\"\n  @client.create_status(pull_request['base']['repo']['full_name'], pull_request['head']['sha'], 'pending')\nend\n```\n\nWe're doing three very basic things here:\n\n* We're looking up the full name of the repository\n* We're looking up the last SHA of the pull request\n* We're setting the status to \"pending\"\n\nThat's it! From here, you can run whatever process you need to in order to execute\nyour test suite. Maybe you're going to pass off your code to Jenkins, or call\non another web service via its API, like [Travis](https://api.travis-ci.com/docs/). After that, you'd\nbe sure to update the status once more. In our example, we'll just set it to `\"success\"`:\n\n```ruby\ndef process_pull_request(pull_request)\n  @client.create_status(pull_request['base']['repo']['full_name'], pull_request['head']['sha'], 'pending')\n  sleep 2 # do busy work...\n  @client.create_status(pull_request['base']['repo']['full_name'], pull_request['head']['sha'], 'success')\n  puts \"Pull request processed!\"\nend\n```\n\n## Conclusion\n\nAt GitHub, we've used a version of [Janky](https://github.com/github/janky) to manage our CI for years.\nThe basic flow is essentially the exact same as the server we've built above.\nAt GitHub, we:\n\n* Fire to Jenkins when a pull request is created or updated (via Janky)\n* Wait for a response on the state of the CI\n* If the code is green, we merge the pull request\n\nAll of this communication is funneled back to our chat rooms. You don't need to\nbuild your own CI setup to use this example.\nYou can always rely on [GitHub integrations](https://github.com/integrations)."}