├── README.md ├── bin ├── compile ├── detect └── release ├── lib └── github.rb └── support └── netrc /README.md: -------------------------------------------------------------------------------- 1 | Heroku buildpack: GitHub private repo access via ~/.netrc 2 | =================================== 3 | 4 | This buildpack uses a GitHub OAuth2 token exposed as `GITHUB_AUTH_TOKEN` 5 | to resolve private repository URLs without putting a specific username 6 | or password in the URLs saved in local files (e.g. `package.json`). 7 | 8 | See [Easier builds and deployments using Git over HTTPS and 9 | OAuth][github-builds] and [GitHub OAuth — Non-web Application Flow][github-oauth] for more detail. Also, you may want to choose a user with read-only access. 10 | 11 | If you use this in conjunction with the `labs:pipeline` feature of Heroku, you may 12 | avoid setting the `GITHUB_AUTH_TOKEN` environment variable on your test & prod apps, 13 | and instead only set it on the app where you push your code & which runs the buildpack. 14 | 15 | [github-builds]: https://github.com/blog/1270-easier-builds-and-deployments-using-git-over-https-and-oauth 16 | [github-oauth]: http://developer.github.com/v3/oauth/#create-a-new-authorization 17 | 18 | Requirements 19 | ------------ 20 | 21 | You'll need to make a GitHub authorization token. Here's the `curl` command you can use. 22 | 23 | ```console 24 | $ curl -u 'my-read-only-user' -d '{"scopes":["repo"],"note":"GITHUB_AUTH_TOKEN for Heroku deplyoments","note_url":"https://github.com/timshadel/heroku-buildpack-github-netrc"}' https://api.github.com/authorizations # GitHub API call 25 | Enter host password for user 'username': [type password] 26 | 27 | { 28 | "scopes": [ 29 | "repo" 30 | ], 31 | "token": "your_token", 32 | "app": { 33 | "url": "http://developer.github.com/v3/oauth/#oauth-authorizations-api", 34 | "name": "Help example (API)" 35 | }, 36 | "url": "https://api.github.com/authorizations/123456", 37 | "note": "GITHUB_AUTH_TOKEN for Heroku deployments.", 38 | "note_url": "https://github.com/timshadel/heroku-buildpack-github-netrc", 39 | "id": 123456, 40 | } 41 | ``` 42 | 43 | This token may be revoked at any time by visiting the [Applications area][github-apps] 44 | of your GitHub account. You'll see the `note` linked to the `note_url` and the revoke 45 | button right next to it. 46 | 47 | You may also create a new token using the GitHub UI; follow the instructions in the [GitHub OAuth help article][github-oauth-help] and ensure your token has the "`repo`" scope. 48 | 49 | [github-apps]: https://github.com/settings/applications 50 | [github-oauth-help]: https://help.github.com/articles/creating-an-oauth-token-for-command-line-use 51 | 52 | Usage 53 | ----- 54 | 55 | First, make sure your app already has a buildpack set: 56 | 57 | $ heroku buildpacks 58 | 59 | If this does not output an existing buildpack, follow the instructions at https://devcenter.heroku.com/articles/buildpacks 60 | 61 | Next, prepend this buildpack to your list of buildpacks, so it runs before your app is built: 62 | 63 | $ heroku buildpacks:add -i 1 https://github.com/timshadel/heroku-buildpack-github-netrc.git 64 | 65 | Set your GitHub auth token: 66 | 67 | $ heroku config:set GITHUB_AUTH_TOKEN= 68 | 69 | Deploy your app: 70 | 71 | ```console 72 | $ git push heroku master # push your changes to Heroku 73 | 74 | ...git output... 75 | 76 | -----> Fetching custom git buildpack... done 77 | -----> Multipack app detected 78 | =====> Downloading Buildpack: https://github.com/timshadel/heroku-buildpack-github-netrc.git 79 | =====> Detected Framework: github-netrc 80 | Generated .netrc & .curlrc files (available only at build-time) 81 | GitHub User: my-read-only-user 82 | Authorization: GITHUB_AUTH_TOKEN for Heroku deplyoments (private repo access) 83 | Organizations: my-org, another-org 84 | ... 85 | ``` 86 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'erb' 4 | require 'fileutils' 5 | require "#{File.join(File.dirname(__FILE__), '..', 'lib', 'github')}" 6 | 7 | build_dir = ARGV[0] 8 | cache_dir = ARGV[1] 9 | env_dir = ARGV[2] 10 | 11 | github_token = if env_dir 12 | token_file = File.join(env_dir, 'GITHUB_AUTH_TOKEN') 13 | File.open(token_file, &:read) if File.exist? token_file 14 | else 15 | # Legacy user-env-compile support 16 | # TODO: Remove once deprecation is complete 17 | ENV['GITHUB_AUTH_TOKEN'] 18 | end 19 | 20 | if github_token.nil? 21 | puts " !!!! GITHUB_AUTH_TOKEN not set" 22 | puts " !!!! Try `heroku config:add GITHUB_AUTH_TOKEN=`" 23 | puts " !!!! See http://developer.github.com/v3/oauth/#non-web-application-flow" 24 | exit 1 25 | end 26 | 27 | netrc_template = File.read "#{File.join(File.dirname(__FILE__), '..', 'support', 'netrc')}" 28 | 29 | ## 30 | # Supply `curl` on the BUILD machine (not in slug) with token to access private GitHub repos 31 | File.open "#{ENV['HOME']}/.netrc", "w+", 0600 do |f| 32 | # The ../support/netrc template relies on the `github_token` variable above 33 | f.puts ERB.new(netrc_template).result 34 | end 35 | 36 | ## 37 | # Tell `curl` on the BUILD machine (not in slug) to use ~/.netrc 38 | File.open "#{ENV['HOME']}/.curlrc", "w+", 0600 do |f| 39 | f.puts "--netrc-optional" 40 | end 41 | 42 | if valid_login? github_token 43 | puts " Generated .netrc & .curlrc files (available only at build-time)" 44 | 45 | ## 46 | # Remove $GITHUB_AUTH_TOKEN from runtime environment at slug boot time. 47 | FileUtils.mkdir_p "#{build_dir}/.profile.d" 48 | File.open "#{build_dir}/.profile.d/netrc.sh", "w+", 0600 do |f| 49 | f.puts "unset GITHUB_AUTH_TOKEN" 50 | end 51 | 52 | ## 53 | # Print out user information 54 | puts user_block(github_token) 55 | else 56 | puts " !!!! No valid GitHub user found with GITHUB_AUTH_TOKEN: '#{github_token}'" 57 | exit 1 58 | end 59 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If someone asks for this buildpack, it's got to be in the 4 | # context of another buildpack, like Multipack. Therefore, 5 | # we're best able to communicate the absence of the needed 6 | # environment variable if we fail during the 'compile' task. 7 | echo "github-netrc" 8 | exit 0 -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f $1/failed ] 4 | then 5 | exit 1 6 | else 7 | echo "--- {}" 8 | fi -------------------------------------------------------------------------------- /lib/github.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | def valid_login? token 4 | login(token) != "error" 5 | end 6 | 7 | def login token 8 | @login ||= github_user_login(token) 9 | end 10 | 11 | def github_user_login token 12 | json = `curl -H "Authorization: Bearer #{token}" -s https://api.github.com/user` rescue "{}" 13 | user = JSON.parse json rescue {} 14 | user["login"] || "error" 15 | end 16 | 17 | def github_user_orgs token 18 | json = `curl -H "Authorization: Bearer #{token}" -s https://api.github.com/user/orgs` rescue "[]" 19 | orgs = JSON.parse json rescue [] 20 | orgs.map {|o| o["login"].downcase }.sort 21 | end 22 | 23 | def user_block token 24 | <<-USER 25 | GitHub User: #{login(token)} 26 | Organizations: #{github_user_orgs(token).join(", ")} 27 | USER 28 | end 29 | -------------------------------------------------------------------------------- /support/netrc: -------------------------------------------------------------------------------- 1 | machine github.com login <%= github_token %> password x-oauth-basic 2 | --------------------------------------------------------------------------------