Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bintray: check remote file hash prior to upload #8905

Merged
merged 1 commit into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 57 additions & 38 deletions Library/Homebrew/bintray.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,69 +131,88 @@ def package_exists?(repo:, package:)
end
end

def file_published?(repo:, remote_file:)
# 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}"
begin
curl "--fail", "--silent", "--head", "--output", "/dev/null", url
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
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
true
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}"
if file_published? repo: bintray_repo, remote_file: filename
already_published = "#{filename} is already published."
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)
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
#{already_published}
Please remove it manually from:
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}
#{filename} is already published with a mismatched hash!
Expected: #{sha256}
Actual: #{result}
EOS
raise Error, failed_message unless warn_on_error
raise Error, "#{failed_message}#{delete_instructions}" unless warn_on_error

opoo already_published
next
opoo failed_message
end

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)
end
next unless publish_package

bottle_count = bottle_hash["bottle"]["tags"].length
odebug "Publishing #{@bintray_org}/#{bintray_repo}/#{bintray_package}/#{version}"
publish(repo: bintray_repo,
package: bintray_package,
Expand Down
10 changes: 5 additions & 5 deletions Library/Homebrew/test/bintray_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
ENV["HOMEBREW_BINTRAY_KEY"] = "deadbeef"
end

describe "::file_published?" do
describe "::remote_checksum" do
it "detects a published file" do
results = bintray.file_published?(repo: "bottles", remote_file: "hello-2.10.catalina.bottle.tar.gz")
expect(results).to be true
hash = bintray.remote_checksum(repo: "bottles", remote_file: "hello-2.10.catalina.bottle.tar.gz")
expect(hash).to eq("449de5ea35d0e9431f367f1bb34392e450f6853cdccdc6bd04e6ad6471904ddb")
end

it "fails on a non-existant file" do
results = bintray.file_published?(repo: "bottles", remote_file: "my-fake-bottle-1.0.snow_hyena.tar.gz")
expect(results).to be false
hash = bintray.remote_checksum(repo: "bottles", remote_file: "my-fake-bottle-1.0.snow_hyena.tar.gz")
expect(hash).to be nil
end
end

Expand Down