Class: Chef::StreamingCookbookUploader

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/streaming_cookbook_uploader.rb

Defined Under Namespace

Classes: MultipartStream, StreamPart, StringPart

Constant Summary collapse

DefaultHeaders =
{ 'accept' => 'application/json', 'x-chef-version' => ::Chef::VERSION }

Class Method Summary collapse

Class Method Details

.make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) ⇒ Object

[View source]

29
30
31
32
33
34
35
36
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
65
66
67
68
69
70
71
72
73
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
# File 'lib/chef/streaming_cookbook_uploader.rb', line 29

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
  
  timestamp = 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)
  
  timestamp = 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: #{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.
  
  signing_options = {
    :http_method=>http_verb,
    :path=>url.path,
    :user_id=>user_id,
    :timestamp=>timestamp}
  (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
  
  headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).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

[View source]

21
22
23
# File 'lib/chef/streaming_cookbook_uploader.rb', line 21

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

[View source]

25
26
27
# File 'lib/chef/streaming_cookbook_uploader.rb', line 25

def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
  make_request(:put, to_url, user_id, secret_key_filename, params, headers)
end