dev-cmd/pr-upload: some refactoring

- make the description more generic/correct
- use "internet archive" over "archive"
- move some logic to a new `GitHubReleases` class (for consistency)
- remove some obvious comments
- extract out and move some constants
This commit is contained in:
Mike McQuaid 2021-03-10 16:08:45 +00:00
parent 5240afbbb5
commit 3f00d08544
No known key found for this signature in database
GPG Key ID: 48A898132FD8EE70
10 changed files with 68 additions and 46 deletions

View File

@ -16,6 +16,9 @@ class Archive
class Error < RuntimeError class Error < RuntimeError
end end
URL_PREFIX = "https://archive.org"
S3_DOMAIN = "s3.us.archive.org"
sig { returns(String) } sig { returns(String) }
def inspect def inspect
"#<Archive: item=#{@archive_item}>" "#<Archive: item=#{@archive_item}>"
@ -34,7 +37,7 @@ class Archive
raise UsageError, "HOMEBREW_INTERNET_ARCHIVE_KEY is unset." if key.blank? raise UsageError, "HOMEBREW_INTERNET_ARCHIVE_KEY is unset." if key.blank?
if key.exclude?(":") if key.exclude?(":")
raise UsageError, "Use HOMEBREW_INTERNET_ARCHIVE_KEY=access:secret. See https://archive.org/account/s3.php" raise UsageError, "Use HOMEBREW_INTERNET_ARCHIVE_KEY=access:secret. See #{URL_PREFIX}/account/s3.php"
end end
args += ["--header", "Authorization: AWS #{key}"] args += ["--header", "Authorization: AWS #{key}"]
@ -61,7 +64,7 @@ class Archive
end end
md5_base64 = Digest::MD5.base64digest(local_file.read) md5_base64 = Digest::MD5.base64digest(local_file.read)
url = "https://#{@archive_item}.s3.us.archive.org/#{directory}/#{remote_file}" url = "https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{remote_file}"
args = ["--upload-file", local_file, "--header", "Content-MD5: #{md5_base64}"] args = ["--upload-file", local_file, "--header", "Content-MD5: #{md5_base64}"]
args << "--fail" unless warn_on_error args << "--fail" unless warn_on_error
result = T.unsafe(self).open_api(url, *args) result = T.unsafe(self).open_api(url, *args)
@ -82,7 +85,7 @@ class Archive
formula.downloader.fetch formula.downloader.fetch
filename = ERB::Util.url_encode(formula.downloader.basename) filename = ERB::Util.url_encode(formula.downloader.basename)
destination_url = "https://archive.org/download/#{@archive_item}/#{directory}/#{filename}" destination_url = "#{URL_PREFIX}/download/#{@archive_item}/#{directory}/#{filename}"
odebug "Uploading to #{destination_url}" odebug "Uploading to #{destination_url}"
@ -101,7 +104,7 @@ class Archive
# @return the hash, the empty string (if the file doesn't have a hash), nil (if the file doesn't exist) # @return the hash, the empty string (if the file doesn't have a hash), nil (if the file doesn't exist)
sig { params(directory: String, remote_file: String).returns(T.nilable(String)) } sig { params(directory: String, remote_file: String).returns(T.nilable(String)) }
def remote_md5(directory:, remote_file:) def remote_md5(directory:, remote_file:)
url = "https://#{@archive_item}.s3.us.archive.org/#{directory}/#{remote_file}" url = "https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{remote_file}"
result = curl_output "--fail", "--silent", "--head", "--location", url result = curl_output "--fail", "--silent", "--head", "--location", url
if result.success? if result.success?
result.stdout.match(/^ETag: "(\h{32})"/)&.values_at(1)&.first || "" result.stdout.match(/^ETag: "(\h{32})"/)&.values_at(1)&.first || ""
@ -116,7 +119,7 @@ class Archive
def file_delete_instructions(directory, filename) def file_delete_instructions(directory, filename)
<<~EOS <<~EOS
Run: Run:
curl -X DELETE -H "Authorization: AWS $HOMEBREW_INTERNET_ARCHIVE_KEY" https://#{@archive_item}.s3.us.archive.org/#{directory}/#{filename} curl -X DELETE -H "Authorization: AWS $HOMEBREW_INTERNET_ARCHIVE_KEY" https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{filename}
Or run: Or run:
ia delete #{@archive_item} #{directory}/#{filename} ia delete #{@archive_item} #{directory}/#{filename}
EOS EOS

View File

@ -14,6 +14,7 @@ class Bintray
include Utils::Curl include Utils::Curl
API_URL = "https://api.bintray.com" API_URL = "https://api.bintray.com"
URL_REGEX = %r{^https://[\w-]+\.bintray\.com/}.freeze
class Error < RuntimeError class Error < RuntimeError
end end

View File

@ -4,6 +4,7 @@
require "cli/parser" require "cli/parser"
require "archive" require "archive"
require "bintray" require "bintray"
require "github_releases"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -14,7 +15,7 @@ module Homebrew
def pr_upload_args def pr_upload_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Apply the bottle commit and publish bottles to Bintray or GitHub Releases. Apply the bottle commit and publish bottles to a host.
EOS EOS
switch "--no-publish", switch "--no-publish",
description: "Apply the bottle commit and upload the bottles, but don't publish them." description: "Apply the bottle commit and upload the bottles, but don't publish them."
@ -50,22 +51,22 @@ module Homebrew
end end
end end
def archive?(bottles_hash) def internet_archive?(bottles_hash)
@archive ||= bottles_hash.values.all? do |bottle_hash| @internet_archive ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].start_with? "https://archive.org/" bottle_hash["bottle"]["root_url"].start_with? "#{Archive::URL_PREFIX}/"
end end
end end
def bintray?(bottles_hash) def bintray?(bottles_hash)
@bintray ||= bottles_hash.values.all? do |bottle_hash| @bintray ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].match? %r{^https://[\w-]+\.bintray\.com/} bottle_hash["bottle"]["root_url"].match? Bintray::URL_REGEX
end end
end end
def github_releases?(bottles_hash) def github_releases?(bottles_hash)
@github_releases ||= bottles_hash.values.all? do |bottle_hash| @github_releases ||= bottles_hash.values.all? do |bottle_hash|
root_url = bottle_hash["bottle"]["root_url"] root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX url_match = root_url.match GitHubReleases::URL_REGEX
_, _, _, tag = *url_match _, _, _, tag = *url_match
tag tag
@ -92,7 +93,7 @@ module Homebrew
if args.dry_run? if args.dry_run?
service = service =
if archive?(bottles_hash) if internet_archive?(bottles_hash)
"Internet Archive" "Internet Archive"
elsif bintray?(bottles_hash) elsif bintray?(bottles_hash)
"Bintray" "Bintray"
@ -122,44 +123,20 @@ module Homebrew
safe_system HOMEBREW_BREW_FILE, *audit_args safe_system HOMEBREW_BREW_FILE, *audit_args
end end
if archive?(bottles_hash) if internet_archive?(bottles_hash)
# Handle uploading to the Internet Archive.
archive_item = args.archive_item || "homebrew" archive_item = args.archive_item || "homebrew"
archive = Archive.new(item: archive_item) archive = Archive.new(item: archive_item)
archive.upload_bottles(bottles_hash, archive.upload_bottles(bottles_hash,
warn_on_error: args.warn_on_upload_failure?) warn_on_error: args.warn_on_upload_failure?)
elsif bintray?(bottles_hash) elsif bintray?(bottles_hash)
# Handle uploading to Bintray.
bintray_org = args.bintray_org || "homebrew" bintray_org = args.bintray_org || "homebrew"
bintray = Bintray.new(org: bintray_org) bintray = Bintray.new(org: bintray_org)
bintray.upload_bottles(bottles_hash, bintray.upload_bottles(bottles_hash,
publish_package: !args.no_publish?, publish_package: !args.no_publish?,
warn_on_error: args.warn_on_upload_failure?) warn_on_error: args.warn_on_upload_failure?)
elsif github_releases?(bottles_hash) elsif github_releases?(bottles_hash)
# Handle uploading to GitHub Releases. github_releases = GitHubReleases.new
bottles_hash.each_value do |bottle_hash| github_releases.upload_bottles(bottles_hash)
root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX
_, user, repo, tag = *url_match
# Ensure a release is created.
release = begin
rel = GitHub.get_release user, repo, tag
odebug "Existing GitHub release \"#{tag}\" found"
rel
rescue GitHub::API::HTTPNotFoundError
odebug "Creating new GitHub release \"#{tag}\""
GitHub.create_or_update_release user, repo, tag
end
# Upload bottles as release assets.
bottle_hash["bottle"]["tags"].each_value do |tag_hash|
remote_file = tag_hash["filename"]
local_file = tag_hash["local_filename"]
odebug "Uploading #{remote_file}"
GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file
end
end
else else
odie "Service specified by root_url is not recognized" odie "Service specified by root_url is not recognized"
end end

View File

@ -0,0 +1,44 @@
# typed: false
# frozen_string_literal: true
require "utils/github"
require "json"
# GitHub Releases client.
#
# @api private
class GitHubReleases
extend T::Sig
include Context
include Utils::Curl
URL_REGEX = %r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze
sig { params(bottles_hash: T::Hash[String, T.untyped]).void }
def upload_bottles(bottles_hash)
bottles_hash.each_value do |bottle_hash|
root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match URL_REGEX
_, user, repo, tag = *url_match
# Ensure a release is created.
release = begin
rel = GitHub.get_release user, repo, tag
odebug "Existing GitHub release \"#{tag}\" found"
rel
rescue GitHub::API::HTTPNotFoundError
odebug "Creating new GitHub release \"#{tag}\""
GitHub.create_or_update_release user, repo, tag
end
# Upload bottles as release assets.
bottle_hash["bottle"]["tags"].each_value do |tag_hash|
remote_file = tag_hash["filename"]
local_file = tag_hash["local_filename"]
odebug "Uploading #{remote_file}"
GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file
end
end
end
end

View File

@ -71,8 +71,6 @@ HOMEBREW_PULL_API_REGEX =
%r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze %r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze
HOMEBREW_PULL_OR_COMMIT_URL_REGEX = HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
%r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze %r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze
HOMEBREW_RELEASES_URL_REGEX =
%r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze
require "fileutils" require "fileutils"

View File

@ -12613,7 +12613,6 @@ class Object
HOMEBREW_PRODUCT = ::T.let(nil, ::T.untyped) HOMEBREW_PRODUCT = ::T.let(nil, ::T.untyped)
HOMEBREW_PULL_API_REGEX = ::T.let(nil, ::T.untyped) HOMEBREW_PULL_API_REGEX = ::T.let(nil, ::T.untyped)
HOMEBREW_PULL_OR_COMMIT_URL_REGEX = ::T.let(nil, ::T.untyped) HOMEBREW_PULL_OR_COMMIT_URL_REGEX = ::T.let(nil, ::T.untyped)
HOMEBREW_RELEASES_URL_REGEX = ::T.let(nil, ::T.untyped)
HOMEBREW_REPOSITORY = ::T.let(nil, ::T.untyped) HOMEBREW_REPOSITORY = ::T.let(nil, ::T.untyped)
HOMEBREW_REQUIRED_RUBY_VERSION = ::T.let(nil, ::T.untyped) HOMEBREW_REQUIRED_RUBY_VERSION = ::T.let(nil, ::T.untyped)
HOMEBREW_RUBY_EXEC_ARGS = ::T.let(nil, ::T.untyped) HOMEBREW_RUBY_EXEC_ARGS = ::T.let(nil, ::T.untyped)

View File

@ -1064,7 +1064,7 @@ __fish_brew_complete_arg 'pr-pull' -l warn-on-upload-failure -d 'Warn instead of
__fish_brew_complete_arg 'pr-pull' -l workflows -d 'Retrieve artifacts from the specified workflow (default: `tests.yml`). Can be a comma-separated list to include multiple workflows' __fish_brew_complete_arg 'pr-pull' -l workflows -d 'Retrieve artifacts from the specified workflow (default: `tests.yml`). Can be a comma-separated list to include multiple workflows'
__fish_brew_complete_cmd 'pr-upload' 'Apply the bottle commit and publish bottles to Bintray or GitHub Releases' __fish_brew_complete_cmd 'pr-upload' 'Apply the bottle commit and publish bottles to a host'
__fish_brew_complete_arg 'pr-upload' -l archive-item -d 'Upload to the specified Internet Archive item (default: `homebrew`)' __fish_brew_complete_arg 'pr-upload' -l archive-item -d 'Upload to the specified Internet Archive item (default: `homebrew`)'
__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 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 debug -d 'Display any debugging information'

View File

@ -185,7 +185,7 @@ __brew_internal_commands() {
'pr-automerge:Find pull requests that can be automatically merged using `brew pr-publish`' 'pr-automerge:Find pull requests that can be automatically merged using `brew pr-publish`'
'pr-publish:Publish bottles for a pull request with GitHub Actions' 'pr-publish:Publish bottles for a pull request with GitHub Actions'
'pr-pull:Download and publish bottles, and apply the bottle commit from a pull request with artifacts generated by GitHub Actions' 'pr-pull:Download and publish bottles, and apply the bottle commit from a pull request with artifacts generated by GitHub Actions'
'pr-upload:Apply the bottle commit and publish bottles to Bintray or GitHub Releases' 'pr-upload:Apply the bottle commit and publish bottles to a host'
'prof:Run Homebrew with a Ruby profiler' 'prof:Run Homebrew with a Ruby profiler'
'readall:Import all items from the specified tap, or from all installed taps if none is provided' 'readall:Import all items from the specified tap, or from all installed taps if none is provided'
'reinstall:Uninstall and then reinstall a formula or cask using the same options it was originally installed with, plus any appended options specific to a formula' 'reinstall:Uninstall and then reinstall a formula or cask using the same options it was originally installed with, plus any appended options specific to a formula'

View File

@ -1198,7 +1198,7 @@ Requires write access to the repository.
### `pr-upload` [*`options`*] ### `pr-upload` [*`options`*]
Apply the bottle commit and publish bottles to Bintray or GitHub Releases. Apply the bottle commit and publish bottles to a host.
* `--no-publish`: * `--no-publish`:
Apply the bottle commit and upload the bottles, but don't publish them. Apply the bottle commit and upload the bottles, but don't publish them.

View File

@ -1677,7 +1677,7 @@ Retrieve artifacts from the specified workflow (default: \fBtests\.yml\fR)\. Can
Comma\-separated list of workflows which can be ignored if they have not been run\. Comma\-separated list of workflows which can be ignored if they have not been run\.
. .
.SS "\fBpr\-upload\fR [\fIoptions\fR]" .SS "\fBpr\-upload\fR [\fIoptions\fR]"
Apply the bottle commit and publish bottles to Bintray or GitHub Releases\. Apply the bottle commit and publish bottles to a host\.
. .
.TP .TP
\fB\-\-no\-publish\fR \fB\-\-no\-publish\fR