Class: Chef::CookbookSiteStreamingUploader
- Defined in:
- lib/chef/cookbook_site_streaming_uploader.rb
Overview
Chef::CookbookSiteStreamingUploader
A streaming multipart HTTP upload implementation. Used to upload cookbooks (in tarball form) to cookbooks.opscode.com
inspired by stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
Defined Under Namespace
Classes: MultipartStream, StreamPart, StringPart
Constant Summary collapse
Class Method Summary collapse
- .create_build_dir(cookbook) ⇒ Object
- .make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
- .post(to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
- .put(to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
Class Method Details
.create_build_dir(cookbook) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/chef/cookbook_site_streaming_uploader.rb', line 37 def create_build_dir(cookbook) tmp_cookbook_path = Tempfile.new("chef-#{cookbook.name}-build") tmp_cookbook_path.close tmp_cookbook_dir = tmp_cookbook_path.path File.unlink(tmp_cookbook_dir) FileUtils.mkdir_p(tmp_cookbook_dir) Chef::Log.debug("Staging at #{tmp_cookbook_dir}") checksums_to_on_disk_paths = cookbook.checksums Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| cookbook.manifest[segment].each do |manifest_record| path_in_cookbook = manifest_record[:path] on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]] dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook) FileUtils.mkdir_p(File.dirname(dest)) Chef::Log.debug("Staging #{on_disk_path} to #{dest}") FileUtils.cp(on_disk_path, dest) end end # First, generate metadata Chef::Log.debug("Generating metadata") kcm = Chef::Knife::CookbookMetadata.new kcm.config[:cookbook_path] = [ tmp_cookbook_dir ] kcm.name_args = [ cookbook.name.to_s ] kcm.run tmp_cookbook_dir end |
.make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/chef/cookbook_site_streaming_uploader.rb', line 74 def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ' parts = [] content_file = nil = Time.now.utc.iso8601 secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename)) unless params.nil? || params.empty? params.each do |key, value| if value.kind_of?(File) content_file = value filepath = value.path filename = File.basename(filepath) parts << StringPart.new( "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n") parts << StreamPart.new(value, File.size(filepath)) parts << StringPart.new("\r\n") else parts << StringPart.new( "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n") parts << StringPart.new(value.to_s + "\r\n") end end parts << StringPart.new("--" + boundary + "--\r\n") end body_stream = MultipartStream.new(parts) = Time.now.utc.iso8601 url = URI.parse(to_url) Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{}") # We use the body for signing the request if the file parameter # wasn't a valid file or wasn't included. Extract the body (with # multi-part delimiters intact) to sign the request. # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and # always hash the entire request body. In the file case it would just be # expanded multipart text - the entire body of the POST. content_body = parts.inject("") { |result,part| result + part.read(0, part.size) } content_file.rewind if content_file # we consumed the file for the above operation, so rewind it. = { :http_method=>http_verb, :path=>url.path, :user_id=>user_id, :timestamp=>} (content_file && [:file] = content_file) || ([:body] = (content_body || "")) headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object().sign(secret_key)) content_file.rewind if content_file # net/http doesn't like symbols for header keys, so we'll to_s each one just in case headers = DefaultHeaders.merge(Hash[*headers.map{ |k,v| [k.to_s, v] }.flatten]) req = case http_verb when :put Net::HTTP::Put.new(url.path, headers) when :post Net::HTTP::Post.new(url.path, headers) end req.content_length = body_stream.size req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty? req.body_stream = body_stream http = Net::HTTP.new(url.host, url.port) if url.scheme == "https" http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE end res = http.request(req) #res = http.start {|http_proc| http_proc.request(req) } # alias status to code and to_s to body for test purposes # TODO: stop the following madness! class << res alias :to_s :body # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[]) def headers self end def status code.to_i end end res end |
.post(to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
66 67 68 |
# File 'lib/chef/cookbook_site_streaming_uploader.rb', line 66 def post(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:post, to_url, user_id, secret_key_filename, params, headers) end |
.put(to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object
70 71 72 |
# File 'lib/chef/cookbook_site_streaming_uploader.rb', line 70 def put(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:put, to_url, user_id, secret_key_filename, params, headers) end |