Merge pull request #10835 from MikeMcQuaid/pr_upload_github_packages

Support uploading and downloading bottles from GitHub packages
This commit is contained in:
Mike McQuaid 2021-03-12 18:07:24 +00:00 committed by GitHub
commit 8726636ffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 0 deletions

View File

@ -4,6 +4,7 @@
require "cli/parser"
require "archive"
require "bintray"
require "github_packages"
require "github_releases"
module Homebrew
@ -33,6 +34,8 @@ module Homebrew
description: "Upload to the specified Internet Archive item (default: `homebrew`)."
flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--github-org=",
description: "Upload to the specified GitHub organisation's GitHub Packages (default: `homebrew`)."
flag "--root-url=",
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
@ -73,6 +76,12 @@ module Homebrew
end
end
def github_packages?(bottles_hash)
@github_packages ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].match? GitHubPackages::URL_REGEX
end
end
def pr_upload
args = pr_upload_args.parse
@ -99,6 +108,8 @@ module Homebrew
"Bintray"
elsif github_releases?(bottles_hash)
"GitHub Releases"
elsif github_packages?(bottles_hash)
"GitHub Packages"
else
odie "Service specified by root_url is not recognized"
end
@ -137,6 +148,10 @@ module Homebrew
elsif github_releases?(bottles_hash)
github_releases = GitHubReleases.new
github_releases.upload_bottles(bottles_hash)
elsif github_packages?(bottles_hash)
github_org = args.github_org || "homebrew"
github_packages = GitHubPackages.new(org: github_org)
github_packages.upload_bottles(bottles_hash)
else
odie "Service specified by root_url is not recognized"
end

View File

@ -13,6 +13,8 @@ require "mechanize/http/content_disposition_parser"
require "utils/curl"
require "github_packages"
# @abstract Abstract superclass for all download strategies.
#
# @api private
@ -527,6 +529,25 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
end
end
# Strategy for downloading a file from an GitHub Packages URL.
#
# @api public
class CurlGitHubPackagesDownloadStrategy < CurlDownloadStrategy
attr_accessor :checksum, :name
private
def _fetch(url:, resolved_url:)
raise "Empty checksum" if checksum.blank?
raise "Empty name" if name.blank?
_, org, repo, = *url.match(GitHubPackages::URL_REGEX)
blob_url = "https://ghcr.io/v2/#{org}/#{repo}/#{name}/blobs/sha256:#{checksum}"
curl_download(blob_url, "--header", "Authorization: Bearer", to: temporary_path)
end
end
# Strategy for downloading a file from an Apache Mirror URL.
#
# @api public
@ -1243,6 +1264,8 @@ class DownloadStrategyDetector
def self.detect_from_url(url)
case url
when GitHubPackages::URL_REGEX
CurlGitHubPackagesDownloadStrategy
when %r{^https?://github\.com/[^/]+/[^/]+\.git$}
GitHubGitDownloadStrategy
when %r{^https?://.+\.git$},

View File

@ -172,6 +172,13 @@ module Homebrew
"\n\n *Note:* Homebrew doesn't require permissions for any of the scopes, but some " \
"developer commands may require additional permissions.",
},
HOMEBREW_GITHUB_PACKAGES_TOKEN: {
description: "Use this GitHub personal access token when accessing the GitHub Packages Registry "\
"(where bottles may be stored).",
},
HOMEBREW_GITHUB_PACKAGES_USER: {
description: "Use this username when accessing the GitHub Packages Registry (where bottles may be stored).",
},
HOMEBREW_GIT_EMAIL: {
description: "Set the Git author and committer email to this value.",
},

View File

@ -0,0 +1,71 @@
# typed: false
# frozen_string_literal: true
require "utils/curl"
require "json"
# GitHub Packages client.
#
# @api private
class GitHubPackages
extend T::Sig
include Context
include Utils::Curl
URL_REGEX = %r{https://ghcr.io/v2/([\w-]+)/([\w-]+)}.freeze
sig { returns(String) }
def inspect
"#<GitHubPackages: org=#{@github_org}>"
end
sig { params(org: T.nilable(String)).void }
def initialize(org: "homebrew")
@github_org = org
raise UsageError, "Must set a GitHub organisation!" unless @github_org
ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] = "1" if @github_org == "homebrew" && !OS.mac?
end
sig { params(bottles_hash: T::Hash[String, T.untyped]).void }
def upload_bottles(bottles_hash)
user = Homebrew::EnvConfig.github_packages_user
token = Homebrew::EnvConfig.github_packages_token
raise UsageError, "HOMEBREW_GITHUB_PACKAGES_USER is unset." if user.blank?
raise UsageError, "HOMEBREW_GITHUB_PACKAGES_TOKEN is unset." if token.blank?
oras = Formula["oras"].opt_bin/"oras" if Formula["oras"].any_version_installed?
oras ||= begin
ohai "Installing `oras` for upload..."
safe_system HOMEBREW_BREW_FILE, "install", "oras"
Formula["oras"].opt_bin/"oras"
end
bottles_hash.each do |formula_name, bottle_hash|
_, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX)
version = bottle_hash["formula"]["pkg_version"]
rebuild = bottle_hash["bottle"]["rebuild"]
bottle_hash["bottle"]["tags"].each do |bottle_tag, tag_hash|
local_file = tag_hash["local_filename"]
odebug "Uploading #{local_file}"
tag = "#{version}.#{bottle_tag}"
tag = "#{tag}.#{rebuild}" if rebuild.positive?
system_command!(oras, verbose: true, print_stdout: true, args: [
"push", "ghcr.io/#{org}/#{repo}/#{formula_name}:#{tag}",
"--username", user,
"--password", token,
"#{local_file}:application/vnd.oci.image.layer.v1.tar+gzip"
])
# TODO: make this package public?
# TODO: make this latest?
end
end
end
end

View File

@ -316,12 +316,16 @@ class Bottle
def fetch(verify_download_integrity: true)
# add the default bottle domain as a fallback mirror
# TODO: this may need adjusted when if we use GitHub Packages by default
if @resource.download_strategy == CurlDownloadStrategy &&
@resource.url.start_with?(Homebrew::EnvConfig.bottle_domain)
fallback_url = @resource.url
.sub(/^#{Regexp.escape(Homebrew::EnvConfig.bottle_domain)}/,
HOMEBREW_BOTTLE_DEFAULT_DOMAIN)
@resource.mirror(fallback_url) if [@resource.url, *@resource.mirrors].exclude?(fallback_url)
elsif @resource.download_strategy == CurlGitHubPackagesDownloadStrategy
@resource.downloader.name = @name
@resource.downloader.checksum = @resource.checksum.hexdigest
end
@resource.fetch(verify_download_integrity: verify_download_integrity)
end

View File

@ -1483,6 +1483,7 @@ _brew_pr_upload() {
--bintray-org
--debug
--dry-run
--github-org
--help
--keep-old
--no-commit

View File

@ -1069,6 +1069,7 @@ __fish_brew_complete_arg 'pr-upload' -l archive-item -d 'Upload to the specified
__fish_brew_complete_arg 'pr-upload' -l bintray-org -d 'Upload to the specified Bintray organisation (default: `homebrew`)'
__fish_brew_complete_arg 'pr-upload' -l debug -d 'Display any debugging information'
__fish_brew_complete_arg 'pr-upload' -l dry-run -d 'Print what would be done rather than doing it'
__fish_brew_complete_arg 'pr-upload' -l github-org -d 'Upload to the specified GitHub organisation\'s GitHub Packages (default: `homebrew`)'
__fish_brew_complete_arg 'pr-upload' -l help -d 'Show this message'
__fish_brew_complete_arg 'pr-upload' -l keep-old -d 'If the formula specifies a rebuild version, attempt to preserve its value in the generated DSL'
__fish_brew_complete_arg 'pr-upload' -l no-commit -d 'Do not generate a new commit before uploading'

View File

@ -1243,6 +1243,7 @@ _brew_pr_upload() {
'--bintray-org[Upload to the specified Bintray organisation (default: `homebrew`)]' \
'--debug[Display any debugging information]' \
'--dry-run[Print what would be done rather than doing it]' \
'--github-org[Upload to the specified GitHub organisation'\''s GitHub Packages (default: `homebrew`)]' \
'--help[Show this message]' \
'--keep-old[If the formula specifies a rebuild version, attempt to preserve its value in the generated DSL]' \
'--no-commit[Do not generate a new commit before uploading]' \

View File

@ -1214,6 +1214,8 @@ Apply the bottle commit and publish bottles to a host.
Upload to the specified Internet Archive item (default: `homebrew`).
* `--bintray-org`:
Upload to the specified Bintray organisation (default: `homebrew`).
* `--github-org`:
Upload to the specified GitHub organisation's GitHub Packages (default: `homebrew`).
* `--root-url`:
Use the specified *`URL`* as the root of the bottle's URL instead of Homebrew's default.
@ -1829,6 +1831,12 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just
*Note:* Homebrew doesn't require permissions for any of the scopes, but some developer commands may require additional permissions.
- `HOMEBREW_GITHUB_PACKAGES_TOKEN`
<br>Use this GitHub personal access token when accessing the GitHub Packages Registry (where bottles may be stored).
- `HOMEBREW_GITHUB_PACKAGES_USER`
<br>Use this username when accessing the GitHub Packages Registry (where bottles may be stored).
- `HOMEBREW_GIT_EMAIL`
<br>Set the Git author and committer email to this value.

View File

@ -1708,6 +1708,10 @@ Upload to the specified Internet Archive item (default: \fBhomebrew\fR)\.
Upload to the specified Bintray organisation (default: \fBhomebrew\fR)\.
.
.TP
\fB\-\-github\-org\fR
Upload to the specified GitHub organisation\'s GitHub Packages (default: \fBhomebrew\fR)\.
.
.TP
\fB\-\-root\-url\fR
Use the specified \fIURL\fR as the root of the bottle\'s URL instead of Homebrew\'s default\.
.
@ -2602,6 +2606,18 @@ Use this personal access token for the GitHub API, for features such as \fBbrew
\fINote:\fR Homebrew doesn\'t require permissions for any of the scopes, but some developer commands may require additional permissions\.
.
.TP
\fBHOMEBREW_GITHUB_PACKAGES_TOKEN\fR
.
.br
Use this GitHub personal access token when accessing the GitHub Packages Registry (where bottles may be stored)\.
.
.TP
\fBHOMEBREW_GITHUB_PACKAGES_USER\fR
.
.br
Use this username when accessing the GitHub Packages Registry (where bottles may be stored)\.
.
.TP
\fBHOMEBREW_GIT_EMAIL\fR
.
.br