diff options
| author | Mike McQuaid | 2017-01-16 18:52:47 +0000 |
|---|---|---|
| committer | GitHub | 2017-01-16 18:52:47 +0000 |
| commit | 133e59724a365b9095c4e6f51809fcac48ff1b44 (patch) | |
| tree | 20192edb9985e8f88a34dfb51503bd8238be7764 /Library | |
| parent | c6d7c85d4f7e6f7963222956638d62165a9c8742 (diff) | |
| parent | 12b9cb7f4c5e0927cb1049db4f1aa53fbe371a6d (diff) | |
| download | brew-133e59724a365b9095c4e6f51809fcac48ff1b44.tar.bz2 | |
Merge pull request #1763 from minamijoyo/add-feature-github-release-download-strategy
New feature: Allow download from private GitHub repository
Diffstat (limited to 'Library')
| -rw-r--r-- | Library/Homebrew/download_strategy.rb | 104 | ||||
| -rw-r--r-- | Library/Homebrew/test/download_strategies_test.rb | 76 |
2 files changed, 179 insertions, 1 deletions
diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index 9f9b2abab..bd036067d 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -532,6 +532,110 @@ class S3DownloadStrategy < CurlDownloadStrategy end end +# GitHubPrivateRepositoryDownloadStrategy downloads contents from GitHub +# Private Repository. To use it, add +# ":using => GitHubPrivateRepositoryDownloadStrategy" to the URL section of +# your formula. This download strategy uses GitHub access tokens (in the +# environment variables HOMEBREW_GITHUB_API_TOKEN) to sign the request. This +# strategy is suitable for corporate use just like S3DownloadStrategy, because +# it lets you use a private GttHub repository for internal distribution. It +# works with public one, but in that case simply use CurlDownloadStrategy. +class GitHubPrivateRepositoryDownloadStrategy < CurlDownloadStrategy + require "utils/formatter" + require "utils/github" + + def initialize(name, resource) + super + parse_url_pattern + set_github_token + end + + def parse_url_pattern + url_pattern = %r{https://github.com/([^/]+)/([^/]+)/(\S+)} + unless @url =~ url_pattern + raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Repository." + end + + _, @owner, @repo, @filepath = *@url.match(url_pattern) + end + + def download_url + "https://#{@github_token}@github.com/#{@owner}/#{@repo}/#{@filepath}" + end + + def _fetch + curl download_url, "-C", downloaded_size, "-o", temporary_path + end + + private + + def set_github_token + @github_token = ENV["HOMEBREW_GITHUB_API_TOKEN"] + unless @github_token + raise CurlDownloadStrategyError, "Environmental variable HOMEBREW_GITHUB_API_TOKEN is required." + end + validate_github_repository_access! + end + + def validate_github_repository_access! + # Test access to the repository + GitHub.repository(@owner, @repo) + rescue GitHub::HTTPNotFoundError + # We only handle HTTPNotFoundError here, + # becase AuthenticationFailedError is handled within util/github. + message = <<-EOS.undent + HOMEBREW_GITHUB_API_TOKEN can not access the repository: #{@owner}/#{@repo} + This token may not have permission to access the repository or the url of formula may be incorrect. + EOS + raise CurlDownloadStrategyError, message + end +end + +# GitHubPrivateRepositoryReleaseDownloadStrategy downloads tarballs from GitHub +# Release assets. To use it, add +# ":using => GitHubPrivateRepositoryReleaseDownloadStrategy" to the URL section +# of your formula. This download strategy uses GitHub access tokens (in the +# environment variables HOMEBREW_GITHUB_API_TOKEN) to sign the request. +class GitHubPrivateRepositoryReleaseDownloadStrategy < GitHubPrivateRepositoryDownloadStrategy + def parse_url_pattern + url_pattern = %r{https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(\S+)} + unless @url =~ url_pattern + raise CurlDownloadStrategyError, "Invalid url pattern for GitHub Release." + end + + _, @owner, @repo, @tag, @filename = *@url.match(url_pattern) + end + + def download_url + "https://#{@github_token}@api.github.com/repos/#{@owner}/#{@repo}/releases/assets/#{asset_id}" + end + + def _fetch + # HTTP request header `Accept: application/octet-stream` is required. + # Without this, the GitHub API will respond with metadata, not binary. + curl download_url, "-C", downloaded_size, "-o", temporary_path, "-H", "Accept: application/octet-stream" + end + + private + + def asset_id + @asset_id ||= resolve_asset_id + end + + def resolve_asset_id + release_metadata = fetch_release_metadata + assets = release_metadata["assets"].select { |a| a["name"] == @filename } + raise CurlDownloadStrategyError, "Asset file not found." if assets.empty? + + assets.first["id"] + end + + def fetch_release_metadata + release_url = "https://api.github.com/repos/#{@owner}/#{@repo}/releases/tags/#{@tag}" + GitHub.open(release_url) + end +end + class SubversionDownloadStrategy < VCSDownloadStrategy def initialize(name, resource) super diff --git a/Library/Homebrew/test/download_strategies_test.rb b/Library/Homebrew/test/download_strategies_test.rb index 87218fb12..2b64abbf9 100644 --- a/Library/Homebrew/test/download_strategies_test.rb +++ b/Library/Homebrew/test/download_strategies_test.rb @@ -2,11 +2,12 @@ require "testing_env" require "download_strategy" class ResourceDouble - attr_reader :url, :specs, :version + attr_reader :url, :specs, :version, :mirrors def initialize(url = "http://example.com/foo.tar.gz", specs = {}) @url = url @specs = specs + @mirrors = [] end end @@ -60,6 +61,79 @@ class VCSDownloadStrategyTests < Homebrew::TestCase end end +class GitHubPrivateRepositoryDownloadStrategyTests < Homebrew::TestCase + def setup + resource = ResourceDouble.new("https://github.com/owner/repo/archive/1.1.5.tar.gz") + ENV["HOMEBREW_GITHUB_API_TOKEN"] = "token" + GitHub.stubs(:repository).returns {} + @strategy = GitHubPrivateRepositoryDownloadStrategy.new("foo", resource) + end + + def test_set_github_token + assert_equal "token", @strategy.instance_variable_get(:@github_token) + end + + def test_parse_url_pattern + assert_equal "owner", @strategy.instance_variable_get(:@owner) + assert_equal "repo", @strategy.instance_variable_get(:@repo) + assert_equal "archive/1.1.5.tar.gz", @strategy.instance_variable_get(:@filepath) + end + + def test_download_url + expected = "https://token@github.com/owner/repo/archive/1.1.5.tar.gz" + assert_equal expected, @strategy.download_url + end +end + +class GitHubPrivateRepositoryReleaseDownloadStrategyTests < Homebrew::TestCase + def setup + resource = ResourceDouble.new("https://github.com/owner/repo/releases/download/tag/foo_v0.1.0_darwin_amd64.tar.gz") + ENV["HOMEBREW_GITHUB_API_TOKEN"] = "token" + GitHub.stubs(:repository).returns {} + @strategy = GitHubPrivateRepositoryReleaseDownloadStrategy.new("foo", resource) + end + + def test_parse_url_pattern + assert_equal "owner", @strategy.instance_variable_get(:@owner) + assert_equal "repo", @strategy.instance_variable_get(:@repo) + assert_equal "tag", @strategy.instance_variable_get(:@tag) + assert_equal "foo_v0.1.0_darwin_amd64.tar.gz", @strategy.instance_variable_get(:@filename) + end + + def test_download_url + @strategy.stubs(:resolve_asset_id).returns(456) + expected = "https://token@api.github.com/repos/owner/repo/releases/assets/456" + assert_equal expected, @strategy.download_url + end + + def test_resolve_asset_id + release_metadata = { + "assets" => [ + { + "id" => 123, + "name" => "foo_v0.1.0_linux_amd64.tar.gz", + }, + { + "id" => 456, + "name" => "foo_v0.1.0_darwin_amd64.tar.gz", + }, + ], + } + @strategy.stubs(:fetch_release_metadata).returns(release_metadata) + assert_equal 456, @strategy.send(:resolve_asset_id) + end + + def test_fetch_release_metadata + expected_release_url = "https://api.github.com/repos/owner/repo/releases/tags/tag" + github_mock = MiniTest::Mock.new + github_mock.expect :call, {}, [expected_release_url] + GitHub.stub :open, github_mock do + @strategy.send(:fetch_release_metadata) + end + github_mock.verify + end +end + class GitDownloadStrategyTests < Homebrew::TestCase include FileUtils |
