summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/client.rb123
-rw-r--r--lib/chef/compile.rb7
-rw-r--r--lib/chef/config.rb12
-rw-r--r--lib/chef/couchdb.rb132
-rw-r--r--lib/chef/file_store.rb9
-rw-r--r--lib/chef/node.rb111
-rw-r--r--lib/chef/openid_registration.rb179
-rw-r--r--lib/chef/platform.rb3
-rw-r--r--lib/chef/resource.rb9
-rw-r--r--lib/chef/resource_collection.rb5
-rw-r--r--lib/chef/rest.rb99
11 files changed, 643 insertions, 46 deletions
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
new file mode 100644
index 0000000000..2cd11bb2e0
--- /dev/null
+++ b/lib/chef/client.rb
@@ -0,0 +1,123 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+require File.join(File.dirname(__FILE__), "mixin", "params_validate")
+
+require 'rubygems'
+require 'facter'
+
+class Chef
+ class Client
+
+ attr_accessor :node, :registration
+
+ def initialize()
+ @node = nil
+ @safe_name = nil
+ @registration = nil
+ @rest = Chef::REST.new(Chef::Config[:registration_url])
+ end
+
+ def run
+ build_node
+ register
+ authenticate
+ save_node
+ converge
+ end
+
+ def build_node
+ node_name = Facter["fqdn"].value ? Facter["fqdn"].value : Facter["hostname"].value
+ @safe_name = node_name.gsub(/\./, '_')
+ begin
+ @node = @rest.get_rest("nodes/#{@safe_name}")
+ rescue Net::HTTPServerException => e
+ unless e.message =~ /^404/
+ raise e
+ end
+ end
+ unless @node
+ @node ||= Chef::Node.new
+ @node.name(node_name)
+ end
+ Facter.each do |field, value|
+ @node[field] = value
+ end
+ @node
+ end
+
+ def register
+ @registration = nil
+ begin
+ @registration = @rest.get_rest("registrations/#{@safe_name}")
+ rescue Net::HTTPServerException => e
+ unless e.message =~ /^404/
+ raise e
+ end
+ end
+
+ if @registration
+ reg = Chef::FileStore.load("registration", @safe_name)
+ @secret = reg["secret"]
+ else
+ create_registration
+ end
+ end
+
+ def create_registration
+ @secret = random_password(40)
+ Chef::FileStore.store("registration", @safe_name, { "secret" => @secret })
+ @rest.post_rest("registrations", { :id => @safe_name, :password => @secret })
+ end
+
+ def authenticate
+ response = @rest.post_rest('openid/consumer/start', {
+ "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{@safe_name}",
+ "submit" => "Verify"
+ })
+ @rest.post_rest(
+ "#{Chef::Config[:openid_url]}#{response["action"]}",
+ { "password" => @secret }
+ )
+ end
+
+ def save_node
+ @rest.put_rest("nodes/#{@safe_name}", @node)
+ end
+
+ def converge
+ results = @rest.get_rest("nodes/#{@safe_name}/compile")
+ results["collection"].resources.each do |r|
+ r.collection = results["collection"]
+ end
+ cr = Chef::Runner.new(results["node"], results["collection"])
+ cr.converge
+ end
+
+ protected
+ def random_password(len)
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ newpass = ""
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
+ newpass
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/chef/compile.rb b/lib/chef/compile.rb
index 5cd200b098..800d89c6b0 100644
--- a/lib/chef/compile.rb
+++ b/lib/chef/compile.rb
@@ -35,8 +35,11 @@ class Chef
end
def load_node(name)
- Chef::Log.debug("Loading Chef Node #{name}")
- @node = Chef::Node.find(name)
+ Chef::Log.debug("Loading Chef Node #{name} from CouchDB")
+ @node = Chef::Node.load(name)
+ Chef::Log.debug("Loading Recipe for Chef Node #{name}")
+ @node.find_file(name)
+ @node
end
def load_definitions()
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 807631aedb..192b510d17 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -39,10 +39,16 @@ class Chef
@configuration = {
:cookbook_path => [ "/etc/chef/site-cookbook", "/etc/chef/cookbook" ],
:node_path => "/etc/chef/node",
- :file_store_path => "/var/lib/chef/store",
- :search_index_path => "/var/lib/chef/search_index",
+ :file_store_path => "/var/chef/store",
+ :search_index_path => "/var/chef/search_index",
:log_level => :info,
- :log_location => STDOUT
+ :log_location => STDOUT,
+ :openid_providers => nil,
+ :ssl_verify_mode => :verify_none,
+ :rest_timeout => 60,
+ :couchdb_url => "http://localhost:5984",
+ :registration_url => "http://localhost:4000",
+ :openid_url => "http://localhost:4001",
}
class << self
diff --git a/lib/chef/couchdb.rb b/lib/chef/couchdb.rb
new file mode 100644
index 0000000000..c5605080a6
--- /dev/null
+++ b/lib/chef/couchdb.rb
@@ -0,0 +1,132 @@
+require File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require 'digest/sha2'
+require 'json'
+
+class Chef
+ class CouchDB
+ include Chef::Mixin::ParamsValidate
+
+ def initialize(url=nil)
+ url ||= Chef::Config[:couchdb_url]
+ @rest = Chef::REST.new(url)
+ end
+
+ def create_db
+ @database_list = @rest.get_rest("_all_dbs")
+ unless @database_list.detect { |db| db == "chef" }
+ response = @rest.put_rest("chef", Hash.new)
+ end
+ "chef"
+ end
+
+ def create_design_document(name, data)
+ to_update = true
+ begin
+ old_doc = @rest.get_rest("chef/_design%2F#{name}")
+ if data["version"] != old_doc["version"]
+ data["_rev"] = old_doc["_rev"]
+ Chef::Log.debug("Updating #{name} views")
+ else
+ to_update = false
+ end
+ rescue
+ Chef::Log.debug("Creating #{name} views for the first time")
+ end
+ if to_update
+ @rest.put_rest("chef/_design%2F#{name}", data)
+ end
+ true
+ end
+
+ def store(obj_type, name, object)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ :object => object,
+ },
+ {
+ :object => { :respond_to => :to_json },
+ }
+ )
+ @rest.put_rest("chef/#{obj_type}_#{safe_name(name)}", object)
+ end
+
+ def load(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ @rest.get_rest("chef/#{obj_type}_#{safe_name(name)}")
+ end
+
+ def delete(obj_type, name, rev=nil)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ unless rev
+ last_obj = @rest.get_rest("chef/#{obj_type}_#{safe_name(name)}")
+ if last_obj.respond_to?(:couchdb_rev)
+ rev = last_obj.couchdb_rev
+ else
+ rev = last_obj['_rev']
+ end
+ end
+ @rest.delete_rest("chef/#{obj_type}_#{safe_name(name)}?rev=#{rev}")
+ end
+
+ def list(view, inflate=false)
+ validate(
+ {
+ :view => view,
+ },
+ {
+ :view => { :kind_of => String }
+ }
+ )
+ if inflate
+ @rest.get_rest("chef/_view/#{view}/all")
+ else
+ @rest.get_rest("chef/_view/#{view}/all_id")
+ end
+ end
+
+ def has_key?(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ begin
+ @rest.get_rest("chef/#{obj_type}_#{safe_name(name)}")
+ true
+ rescue
+ false
+ end
+ end
+
+ private
+ def safe_name(name)
+ name.gsub(/\./, "_")
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/chef/file_store.rb b/lib/chef/file_store.rb
index 1c2b02ed2f..4981cec802 100644
--- a/lib/chef/file_store.rb
+++ b/lib/chef/file_store.rb
@@ -38,7 +38,6 @@ class Chef
)
store_path = create_store_path(obj_type, name)
raise "Cannot find #{store_path} for #{obj_type} #{name}!" unless File.exists?(store_path)
-
object = JSON.parse(IO.read(store_path))
end
@@ -59,7 +58,7 @@ class Chef
end
end
- def list(obj_type)
+ def list(obj_type, inflate=false)
validate(
{
:obj_type => obj_type,
@@ -71,7 +70,11 @@ class Chef
keys = Array.new
Dir[File.join(Chef::Config[:file_store_path], obj_type, '**', '*')].each do |f|
if File.file?(f)
- keys << File.basename(f)
+ if inflate
+ keys << load(obj_type, File.basename(f))
+ else
+ keys << File.basename(f)
+ end
end
end
keys
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 236bd2f9e1..d7350ac2fa 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -30,49 +30,53 @@ require 'json'
class Chef
class Node
- attr_accessor :attribute, :recipe_list
+ attr_accessor :attribute, :recipe_list, :couchdb_rev
include Chef::Mixin::CheckHelper
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
+ DESIGN_DOCUMENT = {
+ "version" => 3,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ },
+ },
+ }
+
# Create a new Chef::Node object.
def initialize()
@name = nil
@attribute = Hash.new
@recipe_list = Array.new
+ @couchdb_rev = nil
+ @couchdb = Chef::CouchDB.new
end
- # Find a Chef::Node by fqdn. Will search first for Chef::Config["node_path"]/fqdn.rb, then
- # hostname.rb, then default.rb.
+ # Find a recipe for this Chef::Node by fqdn. Will search first for
+ # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb.
#
# Returns a new Chef::Node object.
#
# Raises an ArgumentError if it cannot find the node.
- def self.find(fqdn)
- node_file = self.find_file(fqdn)
- unless node_file
- raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!"
- end
- chef_node = Chef::Node.new()
- chef_node.from_file(node_file)
- chef_node
- end
-
- # Returns an array of nodes available, based on the list of files present.
- def self.list
- results = Array.new
- Dir[File.join(Chef::Config[:node_path], "*.rb")].sort.each do |file|
- mr = file.match(/^.+\/(.+)\.rb$/)
- node_name = mr[1]
- results << node_name
- end
- results
- end
-
- # Returns the file name we would use to build a node. Returns nil if it cannot find
- # a file for this node.
- def self.find_file(fqdn)
+ def find_file(fqdn)
node_file = nil
host_parts = fqdn.split(".")
hostname = host_parts[0]
@@ -84,6 +88,10 @@ class Chef
elsif File.exists?(File.join(Chef::Config[:node_path], "default.rb"))
node_file = File.join(Chef::Config[:node_path], "default.rb")
end
+ unless node_file
+ raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!"
+ end
+ self.from_file(node_file)
end
# Set the name of this Node, or return the current name.
@@ -169,14 +177,18 @@ class Chef
def to_json(*a)
attributes = Hash.new
recipes = Array.new
- {
+ result = {
"name" => @name,
'json_class' => self.class.name,
"attributes" => @attribute,
+ "chef_type" => "node",
"recipes" => @recipe_list,
- }.to_json(*a)
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result.to_json(*a)
end
+ # Create a Chef::Node from JSON
def self.json_create(o)
node = new
node.name(o["name"])
@@ -186,10 +198,49 @@ class Chef
o["recipes"].each do |r|
node.recipes << r
end
-
+ node.couchdb_rev = o["_rev"] if o.has_key?("_rev")
node
end
+ # List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get
+ # the full list of all Nodes, fully inflated.
+ def self.list(inflate=false)
+ rs = Chef::CouchDB.new.list("nodes", inflate)
+ if inflate
+ rs["rows"].collect { |r| r["value"] }
+ else
+ rs["rows"].collect { |r| r["key"] }
+ end
+ end
+
+ # Load a node by name from CouchDB
+ def self.load(name)
+ Chef::CouchDB.new.load("node", name)
+ end
+
+ # Remove this node from the CouchDB
+ def destroy
+ Chef::Queue.send_msg(:queue, :node_remove, self)
+ @couchdb.delete("node", @name, @couchdb_rev)
+ end
+
+ # Save this node to the CouchDB
+ def save
+ Chef::Queue.send_msg(:queue, :node_index, self)
+ results = @couchdb.store("node", @name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # Whether or not there is an OpenID Registration with this key.
+ def self.has_key?(name)
+ Chef::CouchDB.new.has_key?("node", name)
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ Chef::CouchDB.new.create_design_document("nodes", DESIGN_DOCUMENT)
+ end
+
# As a string
def to_s
"node[#{@name}]"
diff --git a/lib/chef/openid_registration.rb b/lib/chef/openid_registration.rb
new file mode 100644
index 0000000000..a887e05c2c
--- /dev/null
+++ b/lib/chef/openid_registration.rb
@@ -0,0 +1,179 @@
+#
+# Chef::Node::OpenIDRegistration
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+
+require 'rubygems'
+require 'json'
+
+class Chef
+ class OpenIDRegistration
+
+ attr_accessor :name, :salt, :validated, :password, :couchdb_rev
+
+ include Chef::Mixin::ParamsValidate
+
+ DESIGN_DOCUMENT = {
+ "version" => 3,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ },
+ "validated" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ if (doc.validated == true) {
+ emit(doc.name, doc);
+ }
+ }
+ }
+ EOJS
+ },
+ "unvalidated" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ if (doc.validated == false) {
+ emit(doc.name, doc);
+ }
+ }
+ }
+ EOJS
+ },
+ },
+ }
+
+ # Create a new Chef::OpenIDRegistration object.
+ def initialize()
+ @name = nil
+ @salt = nil
+ @password = nil
+ @validated = false
+ @couchdb_rev = nil
+ @couchdb = Chef::CouchDB.new
+ end
+
+ def name=(n)
+ @name = n.gsub(/\./, '_')
+ end
+
+ # Set the password for this object.
+ def set_password(password)
+ @salt = generate_salt
+ @password = encrypt_password(@salt, password)
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ attributes = Hash.new
+ recipes = Array.new
+ result = {
+ 'name' => @name,
+ 'json_class' => self.class.name,
+ 'salt' => @salt,
+ 'password' => @password,
+ 'validated' => @validated,
+ 'chef_type' => 'openid_registration',
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result.to_json(*a)
+ end
+
+ # Create a Chef::Node from JSON
+ def self.json_create(o)
+ me = new
+ me.name = o["name"]
+ me.salt = o["salt"]
+ me.password = o["password"]
+ me.validated = o["validated"]
+ me.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ me
+ end
+
+ # List all the Chef::OpenIDRegistration objects in the CouchDB. If inflate is set to true, you will get
+ # the full list of all registration objects. Otherwise, you'll just get the IDs
+ def self.list(inflate=false)
+ rs = Chef::CouchDB.new.list("registrations", inflate)
+ if inflate
+ rs["rows"].collect { |r| r["value"] }
+ else
+ rs["rows"].collect { |r| r["key"] }
+ end
+ end
+
+ # Load an OpenIDRegistration by name from CouchDB
+ def self.load(name)
+ Chef::CouchDB.new.load("openid_registration", name)
+ end
+
+ # Whether or not there is an OpenID Registration with this key.
+ def self.has_key?(name)
+ Chef::CouchDB.new.has_key?("openid_registration", name)
+ end
+
+ # Remove this node from the CouchDB
+ def destroy
+ @couchdb.delete("openid_registration", @name, @couchdb_rev)
+ end
+
+ # Save this node to the CouchDB
+ def save
+ results = @couchdb.store("openid_registration", @name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ Chef::CouchDB.new.create_design_document("registrations", DESIGN_DOCUMENT)
+ end
+
+ protected
+
+ def generate_salt
+ salt = Time.now.to_s
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ 1.upto(30) { |i| salt << chars[rand(chars.size-1)] }
+ salt
+ end
+
+ def encrypt_password(salt, password)
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb
index 2e5899a5af..55e85ae354 100644
--- a/lib/chef/platform.rb
+++ b/lib/chef/platform.rb
@@ -73,11 +73,12 @@ class Chef
pmap = Chef::Platform.find(platform, version)
rtkey = resource_type
if resource_type.kind_of?(Chef::Resource)
- rtkey = resource_type.resource_name
+ rtkey = resource_type.resource_name.to_sym
end
if pmap.has_key?(rtkey)
pmap[rtkey]
else
+ Chef::Log.error("#{rtkey.inspect} #{pmap.inspect}")
raise(
ArgumentError,
"Cannot find a provider for #{resource_type} on #{platform} version #{version}"
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 705738fe59..74f27bcb82 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -28,8 +28,8 @@ class Chef
include Chef::Mixin::CheckHelper
include Chef::Mixin::ParamsValidate
- attr_accessor :actions, :params, :provider, :updated, :allowed_actions
- attr_reader :resource_name, :collection, :source_line
+ attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection
+ attr_reader :resource_name, :source_line
def initialize(name, collection=nil)
@name = name
@@ -124,10 +124,11 @@ class Chef
self.instance_variables.each do |iv|
instance_vars[iv] = self.instance_variable_get(iv) unless iv == "@collection"
end
- {
+ results = {
'json_class' => self.class.name,
'instance_vars' => instance_vars
- }.to_json(*a)
+ }
+ results.to_json(*a)
end
def self.json_create(o)
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 362781ea0e..e53a759460 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -118,10 +118,11 @@ class Chef
self.instance_variables.each do |iv|
instance_vars[iv] = self.instance_variable_get(iv)
end
- {
+ results = {
'json_class' => self.class.name,
'instance_vars' => instance_vars
- }.to_json(*a)
+ }
+ results.to_json(*a)
end
def self.json_create(o)
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 1ca18e4ef5..f71c203b3a 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -26,7 +26,104 @@ require 'json'
class Chef
class REST
- def initialize(url, username)
+ def initialize(url)
+ @url = url
+ @cookies = Hash.new
+ end
+
+ # Send an HTTP GET request to the path
+ def get_rest(path)
+ run_request(:GET, create_url(path))
+ end
+
+ # Send an HTTP DELETE request to the path
+ def delete_rest(path)
+ run_request(:DELETE, create_url(path))
+ end
+
+ # Send an HTTP POST request to the path
+ def post_rest(path, json)
+ run_request(:POST, create_url(path), json)
+ end
+
+ # Send an HTTP PUT request to the path
+ def put_rest(path, json)
+ run_request(:PUT, create_url(path), json)
+ end
+
+ def create_url(path)
+ if path =~ /^(http|https):\/\//
+ URI.parse(path)
+ else
+ URI.parse("#{@url}/#{path}")
+ end
+ end
+
+ # 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.
+ def run_request(method, url, data=false, limit=10)
+ 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
+ end
+ http.read_timeout = Chef::Config[:rest_timeout]
+ headers = {
+ 'Accept' => "application/json",
+ }
+ if @cookies["#{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
+ res = http.request(req)
+ if res.kind_of?(Net::HTTPSuccess)
+ if res['set-cookie']
+ @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
+ end
+ if res['content-type'] == "application/json"
+ JSON.parse(res.body)
+ else
+ res.body
+ end
+ elsif res.kind_of?(Net::HTTPRedirection)
+ if res['set-cookie']
+ @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
+ end
+ run_request(:GET, create_url(res['location']), false, limit - 1)
+ else
+ res.error!
+ end
end
end