mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

Previously this would only warn on errors for certain Bintray API requests like publishing. This should now also warn on error for curl POST uploads (if the appropriate flag has been passed).
246 lines
8.3 KiB
Ruby
246 lines
8.3 KiB
Ruby
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
require "utils/curl"
|
|
require "json"
|
|
|
|
# Bintray API client.
|
|
#
|
|
# @api private
|
|
class Bintray
|
|
include Context
|
|
|
|
API_URL = "https://api.bintray.com"
|
|
|
|
class Error < RuntimeError
|
|
end
|
|
|
|
def inspect
|
|
"#<Bintray: org=#{@bintray_org}>"
|
|
end
|
|
|
|
def initialize(org: "homebrew")
|
|
@bintray_org = org
|
|
|
|
raise UsageError, "Must set a Bintray organisation!" unless @bintray_org
|
|
|
|
ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] = "1" if @bintray_org == "homebrew" && !OS.mac?
|
|
end
|
|
|
|
def open_api(url, *extra_curl_args, auth: true)
|
|
args = extra_curl_args
|
|
|
|
if auth
|
|
raise UsageError, "HOMEBREW_BINTRAY_USER is unset." unless (user = Homebrew::EnvConfig.bintray_user)
|
|
raise UsageError, "HOMEBREW_BINTRAY_KEY is unset." unless (key = Homebrew::EnvConfig.bintray_key)
|
|
|
|
args += ["--user", "#{user}:#{key}"]
|
|
end
|
|
|
|
curl(*args, url,
|
|
print_stdout: false,
|
|
secrets: key)
|
|
end
|
|
|
|
def upload(local_file, repo:, package:, version:, remote_file:, sha256: nil, warn_on_error: false)
|
|
unless File.exist? local_file
|
|
msg = "#{local_file} for upload doesn't exist!"
|
|
raise Error, msg unless warn_on_error
|
|
|
|
# Warn and return early here since we know this upload is going to fail.
|
|
opoo msg
|
|
return
|
|
end
|
|
|
|
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/#{remote_file}"
|
|
args = ["--upload-file", local_file]
|
|
args += ["--header", "X-Checksum-Sha2: #{sha256}"] unless sha256.blank?
|
|
args << "--fail" unless warn_on_error
|
|
result = open_api(url, *args)
|
|
|
|
json = JSON.parse(result.stdout)
|
|
if json["message"] != "success"
|
|
msg = "Bottle upload failed: #{json["message"]}"
|
|
raise msg unless warn_on_error
|
|
|
|
opoo msg
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def publish(repo:, package:, version:, file_count:, warn_on_error: false)
|
|
url = "#{API_URL}/content/#{@bintray_org}/#{repo}/#{package}/#{version}/publish"
|
|
upload_args = %w[--request POST]
|
|
upload_args << "--fail" unless warn_on_error
|
|
result = open_api(url, *upload_args)
|
|
json = JSON.parse(result.stdout)
|
|
if file_count.present? && json["files"] != file_count
|
|
message = "Bottle publish failed: expected #{file_count} bottles, but published #{json["files"]} instead."
|
|
raise message unless warn_on_error
|
|
|
|
opoo message
|
|
end
|
|
|
|
odebug "Published #{json["files"]} bottles"
|
|
result
|
|
end
|
|
|
|
def official_org?(org: @bintray_org)
|
|
%w[homebrew linuxbrew].include? org
|
|
end
|
|
|
|
def stable_mirrored?(url)
|
|
headers, = curl_output("--connect-timeout", "15", "--location", "--head", url)
|
|
status_code = headers.scan(%r{^HTTP/.* (\d+)}).last.first
|
|
status_code.start_with?("2")
|
|
end
|
|
|
|
def mirror_formula(formula, repo: "mirror", publish_package: false, warn_on_error: false)
|
|
package = Utils::Bottles::Bintray.package formula.name
|
|
|
|
create_package(repo: repo, package: package) unless package_exists?(repo: repo, package: package)
|
|
|
|
formula.downloader.fetch
|
|
|
|
version = ERB::Util.url_encode(formula.pkg_version)
|
|
filename = ERB::Util.url_encode(formula.downloader.basename)
|
|
destination_url = "https://dl.bintray.com/#{@bintray_org}/#{repo}/#{filename}"
|
|
|
|
odebug "Uploading to #{destination_url}"
|
|
|
|
upload(
|
|
formula.downloader.cached_location,
|
|
repo: repo,
|
|
package: package,
|
|
version: version,
|
|
sha256: formula.stable.checksum,
|
|
remote_file: filename,
|
|
warn_on_error: warn_on_error,
|
|
)
|
|
return destination_url unless publish_package
|
|
|
|
odebug "Publishing #{@bintray_org}/#{repo}/#{package}/#{version}"
|
|
publish(repo: repo, package: package, version: version, file_count: 1, warn_on_error: warn_on_error)
|
|
|
|
destination_url
|
|
end
|
|
|
|
def create_package(repo:, package:, **extra_data_args)
|
|
url = "#{API_URL}/packages/#{@bintray_org}/#{repo}"
|
|
data = { name: package, public_download_numbers: true }
|
|
data[:public_stats] = official_org?
|
|
data.merge! extra_data_args
|
|
open_api url, "--header", "Content-Type: application/json", "--request", "POST", "--data", data.to_json
|
|
end
|
|
|
|
def package_exists?(repo:, package:)
|
|
url = "#{API_URL}/packages/#{@bintray_org}/#{repo}/#{package}"
|
|
begin
|
|
open_api url, "--fail", "--silent", "--output", "/dev/null", auth: false
|
|
rescue ErrorDuringExecution => e
|
|
stderr = e.output
|
|
.select { |type,| type == :stderr }
|
|
.map { |_, line| line }
|
|
.join
|
|
raise if e.status.exitstatus != 22 && !stderr.include?("404 Not Found")
|
|
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
# Gets the SHA-256 checksum of the specified remote file.
|
|
# Returns the empty string if the file exists but doesn't have a checksum.
|
|
# Returns nil if the file doesn't exist.
|
|
def remote_checksum(repo:, remote_file:)
|
|
url = "https://dl.bintray.com/#{@bintray_org}/#{repo}/#{remote_file}"
|
|
result = curl_output "--fail", "--silent", "--head", url
|
|
if result.success?
|
|
result.stdout.match(/^X-Checksum-Sha2:\s+(\h{64})\b/i)&.values_at(1)&.first || ""
|
|
else
|
|
raise Error if result.status.exitstatus != 22 && !result.stderr.include?("404 Not Found")
|
|
|
|
nil
|
|
end
|
|
end
|
|
|
|
def file_delete_instructions(bintray_repo, bintray_package, filename)
|
|
<<~EOS
|
|
Remove this file manually in your web browser:
|
|
https://bintray.com/#{@bintray_org}/#{bintray_repo}/#{bintray_package}/view#files
|
|
Or run:
|
|
curl -X DELETE -u $HOMEBREW_BINTRAY_USER:$HOMEBREW_BINTRAY_KEY \\
|
|
https://api.bintray.com/content/#{@bintray_org}/#{bintray_repo}/#{filename}
|
|
EOS
|
|
end
|
|
|
|
def upload_bottles(bottles_hash, publish_package: false, warn_on_error: false)
|
|
formula_packaged = {}
|
|
|
|
bottles_hash.each do |formula_name, bottle_hash|
|
|
version = ERB::Util.url_encode(bottle_hash["formula"]["pkg_version"])
|
|
bintray_package = bottle_hash["bintray"]["package"]
|
|
bintray_repo = bottle_hash["bintray"]["repository"]
|
|
bottle_count = bottle_hash["bottle"]["tags"].length
|
|
|
|
bottle_hash["bottle"]["tags"].each do |_tag, tag_hash|
|
|
filename = tag_hash["filename"] # URL encoded in Bottle::Filename#bintray
|
|
sha256 = tag_hash["sha256"]
|
|
delete_instructions = file_delete_instructions(bintray_repo, bintray_package, filename)
|
|
|
|
odebug "Checking remote file #{@bintray_org}/#{bintray_repo}/#{filename}"
|
|
result = remote_checksum(repo: bintray_repo, remote_file: filename)
|
|
|
|
case result
|
|
when nil
|
|
# File doesn't exist.
|
|
if !formula_packaged[formula_name] && !package_exists?(repo: bintray_repo, package: bintray_package)
|
|
odebug "Creating package #{@bintray_org}/#{bintray_repo}/#{bintray_package}"
|
|
create_package repo: bintray_repo, package: bintray_package
|
|
formula_packaged[formula_name] = true
|
|
end
|
|
|
|
odebug "Uploading #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}/#{filename}"
|
|
upload(tag_hash["local_filename"],
|
|
repo: bintray_repo,
|
|
package: bintray_package,
|
|
version: version,
|
|
remote_file: filename,
|
|
sha256: sha256,
|
|
warn_on_error: warn_on_error)
|
|
when sha256
|
|
# File exists, checksum matches.
|
|
odebug "#{filename} is already published with matching hash."
|
|
bottle_count -= 1
|
|
when ""
|
|
# File exists, but can't find checksum
|
|
failed_message = "#{filename} is already published!"
|
|
raise Error, "#{failed_message}\n#{delete_instructions}" unless warn_on_error
|
|
|
|
opoo failed_message
|
|
else
|
|
# File exists, but checksum either doesn't exist or is mismatched.
|
|
failed_message = <<~EOS
|
|
#{filename} is already published with a mismatched hash!
|
|
Expected: #{sha256}
|
|
Actual: #{result}
|
|
EOS
|
|
raise Error, "#{failed_message}#{delete_instructions}" unless warn_on_error
|
|
|
|
opoo failed_message
|
|
end
|
|
end
|
|
next unless publish_package
|
|
|
|
odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}"
|
|
publish(repo: bintray_repo,
|
|
package: bintray_package,
|
|
version: version,
|
|
file_count: bottle_count,
|
|
warn_on_error: warn_on_error)
|
|
end
|
|
end
|
|
end
|