lib/ridley/sandbox_uploader.rb
module Ridley
# @api private
class SandboxUploader
class << self
# Return the checksum of the contents of the file at the given filepath
#
# @param [String] io
# a filepath or an IO
# @param [Digest::Base] digest
#
# @return [String]
# the binary checksum of the contents of the file
def checksum(io, digest = Digest::MD5.new)
while chunk = io.read(1024 * 8)
digest.update(chunk)
end
digest.hexdigest
end
# Return a base64 encoded checksum of the contents of the given file. This is the expected
# format of sandbox checksums given to the Chef Server.
#
# @param [String] io
# a filepath or an IO
#
# @return [String]
# a base64 encoded checksum
def checksum64(io)
Base64.encode64([checksum(io)].pack("H*")).strip
end
end
include Celluloid
attr_reader :client_name
attr_reader :client_key
attr_reader :options
def initialize(client_name, client_key, options = {})
@client_name = client_name
@client_key = client_key
@options = options
end
# Upload one file into the sandbox for the given checksum id
#
# @param [Ridley::SandboxObject] sandbox
# @param [String] chk_id
# checksum of the file being uploaded
# @param [String, #read] file
# a filepath or an IO
#
# @raise [Errors::ChecksumMismatch]
# if the given file does not match the expected checksum
#
# @return [Hash, nil]
def upload(sandbox, chk_id, file)
checksum = sandbox.checksum(chk_id)
unless checksum[:needs_upload]
return nil
end
io = file.respond_to?(:read) ? file : File.new(file, 'rb')
calculated_checksum = self.class.checksum64(io)
expected_checksum = Base64.encode64([chk_id].pack('H*')).strip
unless calculated_checksum == expected_checksum
raise Errors::ChecksumMismatch,
"Error uploading #{chk_id}. Expected #{expected_checksum} but got #{calculated_checksum}"
end
headers = {
'Content-Type' => 'application/x-binary',
'content-md5' => calculated_checksum
}
url = URI(checksum[:url])
upload_path = url.path
url.path = ""
# versions prior to OSS Chef 11 will strip the port to upload the file to in the checksum
# url returned. This will ensure we are uploading to the proper location.
if sandbox.send(:resource).connection.foss?
url.port = URI(sandbox.send(:resource).connection.server_url).port
end
begin
io.rewind
Faraday.new(url, self.options) do |c|
c.response :chef_response
c.response :follow_redirects
c.request :chef_auth, self.client_name, self.client_key
c.adapter :httpclient
end.put(upload_path, io.read, headers)
rescue Ridley::Errors::HTTPError => ex
abort(ex)
end
end
end
end