Class: Chef::REST

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

Defined Under Namespace

Classes: CookieJar

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url) ⇒ REST

Returns a new instance of REST.



37
38
39
40
# File 'lib/chef/rest.rb', line 37

def initialize(url)
  @url = url
  @cookies = CookieJar.instance
end

Instance Attribute Details

#cookiesObject

Returns the value of attribute cookies.



35
36
37
# File 'lib/chef/rest.rb', line 35

def cookies
  @cookies
end

#urlObject

Returns the value of attribute url.



35
36
37
# File 'lib/chef/rest.rb', line 35

def url
  @url
end

Instance Method Details

#authenticate(user, pass) ⇒ Object

Authenticate



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/chef/rest.rb', line 66

def authenticate(user, pass)
  Chef::Log.debug("Authenticating #{user} via openid") 
  response = post_rest('openid/consumer/start', { 
    "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{user}",
    "submit" => "Verify"
  })
  post_rest(
    "#{Chef::Config[:openid_url]}#{response["action"]}",
    { "password" => pass }
  )
end

#create_url(path) ⇒ Object



103
104
105
106
107
108
109
# File 'lib/chef/rest.rb', line 103

def create_url(path)
  if path =~ /^(http|https):\/\//
    URI.parse(path)
  else
    URI.parse("#{@url}/#{path}")
  end
end

#delete_rest(path) ⇒ Object

Send an HTTP DELETE request to the path



89
90
91
# File 'lib/chef/rest.rb', line 89

def delete_rest(path)             
  run_request(:DELETE, create_url(path))       
end

#get_rest(path, raw = false) ⇒ Object

Send an HTTP GET request to the path

Parameters

path

The path to GET

raw

Whether you want the raw body returned, or JSON inflated. Defaults

to JSON inflated.


84
85
86
# File 'lib/chef/rest.rb', line 84

def get_rest(path, raw=false)
  run_request(:GET, create_url(path), false, 10, raw)    
end

#post_rest(path, json) ⇒ Object

Send an HTTP POST request to the path



94
95
96
# File 'lib/chef/rest.rb', line 94

def post_rest(path, json)          
  run_request(:POST, create_url(path), json)    
end

#put_rest(path, json) ⇒ Object

Send an HTTP PUT request to the path



99
100
101
# File 'lib/chef/rest.rb', line 99

def put_rest(path, json)           
  run_request(:PUT, create_url(path), json)
end

#register(user, pass, validation_token = nil) ⇒ Object

Register for an OpenID



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/chef/rest.rb', line 43

def register(user, pass, validation_token=nil)
  Chef::Log.debug("Registering #{user} for an openid") 
  registration = nil
  begin
    registration = get_rest("registrations/#{user}")
  rescue Net::HTTPServerException => e
    unless e.message =~ /^404/
      raise e
    end
  end
  unless registration
    post_rest(
      "registrations", 
      { 
        :id => user, 
        :password => pass, 
        :validation_token => validation_token 
      }
    )
  end
end

#run_request(method, url, data = false, limit = 10, raw = false) ⇒ Object

Actually run an HTTP request. First argument is the HTTP method, which should be one of :GET, :PUT, :POST or :DELETE. Next is the URL, then an object to include in the body (which will be converted with .to_json) and finally, the limit of HTTP Redirects to follow (10).

Typically, you won’t use this method – instead, you’ll use one of the helper methods (get_rest, post_rest, etc.)

Will return the body of the response on success.

Raises:

  • (ArgumentError)


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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/chef/rest.rb', line 120

def run_request(method, url, data=false, limit=10, raw=false)
  http_retry_delay = Chef::Config[:http_retry_delay] 
  http_retry_count = Chef::Config[:http_retry_count]

  raise ArgumentError, 'HTTP redirect too deep' if limit == 0 

  http = Net::HTTP.new(url.host, url.port)
  if url.scheme == "https"
    http.use_ssl = true 
    if Chef::Config[:ssl_verify_mode] == :verify_none
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end
    if File.exists?(Chef::Config[:ssl_client_cert])
      http.cert = OpenSSL::X509::Certificate.new(File.read(Chef::Config[:ssl_client_cert]))
      http.key = OpenSSL::PKey::RSA.new(File.read(Chef::Config[:ssl_client_key]))
    end
  end
  http.read_timeout = Chef::Config[:rest_timeout]
  headers = Hash.new
  unless raw
    headers = { 
      'Accept' => "application/json",
    }
  end
  if @cookies.has_key?("#{url.host}:#{url.port}")
    headers['Cookie'] = @cookies["#{url.host}:#{url.port}"]
  end
  req = nil
  case method
  when :GET
    req_path = "#{url.path}"
    req_path << "?#{url.query}" if url.query
    req = Net::HTTP::Get.new(req_path, headers)
  when :POST
    headers["Content-Type"] = 'application/json' if data
    req = Net::HTTP::Post.new(url.path, headers)          
    req.body = data.to_json if data
  when :PUT
    headers["Content-Type"] = 'application/json' if data
    req = Net::HTTP::Put.new(url.path, headers)
    req.body = data.to_json if data
  when :DELETE
    req_path = "#{url.path}"
    req_path << "?#{url.query}" if url.query
    req = Net::HTTP::Delete.new(req_path, headers)
  else
    raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method"
  end
  
  # Optionally handle HTTP Basic Authentication
  req.basic_auth(url.user, url.password) if url.user

  Chef::Log.debug("Sending HTTP Request via #{req.method} to #{req.path}")
  res = nil
  tf = nil
  http_retries = 1

  begin
    res = http.request(req) do |response|
      if raw
        tf = Tempfile.new("chef-rest") 
        # Stolen from http://www.ruby-forum.com/topic/166423
        # Kudos to _why!
        size, total = 0, response.header['Content-Length'].to_i
        response.read_body do |chunk|
          tf.write(chunk) 
          size += chunk.size
          if size == 0
            Chef::Log.debug("#{req.path} done (0 length file)")
          elsif total == 0
            Chef::Log.debug("#{req.path} (zero content length)")
          else
            Chef::Log.debug("#{req.path}" + " %d%% done (%d of %d)" % [(size * 100) / total, size, total])
          end
        end
        tf.close 
        tf
      else
        response.read_body
      end
      response
    end
  rescue Errno::ECONNREFUSED
    Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{req.path} #{http_retries}/#{http_retry_count}")
    sleep(http_retry_delay)
    retry if (http_retries += 1) < http_retry_count
    raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{req.path}, giving up"
  rescue Timeout::Error
    Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{req.path}, retry #{http_retries}/#{http_retry_count}")
    sleep(http_retry_delay)
    retry if (http_retries += 1) < http_retry_count
    raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{req.path}, giving up"
  end
  
  if res.kind_of?(Net::HTTPSuccess)
    if res['set-cookie']
      @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
    end
    if res['content-type'] =~ /json/
      JSON.parse(res.body)
    else
      if raw
        tf
      else
        res.body
      end
    end
  elsif res.kind_of?(Net::HTTPFound) or res.kind_of?(Net::HTTPMovedPermanently)
    if res['set-cookie']
      @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
    end
    run_request(:GET, create_url(res['location']), false, limit - 1, raw)
  else
    res.error!
  end
end