diff --git a/.gitignore b/.gitignore index 16eef85..246c822 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ doc/**/* .yardoc/* Gemfile.lock fixture.rb +coverage +wiki diff --git a/Rakefile b/Rakefile index 4b9cf2a..9e959ef 100644 --- a/Rakefile +++ b/Rakefile @@ -23,6 +23,7 @@ Bundler.require(:doc) desc "Generate documentation" YARD::Rake::YardocTask.new do |t| t.files = [ 'lib/**/*.rb' ] + # t.options = [ "-M redcarpet" ] end desc "Generate docs" diff --git a/TODO.md b/TODO.md index 045439a..9cd5261 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,3 @@ * Refactor classes that inherit from Spice::Chef to take config values -* Add support for data bag item creation \ No newline at end of file +* Add support for data bag item creation +* implement GET /cookbooks?num_versions=all \ No newline at end of file diff --git a/lib/spice.rb b/lib/spice.rb index c0c3b57..48a898a 100644 --- a/lib/spice.rb +++ b/lib/spice.rb @@ -3,8 +3,6 @@ require 'spice/config' require 'spice/connection' -require 'spice/mock' - module Spice extend Spice::Config @@ -27,8 +25,32 @@ def respond_to?(method, include_private=false) end # def respond_to? def mock - Spice::Mock.setup_mock_client + Spice.server_url = 'http://localhost:4000' + Spice.client_name = "testclient" + Spice.client_key = Spice.read_key_file(File.expand_path("../../spec/fixtures/client.pem", __FILE__)) + Spice.chef_version = "0.10.10" + self end # def mock - end -end + + def read_key_file(key_file_path) + + begin + raw_key = File.read(key_file_path).strip + rescue SystemCallError, IOError => e + raise IOError, "Unable to read #{key_file_path}" + end + + begin_rsa = "-----BEGIN RSA PRIVATE KEY-----" + end_rsa = "-----END RSA PRIVATE KEY-----" + + unless (raw_key =~ /\A#{begin_rsa}$/) && (raw_key =~ /^#{end_rsa}\Z/) + msg = "The file #{key_file} is not a properly formatted private key.\n" + msg << "It must contain '#{begin_rsa}' and '#{end_rsa}'" + raise ArgumentError, msg + end + return OpenSSL::PKey::RSA.new(raw_key) + end # def read_key_file + + end # class << self +end # module Spice diff --git a/lib/spice/authentication.rb b/lib/spice/authentication.rb index 413ab58..74b0543 100644 --- a/lib/spice/authentication.rb +++ b/lib/spice/authentication.rb @@ -6,7 +6,7 @@ module Spice module Authentication def signature_headers(method, path, json_body=nil) - uri = URI(Spice.server_url) + uri = URI(server_url) params = { :http_method => method, @@ -14,29 +14,14 @@ def signature_headers(method, path, json_body=nil) :body => json_body || "", :host => "#{uri.host}:#{uri.port}", :timestamp => Time.now.utc.iso8601, - :user_id => Spice.client_name + :user_id => client_name } - - begin - raw_key = File.read(Spice.key_file).strip - rescue SystemCallError, IOError => e - raise IOError, "Unable to read #{key_file}" - end - - unless (raw_key =~ /\A-----BEGIN RSA PRIVATE KEY-----$/) && - (raw_key =~ /^-----END RSA PRIVATE KEY-----\Z/) - msg = "The file #{key_file} is not a properly formatted private key.\n" - msg << "It must contain '-----BEGIN RSA PRIVATE KEY-----' and '-----END RSA PRIVATE KEY-----'" - raise ArgumentError, msg - end - - key = OpenSSL::PKey::RSA.new(raw_key) signing_object = Mixlib::Authentication::SignedHeaderAuth.signing_object(params) - signed_headers = signing_object.sign(key) + signed_headers = signing_object.sign(client_key) # Platform requires X-Chef-Version header - signed_headers['X-Chef-Version'] = Spice.chef_version + signed_headers['X-Chef-Version'] = chef_version # signed_headers['Content-Length'] = json_body.bytesize.to_s if json_body signed_headers end # def signature_headers diff --git a/lib/spice/config.rb b/lib/spice/config.rb index 558ed80..e3bb34e 100644 --- a/lib/spice/config.rb +++ b/lib/spice/config.rb @@ -19,16 +19,17 @@ module Config DEFAULT_CONNECTION_OPTIONS = {} # Default client name - DEFAULT_CLIENT_NAME = nil + DEFAULT_CLIENT_NAME = "" # Default key file - DEFAULT_KEY_FILE = nil + DEFAULT_CLIENT_KEY = "" # An array of valid config options + VALID_OPTIONS_KEYS = [ :server_url, :client_name, - :key_file, + :client_key, :chef_version, :user_agent, :connection_options, @@ -56,11 +57,10 @@ def self.extended(base) # Spice.setup do |s| # s.server_url = "http://chef.example.com:4000" # s.client_name = "admin" - # s.key_file = "/home/admin/.chef/admin.pem" + # s.client_key = Spice.read_key_file("/path/to/key_file.pem") # end # @yieldparam Spice - # @yieldreturn Spice - + # @yieldreturn Spice def setup yield self self @@ -79,7 +79,7 @@ def reset self.server_url = DEFAULT_SERVER_URL self.chef_version = DEFAULT_CHEF_VERSION self.client_name = DEFAULT_CLIENT_NAME - self.key_file = DEFAULT_KEY_FILE + self.client_key = DEFAULT_CLIENT_KEY self.connection_options = DEFAULT_CONNECTION_OPTIONS self.middleware = DEFAULT_MIDDLEWARE self diff --git a/lib/spice/connection.rb b/lib/spice/connection.rb index 6140276..8e56f5e 100644 --- a/lib/spice/connection.rb +++ b/lib/spice/connection.rb @@ -33,8 +33,12 @@ class Connection include Spice::Connection::Search include Spice::Request include Spice::Authentication - - attr_accessor *Config::VALID_OPTIONS_KEYS + + # @private + + Config::VALID_OPTIONS_KEYS.each do |key| + attr_accessor key + end def initialize(attrs=Mash.new) attrs = Spice.options.merge(attrs) diff --git a/lib/spice/connection/cookbooks.rb b/lib/spice/connection/cookbooks.rb index c802226..11a2149 100644 --- a/lib/spice/connection/cookbooks.rb +++ b/lib/spice/connection/cookbooks.rb @@ -8,15 +8,15 @@ module Cookbooks def cookbooks if Gem::Version.new(Spice.chef_version) >= Gem::Version.new("0.10.0") cookbooks = [] - get("/cookbooks").each_pair do |key, value| + get("/cookbooks?num_versions=all").each_pair do |key, value| versions = value['versions'].map{ |v| v['version'] } cookbooks << Spice::Cookbook.get_or_new(:name => key, :versions => versions) end cookbooks else get("/cookbooks").keys.map do |cookbook| - attributes = get("/cookbooks/#{cookbook}").to_a[0] - Spice::Cookbook.get_or_new(:name => attributes[0], :versions => attributes[1]) + cb = get("/cookbooks/#{cookbook}") + Spice::Cookbook.get_or_new(:name => cookbook, :versions => cb[cookbook]) end end end # def cookbooks diff --git a/lib/spice/connection/data_bags.rb b/lib/spice/connection/data_bags.rb index 1fa1b63..2e86911 100644 --- a/lib/spice/connection/data_bags.rb +++ b/lib/spice/connection/data_bags.rb @@ -29,9 +29,8 @@ def data_bag(name) # @return [Spice::DataBagItem] # @raise [Spice::Error::NotFound] raised when data bag item does not exist def data_bag_item(name, id) - data = get("/data/#{name}/#{id}") - data.delete('id') - Spice::DataBagItem.get_or_new(:_id => id, :data => data, :name => name) + data_bag_item = get("/data/#{name}/#{id}") + Spice::DataBagItem.get_or_new(data_bag_item) end # def data_bag_item def create_data_bag(name) diff --git a/lib/spice/connection/search.rb b/lib/spice/connection/search.rb index 5c4f381..340f05d 100644 --- a/lib/spice/connection/search.rb +++ b/lib/spice/connection/search.rb @@ -8,6 +8,7 @@ module Search # @option options [Numeric] :start The number by which to offset the results # @option options [Numeric] :rows The maximum number of rows to return def search(index, options=Mash.new) + index = index.to_s options = {:q => options} if options.is_a? String options.symbolize_keys! diff --git a/lib/spice/core_ext/mash.rb b/lib/spice/core_ext/mash.rb index 05b10c8..f0c7c21 100644 --- a/lib/spice/core_ext/mash.rb +++ b/lib/spice/core_ext/mash.rb @@ -53,7 +53,7 @@ class Mash < Hash # @param constructor # The default value for the mash. Defaults to an empty hash. # - # @details [Alternatives] + # [Alternatives] # If constructor is a Hash, a new mash will be created based on the keys of # the hash and no default value will be set. def initialize(constructor = {}) @@ -81,7 +81,7 @@ def initialize_copy(orig) # @param key The default value for the mash. Defaults to nil. # - # @details [Alternatives] + # [Alternatives] # If key is a Symbol and it is a key in the mash, then the default value will # be set to the value matching the key. def default(key = nil) @@ -130,14 +130,14 @@ def key?(key) alias_method :member?, :key? # @param key The key to fetch. This will be run through convert_key. - # @param *extras Default value. + # @param extras Default value. # # @return [Object] The value at key or the default value. def fetch(key, *extras) super(convert_key(key), *extras) end - # @param *indices + # @param indices # The keys to retrieve values for. These will be run through +convert_key+. # # @return [Array] The values at each of the provided keys @@ -158,8 +158,6 @@ def delete(key) super(convert_key(key)) end - # @param *rejected The key to convert. # - # @param [Object] - # The converted key. If the key was a symbol, it will be converted to a - # string. - # # @api private def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key diff --git a/lib/spice/environment.rb b/lib/spice/environment.rb index b3aed65..f18c69d 100644 --- a/lib/spice/environment.rb +++ b/lib/spice/environment.rb @@ -5,13 +5,13 @@ class Environment < Base attr_reader :name, :description, :attributes, :cookbook_versions, :chef_type, :json_class - def json_class - @json_class ||= "Chef::Environment" + def initialize(attrs=Mash.new) + super + @attrs['json_class'] ||= "Chef::Environment" + @attrs['chef_type'] ||= "environment" + @attrs['attributes'] ||= Mash.new + @attrs['cookbook_version'] ||= Mash.new end - def chef_type - @chef_type ||= 'environment' - end - end end \ No newline at end of file diff --git a/lib/spice/mock.rb b/lib/spice/mock.rb deleted file mode 100644 index a6cb7c1..0000000 --- a/lib/spice/mock.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Spice - class Mock - class << self - def setup_mock_client - self.setup_authentication - Spice.server_url = 'http://localhost:4000' - Spice.client_name = "testclient" - Spice.key_file = "/tmp/keyfile.pem" - Spice.chef_version = "0.10.4" - end - - def setup_authentication - File.open("/tmp/keyfile.pem","w+") do |f| - f.write(%Q{-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAqd1K3siOLkxiU0B98QzP+qbT3uPaCIGGR+EAO8mXMpTUNfPv -Kr4NeIV8kqfrngghYF3Y2Ky0e3Ifl3b7UQTfrSofmyoC/EBBA4dH2ygUoYtP0aIi -sdxnSpYhX9PXavZ4POkDuO3SKRUEuC71OzIQQ3GijXA8L1JRBTOb0QiC7vBSuwaM -15qAqW4FGs7jYyG909AERY3AQTmZZ8TsAY07fqv4BC8a6965dzBp3GD5KY/IuRlN -hoqp1xQdqZ8C0nNk6akWoia9/NMs4rULpuiPSeVOSDX0iGcOMFG6Ur5ihBl8S+Dy -96QpNU3Xx/puhALbGxPkoyELwmhuZRmpMo3YDQIDAQABAoIBABYifAsj30MxOO7Y -TJEItmFXM+yrjFHnbvQW4czorAcvVafiLZxIP8Egw+bocs+ZB6BjGkrB1pLvgCZg -nscj93G9N3kktFbimJY5Hqf0RRv352LN2e+LZPpXLeoq1LtferOVOaLzeptX3dGS -bOpVz8C6IhCEO5N/Coe+/eLzVPyFpibhlNpQPPUbPP8mpL2d0oro4GHcO0lcdm0t -5iAs7drwOcA97dntBZFwMj5tA0d6ujgc88lS3eNqZAx5TsOKh+yetNMpO+GJlXFO -koiJ4ZF6Fx6/5R+iX+ejtebA3GPdPAHSMxmVUGWDOeRTdpsqsEyDXsLADb1FTVnk -B4WPvRECgYEA1GkyU2CUw3kcKAQrxfmGdDuTbwSG7/Np9nCie57PDE16CQd6BP5H -cRbOoC10/h2gJmaqODV7PJmeimOd5C74xiLfZ8TZvg+nv2NOwbGMLbU9PiZcjda9 -YEVmya6UpMBZdD73yir5PUPHiws3Xx0+8CKpD/nfDhfOVPTIzrJyDMMCgYEAzLj0 -2NLWhI17KxNpFV76XlEHmwwhKKCN0HsFj/qSmPQZsRn5Xph9wgH2ey+ag12IOVGe -kbYPz1/74uzfkZpye4ysn1cvDUXbPbAzdBJ8VT/kmQpB1+ZRFRi7xUf+AEp54932 -FObLehFPo+w0SKNHeJqKY0k/k/0/AwpapLwVeu8CgYEAh8nYSkTr1SqPPWWtNhqW -Qaf0UHxsZukNTGYk+TJE2nCNG0iUUKzdrwYNgYiNygXWY7YuC1DlP5BVMdMNFNqS -XtfcSdImAMKxUkCCEIEYRAAg7qJHeMVWuzyiwTvB+rCcfxvh/HQMcYXrApBhDYT8 -vzbpLTVnyvKdDOKPnNOm5VECgYBajhGX+yLyfRaXRj28O0fqAlOn7KSaMPKp3lDm -kALab1cR9JhOlItEDtQ1RyhEpVHFcOoAMBUsOJvVk8uMv1GWfvI4hTsF1vmUfuUz -mZ2vo9R9MYFQe8sv1sHwENk0zby+44afVjt5IkElFC1IWBkcKte99T+POXzu3lyb -86pYtwKBgEaeHGw4mSbnz6aecPbshGYrV3CkPRO9z6nAiC6lp6a0PYJAPNDrvqdg -5h0uCkb5FfgWIOq2tCUYY/ZrSglE17wPmd8iRAN7wb+prKAHOh+o4P0k1EtviNdU -8JgshucQzoeIygNfo2QG3vfsfA/4V9aa/yb/bqHPsJHBgnuSVjv+ ------END RSA PRIVATE KEY-----}) - end - end - end - end -end \ No newline at end of file diff --git a/lib/spice/persistence.rb b/lib/spice/persistence.rb deleted file mode 100644 index 924d82e..0000000 --- a/lib/spice/persistence.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Spice - module Persistence - def self.included(base) - base.extend ActiveModel::Callbacks - base.send(:define_model_callbacks, :create, :update, :destroy) - base.send(:after_create, :do_post) - base.send(:after_update, :do_put) - base.send(:after_destroy, :do_delete) - end - - def create - run_callbacks :create do - end - end - - def update - run_callbacks :update do - end - end - - def destroy - run_callbacks :destroy do - end - end - - def endpoint(ep=nil) - @endpoint = ep if !ep.nil? - @endpoint - end - - def all(options={}) - connection.send(endpoint, options) - end - - def get(options) - connection.send(endpoint, options).first - end - - def connection - Spice.connection - end - - def do_post - response = post("/#{self.class.endpoint}", attributes) - update_attributes(response.body) - response = get("/#{self.class.endpoint}/#{name}") - update_attributes(response.body) - end - - def do_put - put("/#{self.class.endpoint}/#{name}", attributes) - end - - def do_delete - delete("/#{self.class.endpoint}/#{name}") - end - - def new_record? - raise NotImplementedError, "Override this method in the class that includes this module." - end - end -end \ No newline at end of file diff --git a/lib/spice/request.rb b/lib/spice/request.rb index ea28504..e2e2f0a 100644 --- a/lib/spice/request.rb +++ b/lib/spice/request.rb @@ -32,26 +32,30 @@ def connection :headers => { :accept => 'application/json', :content_type => 'application/json', - :user_agent => Spice.user_agent + :user_agent => user_agent } } - options = default_options.deep_merge(Spice.connection_options) + options = default_options.deep_merge(connection_options) # @connection = Faraday.new(Spice.server_url, options, &Spice.middleware) - Faraday.new(Spice.server_url, options, &Spice.middleware) + Faraday.new(server_url, options, &middleware) end def request(method, path, params, options) json_params = params ? Yajl.dump(params) : "" - uri = Spice.server_url + uri = server_url uri = URI(uri) unless uri.respond_to?(:host) uri += path headers = signature_headers(method.to_sym.upcase, uri.path, json_params) - + # puts headers.inspect + # puts client_key.inspect response = connection.run_request(method.to_sym, path, nil, headers) do |request| request.options[:raw] = true if options[:raw] + + # puts request.inspect + unless params.nil? if request.method == :post || :put request.body = json_params @@ -64,6 +68,10 @@ def request(method, path, params, options) options[:raw] ? response : response.body + # File.open(File.expand_path("../../../spec/fixtures/#{path.gsub(/\?.*/, '')}.json", __FILE__), "w") do |f| + # f.write Yajl.dump(response.body) + # end + # rescue Faraday::Error::ClientError raise Spice::Error::ClientError end diff --git a/spec/spice/search_spec.rb b/spec/spice/search_spec.rb deleted file mode 100644 index 51f5efc..0000000 --- a/spec/spice/search_spec.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'spec_helper' - diff --git a/spice.gemspec b/spice.gemspec index 4fb2cc1..ff748c6 100644 --- a/spice.gemspec +++ b/spice.gemspec @@ -16,7 +16,9 @@ Gem::Specification.new do |s| s.add_dependency "faraday", "~> 0.8.0" s.add_dependency "mixlib-authentication", '>= 1.1.4' s.add_dependency "yajl-ruby", '>= 1.1.0' - + s.add_dependency "fakefs" + + s.add_development_dependency 'simplecov' s.add_development_dependency 'rspec', '>= 2.8.0' s.add_development_dependency "webmock", ">= 1.8.2" s.add_development_dependency "timecop", ">= 0.3.5"