require 'rest_client' require 'chef/cookbook_loader' require 'chef/checksum_cache' require 'chef/sandbox' require 'chef/cookbook_version' require 'chef/cookbook/syntax_check' require 'chef/cookbook/file_system_file_vendor' class Chef class CookbookUploader class << self def upload_cookbook(cookbook) Chef::Log.info("Saving #{cookbook.name}") rest = Chef::REST.new(Chef::Config[:chef_server_url]) # Syntax Check validate_cookbook(cookbook) # Generate metadata.json from metadata.rb build_metadata(cookbook) # generate checksums of cookbook files and create a sandbox checksum_files = cookbook.checksums checksums = checksum_files.inject({}){|memo,elt| memo[elt.first]=nil ; memo} new_sandbox = rest.post_rest("sandboxes", { :checksums => checksums }) Chef::Log.info("Uploading files") # upload the new checksums and commit the sandbox new_sandbox['checksums'].each do |checksum, info| if info['needs_upload'] == true Chef::Log.info("Uploading #{checksum_files[checksum]} (checksum hex = #{checksum}) to #{info['url']}") # Checksum is the hexadecimal representation of the md5, # but we need the base64 encoding for the content-md5 # header checksum64 = Base64.encode64([checksum].pack("H*")).strip timestamp = Time.now.utc.iso8601 file_contents = File.read(checksum_files[checksum]) # TODO - 5/28/2010, cw: make signing and sending the request streaming sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object( :http_method => :put, :path => URI.parse(info['url']).path, :body => file_contents, :timestamp => timestamp, :user_id => rest.client_name ) headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' } headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key))) begin RestClient::Resource.new(info['url'], :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents) rescue RestClient::Exception => e Chef::Log.error("Upload failed: #{e.message}\n#{e.response.body}") raise end else Chef::Log.debug("#{checksum_files[checksum]} has not changed") end end sandbox_url = new_sandbox['uri'] Chef::Log.debug("Committing sandbox") # Retry if S3 is claims a checksum doesn't exist (the eventual # in eventual consistency) retries = 0 begin rest.put_rest(sandbox_url, {:is_completed => true}) rescue Net::HTTPServerException => e if e.message =~ /^400/ && (retries += 1) <= 5 sleep 2 retry else raise end end # files are uploaded, so save the manifest cookbook.save Chef::Log.info("Upload complete!") end def build_metadata(cookbook) Chef::Log.debug("Generating metadata") # FIXME: This knife command should be factored out into a # library for use here kcm = Chef::Knife::CookbookMetadata.new kcm.config[:cookbook_path] = Chef::Config[:cookbook_path] kcm.name_args = [ cookbook.name.to_s ] kcm.run cookbook.reload_metadata! end def validate_cookbook(cookbook) syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook.name, @user_cookbook_path) Chef::Log.info("Validating ruby files") exit(1) unless syntax_checker.validate_ruby_files Chef::Log.info("Validating templates") exit(1) unless syntax_checker.validate_templates Chef::Log.info("Syntax OK") true end end end end