spice 1.0.0.pre → 1.0.0.rc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # Release Notes - Spice - Version 1.0.0
2
2
 
3
+ * Note: this version of Spice is a major version bump, which means it is *not* backwards-compatible with previous versions.
4
+
3
5
  ## Removed
4
6
 
5
7
  * Old-style connections are no longer supported. Please use the connection string like Chef uses (ex. http://chef.example.com:4000)
6
8
 
9
+ ## Improvement
10
+
11
+ * Complete rewrite! Returned results are now full interactive objects and not just hashes. Create, update, and destroy resources with an ORM-like interface.
12
+
7
13
  # Release Notes - Spice - Version 0.5.0
8
14
 
9
15
  ## Bug
@@ -15,8 +21,6 @@
15
21
 
16
22
  * Node support!
17
23
 
18
-
19
-
20
24
  # Release Notes - Spice - Version 0.3.0
21
25
 
22
26
  ## Bug
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :development, :test do
6
+ gem 'rake'
6
7
  gem 'rspec', '>= 2.8.0'
7
8
  gem "webmock", ">= 1.7.10"
8
9
  gem "timecop", ">= 0.3.5"
@@ -17,4 +18,5 @@ end
17
18
 
18
19
  group :doc do
19
20
  gem 'yard'
21
+ gem 'redcarpet'
20
22
  end
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # spice
1
+ # Spice - Chef API Wrapper [![Dependency Status](https://gemnasium.com/danryan/spice.png)][gemnasium]
2
2
 
3
- Spice is a zesty Chef API wrapper. Its primary purpose is to let you easily integrate your apps with a (Chef)[http://opscode.com/chef] server easily. Spice provides support for the [Chef API](http://wiki.opscode.com/display/chef/Server+API)
3
+ [gemnasium]: https://gemnasium.com/danryan/spice
4
+
5
+ Spice lets you easily integrate your apps with a [Chef](http://opscode.com/chef) server. Spice provides support for the [Chef API](http://wiki.opscode.com/display/chef/Server+API)
4
6
 
5
7
  ## Installation
6
8
 
@@ -12,7 +14,7 @@ Of course, You can always grab the source from http://github.com/danryan/spice.
12
14
 
13
15
  ### Deprecation notice
14
16
 
15
- Explicitly setting a `host`, `port`, and `scheme` value has been deprecated in favor of setting a single variable, `server_url`, which matches the format of Chef's client config parameter, `chef_server_url`. The old way of defining `host`, `port`, and `scheme` has been removed.
17
+ Explicitly setting a `host`, `port`, and `scheme` value has been removed in favor of setting a single variable, `server_url`, which matches the format of Chef's client config parameter, `chef_server_url`.
16
18
 
17
19
  ### Contributors
18
20
 
data/Rakefile CHANGED
@@ -23,4 +23,9 @@ Bundler.require(:doc)
23
23
  desc "Generate documentation"
24
24
  YARD::Rake::YardocTask.new do |t|
25
25
  t.files = [ 'lib/**/*.rb' ]
26
+ end
27
+
28
+ desc "Generate docs"
29
+ task :doc do
30
+ sh %{bundle exec rake yard}
26
31
  end
data/lib/spice.rb CHANGED
@@ -1,11 +1,13 @@
1
- require 'toystore'
2
- require 'adapter/memory'
1
+ require 'virtus'
2
+ require 'aequitas'
3
+ require 'active_model/callbacks'
4
+ require 'active_support/core_ext/hash/deep_merge'
5
+ require 'active_support/core_ext/hash/keys'
3
6
  require 'mixlib/authentication'
4
7
 
5
8
  require 'spice/config'
6
9
  require 'spice/error'
7
10
  require 'spice/authentication'
8
- require 'spice/chef'
9
11
 
10
12
  require 'spice/role'
11
13
  require 'spice/client'
data/lib/spice/client.rb CHANGED
@@ -2,12 +2,16 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class Client
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "clients"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :public_key, String
13
17
  attribute :private_key, String
@@ -18,12 +22,34 @@ module Spice
18
22
 
19
23
  validates_presence_of :name, :json_class, :chef_type
20
24
 
21
-
25
+ after_create :wat
26
+ def wat
27
+ puts "WAT"
28
+ end
29
+
22
30
  def do_post
23
31
  response = connection.post("/clients", :name => name)
24
32
  update_attributes(response.body)
25
33
  response = connection.get("/clients/#{name}")
26
34
  update_attributes(response.body)
27
35
  end
36
+
37
+ def do_put
38
+ response = connection.put("/clients/#{name}", attributes)
39
+ end
40
+
41
+ def self.get(name)
42
+ connection.client(name)
43
+ end
44
+
45
+ # Check if the client exists on the Chef server
46
+ def new_record?
47
+ begin
48
+ connection.get("/clients/#{name}")
49
+ return false
50
+ rescue Spice::Error::NotFound
51
+ return true
52
+ end
53
+ end
28
54
  end
29
55
  end
data/lib/spice/config.rb CHANGED
@@ -2,42 +2,61 @@ require 'spice/version'
2
2
 
3
3
  module Spice
4
4
  module Config
5
+ # The default Chef server URL
5
6
  DEFAULT_SERVER_URL = "http://localhost:4000"
6
7
 
8
+ # The default Chef version (changing this will enable disable certain features)
7
9
  DEFAULT_CHEF_VERSION = "0.10.8"
8
-
10
+
11
+ # The default Spice User-Agent header
9
12
  DEFAULT_USER_AGENT = "Spice #{Spice::VERSION}"
10
13
 
14
+ # Default connection options
11
15
  DEFAULT_CONNECTION_OPTIONS = {}
12
16
 
17
+ # An array of valid config options
13
18
  VALID_OPTIONS_KEYS = [
14
19
  :server_url,
15
20
  :client_name,
16
21
  :key_file,
17
22
  :raw_key,
18
23
  :chef_version,
19
- :adapter,
20
24
  :user_agent,
21
25
  :connection_options
22
26
  ]
23
27
 
24
- attr_accessor *VALID_OPTIONS_KEYS
28
+ VALID_OPTIONS_KEYS.each do |key|
29
+ attr_accessor key
30
+ end
25
31
 
32
+ # Reset all config options to default when the module is extended
26
33
  def self.extended(base)
27
34
  base.reset
28
35
  end
29
36
 
37
+ # Convenience method to configure Spice in a block
38
+ # @example Configuring spice
39
+ # Spice.setup do |s|
40
+ # s.server_url = "http://chef.example.com:4000"
41
+ # s.client_name = "admin"
42
+ # s.key_file = "/home/admin/.chef/admin.pem"
43
+ # end
44
+ # @yieldparam Spice
45
+ # @yieldreturn Spice
46
+
30
47
  def setup
31
48
  yield self
32
49
  self
33
50
  end
34
51
 
52
+ # Create an options hash from valid options keys
35
53
  def options
36
54
  options = {}
37
55
  VALID_OPTIONS_KEYS.each{|k| options[k] = send(k)}
38
56
  options
39
57
  end
40
58
 
59
+ # Reset all config options to their defaults
41
60
  def reset
42
61
  self.user_agent = DEFAULT_USER_AGENT
43
62
  self.server_url = DEFAULT_SERVER_URL
@@ -12,10 +12,12 @@ require 'spice/connection/nodes'
12
12
  require 'spice/connection/roles'
13
13
  require 'spice/connection/search'
14
14
 
15
+
16
+
15
17
  module Spice
16
18
  class Connection
17
- include Toy::Store
18
- store :memory, {}
19
+ include Virtus
20
+ include Aequitas
19
21
 
20
22
  include Spice::Connection::Clients
21
23
  include Spice::Connection::Cookbooks
@@ -28,6 +30,9 @@ module Spice
28
30
  include Spice::Connection::Search
29
31
  include Spice::Request
30
32
 
33
+ # @macro [attach] attribute
34
+ # @attribute [rw]
35
+ # @return [$2] the $1 attribute
31
36
  attribute :client_name, String
32
37
  attribute :key_file, String
33
38
  attribute :key, String
@@ -46,6 +51,11 @@ module Spice
46
51
  URI.parse(server_url)
47
52
  end
48
53
 
54
+ # Perform an HTTP GET request
55
+ # @param [String] path The relative path
56
+ # @return [Faraday::Response]
57
+ # @example Get a list of all nodes
58
+ # Spice.connection.get("/nodes")
49
59
  def get(path)
50
60
  response = request(:headers => build_headers(
51
61
  :GET,
@@ -56,6 +66,12 @@ module Spice
56
66
  return response
57
67
  end # def get
58
68
 
69
+ # Perform an HTTP POST request
70
+ # @param [String] path The relative path
71
+ # @param [Hash] payload The payload to send with the POST request
72
+ # @return [Faraday::Response]
73
+ # @example Create a new client named "foo"
74
+ # Spice.connection.post("/clients", :name => "foo")
59
75
  def post(path, payload)
60
76
  response = request(:headers => build_headers(
61
77
  :POST,
@@ -68,7 +84,13 @@ module Spice
68
84
  return response
69
85
  end # def post
70
86
 
71
- def put(path, payload, headers={})
87
+ # Perform an HTTP PUT request
88
+ # @param [String] path The relative path
89
+ # @param [Hash] payload The payload to send with the PUT request
90
+ # @return [Faraday::Response]
91
+ # @example Make an existing client "foo" an admin
92
+ # Spice.connection.put("/clients/foo", :admin => true)
93
+ def put(path, payload)
72
94
  response = request(:headers => build_headers(
73
95
  :PUT,
74
96
  "#{parsed_url.path}#{path}",
@@ -80,7 +102,12 @@ module Spice
80
102
  return response
81
103
  end # def put
82
104
 
83
- def delete(path, headers={})
105
+ # Perform an HTTP DELETE request
106
+ # @param [String] path The relative path
107
+ # @return [Faraday::Response]
108
+ # @example Delete the client "foo"
109
+ # Spice.connection.delete("/clients/foo")
110
+ def delete(path)
84
111
  response = request(:headers => build_headers(
85
112
  :DELETE,
86
113
  "#{parsed_url.path}#{path}")
@@ -1,10 +1,22 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module Clients
4
+ # A collection of clients
5
+ # @param [Hash] options An options hash that is passed to {Search#search}
6
+ # @return [Array<Spice::Client>, Array]
7
+ # @see Search#search
8
+ # @example Retrieve all clients with names that begin with "app"
9
+ # Spice.clients(:q => "name:app*")
4
10
  def clients(options={})
5
11
  connection.search('client', options)
6
12
  end
7
13
 
14
+ # Retrieve a single client
15
+ # @param [String] name The client name
16
+ # @return [Spice::Client]
17
+ # @raise [Spice::Error::NotFound] raised when client does not exist
18
+ # @example Retrieve the client named "admin"
19
+ # Spice.client("name")
8
20
  def client(name)
9
21
  attributes = connection.get("/clients/#{name}").body
10
22
  Spice::Client.new(attributes)
@@ -1,7 +1,11 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module Cookbooks
4
- def cookbooks(options={})
4
+ # Retrieve an array of all cookbooks
5
+ # @return [Array<Spice::Cookbook>]
6
+ # @example Retrieve all cookbooks with versions
7
+ # Spice.cookbooks
8
+ def cookbooks
5
9
  if Gem::Version.new(Spice.chef_version) >= Gem::Version.new("0.10.0")
6
10
  cookbooks = []
7
11
  connection.get("/cookbooks").body.each_pair do |key, value|
@@ -17,24 +21,32 @@ module Spice
17
21
  end
18
22
  end
19
23
 
24
+ # Retrieve a single cookbook
25
+ # @param [String] name The name of the cookbook
26
+ # @return [Spice::Cookbook]
27
+ # @raise [Spice::Error::NotFound] raised when cookbook does not exist
20
28
  def cookbook(name)
21
29
  if Gem::Version.new(Spice.chef_version) >= Gem::Version.new("0.10.0")
22
30
  cookbook = connection.get("/cookbooks/#{name}").body
23
- puts cookbook[name]
24
31
  versions = cookbook[name]['versions'].map{ |v| v['version'] }
25
32
 
26
33
  Spice::Cookbook.new(:name => name, :versions => versions)
27
34
  else
28
- connection.get("/cookbooks").body.keys.map do |cookbook|
29
- attributes = connection.get("/cookbooks/#{cookbook}").body.to_a[0]
30
- Spice::Cookbook.new(:name => attributes[0], :versions => attributes[1])
31
- end
35
+ cookbook = connection.get("/cookbooks/#{name}").body
36
+ Spice::Cookbook.new(:name => name, :versions => cookbook[name])
32
37
  end
33
38
  end
34
39
 
40
+ # Retrieve a single cookbook version
41
+ # @param [String] name The cookbook name
42
+ # @param [String] version The cookbook version
43
+ # @return [Spice::CookbookVersion]
44
+ # @raise [Spice::Error::NotFound] raised when cookbook version does not exist
35
45
  def cookbook_version(name, version)
36
46
  attributes = connection.get("/cookbooks/#{name}/#{version}").body
37
- Spice::CookbookVersion.new(attributes)
47
+ duped_attributes = attributes.dup
48
+ duped_attributes[:_attributes] = attributes['attributes']
49
+ Spice::CookbookVersion.new(duped_attributes)
38
50
  end
39
51
 
40
52
  end # module Cookbooks
@@ -1,6 +1,10 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module DataBags
4
+ # Retrieve an array of all data bags
5
+ # @return [Array<Spice::DataBag>]
6
+ # @example Retrieve all data bags
7
+ # Spice.data_bags
4
8
  def data_bags
5
9
  connection.get("/data").body.keys.map do |data_bag|
6
10
  items = connection.search(data_bag)
@@ -10,11 +14,20 @@ module Spice
10
14
 
11
15
  alias :data :data_bags
12
16
 
17
+ # Retrieve a single data bag and its items
18
+ # @param [String] name The data bag name
19
+ # @return [Spice::DataBag]
20
+ # @raise [Spice::Error::NotFound] raised when data bag does not exist
13
21
  def data_bag(name)
14
22
  items = connection.search(name)
15
23
  Spice::DataBag.new(:name => name, :items => items)
16
24
  end
17
25
 
26
+ # Retrieve a single data bag item
27
+ # @param [String] name The data bag name
28
+ # @param [String] id The data bag item id
29
+ # @return [Spice::DataBagItem]
30
+ # @raise [Spice::Error::NotFound] raised when data bag item does not exist
18
31
  def data_bag_item(name, id)
19
32
  data = connection.get("/data/#{name}/#{id}")
20
33
  data.delete('id')
@@ -1,10 +1,22 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module Environments
4
+ # A collection of environments
5
+ # @param [Hash] options An options hash that is passed to {Search#search}
6
+ # @return [Array<Spice::Environment>]
7
+ # @see Search#search
8
+ # @example Retrieve all environments with names that begin with "prod"
9
+ # Spice.environments(:q => "name:prod*")
4
10
  def environments(options={})
5
11
  connection.search('environment', options)
6
12
  end
7
13
 
14
+ # Retrieve a single environment
15
+ # @param [String] name The environment name
16
+ # @return [Spice::Environment]
17
+ # @raise [Spice::Error::NotFound] raised when environment does not exist
18
+ # @example Retrieve the environment named "production"
19
+ # Spice.environment("production")
8
20
  def environment(name)
9
21
  attributes = connection.get("/environments/#{name}").body
10
22
  attributes['attrs'] = attributes.delete('attributes')
@@ -1,10 +1,24 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module Nodes
4
+ # A collection of nodes
5
+ # @param [Hash] options An options hash that is passed to {Search#search}
6
+ # @return [Array<Spice::Node>]
7
+ # @see Search#search
8
+ # @example Retrieve all nodes that have the role "base"
9
+ # Spice.nodes(:q => "roles:base")
10
+ # @example Retrieve nodes with role "base" in the "production" environment
11
+ # Spice.nodes(:q => "roles:base AND environment:production")
4
12
  def nodes(options={})
5
13
  connection.search('node', options)
6
14
  end
7
15
 
16
+ # Retrieve a single client
17
+ # @param [String] name The node name
18
+ # @return [Spice::Node]
19
+ # @raise [Spice::Error::NotFound] raised when node does not exist
20
+ # @example Retrieve the node named "app.example.com"
21
+ # Spice.node("app.example.com")
8
22
  def node(name)
9
23
  node_attributes = connection.get("/nodes/#{name}").body
10
24
  Spice::Node.new(node_attributes)
@@ -1,10 +1,22 @@
1
1
  module Spice
2
2
  class Connection
3
3
  module Roles
4
+ # A collection of roles
5
+ # @param [Hash] options An options hash that is passed to {Search#search}
6
+ # @return [Array<Spice::Role>]
7
+ # @see Search#search
8
+ # @example Retrieve all roles that start with "app_"
9
+ # Spice.roles(:q => "name:app_*")
4
10
  def roles(options={})
5
11
  connection.search('role', options)
6
12
  end
7
13
 
14
+ # Retrieve a single role
15
+ # @param [String] name The role name
16
+ # @return [Spice::Role]
17
+ # @raise [Spice::Error::NotFound] raised when role does not exist
18
+ # @example Retrieve the role "app_server"
19
+ # Spice.role("app_server")
8
20
  def role(name)
9
21
  attributes = connection.get("/roles/#{name}").body
10
22
  Spice::Role.new(attributes)
@@ -3,6 +3,10 @@ require 'cgi'
3
3
  module Spice
4
4
  class Connection
5
5
  module Search
6
+ # @option options [String] :q The Solr search query string
7
+ # @option options [String] :sort Order by which to sort the results
8
+ # @option options [Numeric] :start The number by which to offset the results
9
+ # @option options [Numeric] :rows The maximum number of rows to return
6
10
  def search(index, options={})
7
11
  options = {:q => options} if options.is_a? String
8
12
  options.symbolize_keys!
@@ -2,15 +2,33 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class Cookbook
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "cookbooks"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :versions, Array, :default => []
13
17
 
14
18
  validates_presence_of :name
19
+
20
+ def self.get(name)
21
+ connection.cookbook(name)
22
+ end
23
+
24
+ # Check if the cookbook exists on the Chef server
25
+ def new_record?
26
+ begin
27
+ connection.get("/cookbooks/#{name}")
28
+ return false
29
+ rescue Spice::Error::NotFound
30
+ return true
31
+ end
32
+ end
15
33
  end
16
34
  end
@@ -2,23 +2,26 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class CookbookVersion
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "cookbooks"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :version, String
13
17
  attribute :definitions, Array, :default => []
14
- attribute :attributes, Array, :default => []
15
18
  attribute :files, Array, :default => []
16
19
  attribute :providers, Array, :default => []
17
20
  attribute :metadata, Hash, :default => {}
18
21
  attribute :libraries, Array, :default => []
19
22
  attribute :templates, Array, :default => []
20
23
  attribute :resources, Array, :default => []
21
- attribute :attributes, Array, :default => []
24
+ attribute :_attributes, Array, :default => []
22
25
  attribute :json_class, String, :default => "Chef::CookbookVersion"
23
26
  attribute :cookbook_name, String
24
27
  attribute :version, String
@@ -29,15 +32,37 @@ module Spice
29
32
  validates_presence_of :name
30
33
 
31
34
  def do_post
32
- connection.put("/cookbooks/#{cookbook_name}/#{version}", attributes)
35
+ duped_attributes = attributes.dup
36
+ duped_attributes[:attributes] = attributes[:_attributes]
37
+ duped_attributes.delete(:_attributes)
38
+
39
+ connection.put("/cookbooks/#{cookbook_name}/#{version}", duped_attributes)
33
40
  end
34
41
 
35
42
  def do_put
36
- connection.put("/cookbooks/#{cookbook_name}/#{version}", attributes)
43
+ duped_attributes = attributes.dup
44
+ duped_attributes[:attributes] = attributes[:_attributes]
45
+ duped_attributes.delete(:_attributes)
46
+
47
+ connection.put("/cookbooks/#{cookbook_name}/#{version}", duped_attributes)
37
48
  end
38
49
 
39
50
  def do_delete
40
51
  connection.delete("/cookbooks/#{cookbook_name}/#{version}")
41
52
  end
53
+
54
+ def self.get(name, version)
55
+ connection.cookbook_version(name, version)
56
+ end
57
+
58
+ # Check if the cookbook version exists on the Chef server
59
+ def new_record?
60
+ begin
61
+ connection.get("/cookbooks/#{name.split('-').first}/#{version}")
62
+ return false
63
+ rescue Spice::Error::NotFound
64
+ return true
65
+ end
66
+ end
42
67
  end
43
68
  end
@@ -2,12 +2,16 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class DataBag
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "data"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :items, Array, :default => []
13
17
 
@@ -24,5 +28,15 @@ module Spice
24
28
  def do_post
25
29
  response = connection.post("/data", attributes)
26
30
  end
31
+
32
+ # Check if the data bag exists on the Chef server
33
+ def new_record?
34
+ begin
35
+ connection.get("/data/#{name}")
36
+ return false
37
+ rescue Spice::Error::NotFound
38
+ return true
39
+ end
40
+ end
27
41
  end
28
42
  end
@@ -1,12 +1,20 @@
1
1
  module Spice
2
2
  class DataBagItem
3
- include Toy::Store
3
+ include Virtus
4
+ include Aequitas
4
5
  include Spice::Persistence
5
6
  extend Spice::Persistence
6
- store :memory, {}
7
+
7
8
  endpoint "data"
8
9
 
10
+ # The _id attribute is used as the "id" field in the data bag item.
11
+ # "id" is an attribute reserved by ToyStore, the attribute system used by Spice.
12
+ # @attribute [rw]
13
+ # @return [String] the _attribute
9
14
  attribute :_id, String
15
+ # @macro [attach] attribute
16
+ # @attribute [rw]
17
+ # @return [$2] the $1 attribute
10
18
  attribute :data, Hash, :default => {}
11
19
  attribute :name, String
12
20
 
@@ -18,18 +26,28 @@ module Spice
18
26
 
19
27
  def do_post
20
28
  attrs = data.dup
21
- attrs['id'] = attributes['_id']
29
+ attrs[:id] = attributes[:_id]
22
30
  connection.post("/data/#{name}", attrs)
23
31
  end
24
32
 
25
33
  def do_put
26
34
  attrs = data.dup
27
- attrs['id'] = attributes['_id']
35
+ attrs[:id] = attributes[:_id]
28
36
  connection.put("/data/#{name}/#{_id}", attrs)
29
37
  end
30
38
 
31
39
  def do_delete
32
40
  connection.delete("/data/#{name}/#{_id}")
33
41
  end
42
+
43
+ # Check if the data bag item exists on the Chef server
44
+ def new_record?
45
+ begin
46
+ connection.get("/data/#{name}/#{_id}")
47
+ return false
48
+ rescue Spice::Error::NotFound
49
+ return true
50
+ end
51
+ end
34
52
  end
35
53
  end
@@ -2,12 +2,16 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class Environment
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "environments"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :description, String
13
17
  attribute :attrs, Hash, :default => {}
@@ -17,5 +21,12 @@ module Spice
17
21
 
18
22
  validates_presence_of :name, :description
19
23
 
24
+ # Check if the environment exists on the Chef server
25
+ def new_record?
26
+ connection.get("/environments/#{name}")
27
+ return false
28
+ rescue Spice::Error::NotFound
29
+ return true
30
+ end
20
31
  end
21
32
  end
data/lib/spice/error.rb CHANGED
@@ -9,22 +9,22 @@ module Spice
9
9
 
10
10
  end
11
11
 
12
- class BadRequest < Error
12
+ class Error::BadRequest < Error
13
13
  end
14
14
 
15
- class Unauthorized < Error
15
+ class Error::Unauthorized < Error
16
16
  end
17
17
 
18
- class Forbidden < Error
18
+ class Error::Forbidden < Error
19
19
  end
20
20
 
21
- class NotFound < Error
21
+ class Error::NotFound < Error
22
22
  end
23
23
 
24
- class NotAcceptable < Error
24
+ class Error::NotAcceptable < Error
25
25
  end
26
26
 
27
- class Conflict < Error
27
+ class Error::Conflict < Error
28
28
  end
29
29
 
30
30
  end
data/lib/spice/node.rb CHANGED
@@ -2,12 +2,16 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class Node
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
- store :memory, {}
9
+
9
10
  endpoint "nodes"
10
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
11
15
  attribute :name, String
12
16
  attribute :chef_type, String, :default => "node"
13
17
  attribute :json_class, String, :default => "Chef::Node"
@@ -19,5 +23,18 @@ module Spice
19
23
 
20
24
  validates_presence_of :name
21
25
 
26
+ def self.get(name)
27
+ connection.node(name)
28
+ end
29
+
30
+ # Check if the node exists on the Chef server
31
+ def new_record?
32
+ begin
33
+ connection.get("/nodes/#{name}")
34
+ return false
35
+ rescue Spice::Error::NotFound
36
+ return true
37
+ end
38
+ end
22
39
  end
23
40
  end
@@ -1,9 +1,26 @@
1
1
  module Spice
2
2
  module Persistence
3
3
  def self.included(base)
4
- base.after_create :do_post
5
- base.after_update :do_put
6
- base.after_destroy :do_delete
4
+ base.extend ActiveModel::Callbacks
5
+ base.send(:define_model_callbacks, :create, :update, :destroy)
6
+ base.send(:after_create, :do_post)
7
+ base.send(:after_update, :do_put)
8
+ base.send(:after_destroy, :do_delete)
9
+ end
10
+
11
+ def create
12
+ run_callbacks :create do
13
+ end
14
+ end
15
+
16
+ def update
17
+ run_callbacks :update do
18
+ end
19
+ end
20
+
21
+ def destroy
22
+ run_callbacks :destroy do
23
+ end
7
24
  end
8
25
 
9
26
  def endpoint(ep=nil)
@@ -38,5 +55,8 @@ module Spice
38
55
  connection.delete("/#{self.class.endpoint}/#{name}")
39
56
  end
40
57
 
58
+ def new_record?
59
+ raise NotImplementedError, "Override this method in the class that includes this module."
60
+ end
41
61
  end
42
62
  end
@@ -7,22 +7,26 @@ module Spice
7
7
  def on_complete(env)
8
8
  case env[:status].to_i
9
9
  when 400
10
- raise Spice::BadRequest.new(error(env[:body]), env[:request_headers])
10
+ raise Spice::Error::BadRequest.new(error(env[:body]), env[:request_headers])
11
11
  when 401
12
- raise Spice::Unauthorized.new(error(env[:body]), env[:request_headers])
12
+ raise Spice::Error::Unauthorized.new(error(env[:body]), env[:request_headers])
13
13
  when 403
14
- raise Spice::Forbidden.new(error(env[:body]), env[:request_headers])
14
+ raise Spice::Error::Forbidden.new(error(env[:body]), env[:request_headers])
15
15
  when 404
16
- raise Spice::NotFound.new(error(env[:body]), env[:request_headers])
16
+ raise Spice::Error::NotFound.new(error(env[:body]), env[:request_headers])
17
17
  when 406
18
- raise Spice::NotAcceptable.new(error(env[:body]), env[:request_headers])
18
+ raise Spice::Error::NotAcceptable.new(error(env[:body]), env[:request_headers])
19
19
  when 409
20
- raise Spice::Conflict.new(error(env[:body]), env[:request_headers])
20
+ raise Spice::Error::Conflict.new(error(env[:body]), env[:request_headers])
21
21
  end
22
22
  end
23
23
 
24
24
  def error(body)
25
- body["error"].join(',')
25
+ if body["error"].kind_of?(Array)
26
+ body["error"].join(',')
27
+ else
28
+ body["error"]
29
+ end
26
30
  end
27
31
 
28
32
  end
data/lib/spice/role.rb CHANGED
@@ -2,13 +2,16 @@ require 'spice/persistence'
2
2
 
3
3
  module Spice
4
4
  class Role
5
- include Toy::Store
5
+ include Virtus
6
+ include Aequitas
6
7
  include Spice::Persistence
7
8
  extend Spice::Persistence
8
9
 
9
- store :memory, {}
10
10
  endpoint "roles"
11
11
 
12
+ # @macro [attach] attribute
13
+ # @attribute [rw]
14
+ # @return [$2] the $1 attribute
12
15
  attribute :name, String
13
16
  attribute :description, String
14
17
  attribute :run_list, Array, :default => []
@@ -18,6 +21,19 @@ module Spice
18
21
  attribute :chef_type, String, :default => "role"
19
22
 
20
23
  validates_presence_of :name, :description
21
-
24
+
25
+ def self.get(name)
26
+ connection.role(name)
27
+ end
28
+
29
+ # Check if the role exists on the Chef server
30
+ def new_record?
31
+ begin
32
+ connection.get("/roles/#{name}")
33
+ return false
34
+ rescue Spice::Error::NotFound
35
+ return true
36
+ end
37
+ end
22
38
  end
23
39
  end
data/lib/spice/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spice
2
- VERSION = "1.0.0.pre"
2
+ VERSION = "1.0.0.rc"
3
3
  end
data/spice.gemspec CHANGED
@@ -14,8 +14,9 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "spice"
16
16
 
17
- s.add_dependency "toystore", ">= 0.8.2"
18
- s.add_dependency "adapter", ">= 0.5.2"
17
+ s.add_dependency "virtus", ">= 0.3.0"
18
+ s.add_dependency "aequitas", ">= 0.0.2"
19
+ s.add_dependency "activemodel"
19
20
  s.add_dependency "faraday"
20
21
  s.add_dependency "mixlib-authentication", '>= 1.1.4'
21
22
  s.add_dependency "yajl-ruby", '>= 1.1.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spice
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre
4
+ version: 1.0.0.rc
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,44 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-16 00:00:00.000000000Z
12
+ date: 2012-03-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: toystore
16
- requirement: &70283810072460 !ruby/object:Gem::Requirement
15
+ name: virtus
16
+ requirement: &70252408715180 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 0.8.2
21
+ version: 0.3.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70283810072460
24
+ version_requirements: *70252408715180
25
25
  - !ruby/object:Gem::Dependency
26
- name: adapter
27
- requirement: &70283810071860 !ruby/object:Gem::Requirement
26
+ name: aequitas
27
+ requirement: &70252408714580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.5.2
32
+ version: 0.0.2
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70283810071860
35
+ version_requirements: *70252408714580
36
+ - !ruby/object:Gem::Dependency
37
+ name: activemodel
38
+ requirement: &70252408713960 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70252408713960
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: faraday
38
- requirement: &70283810071480 !ruby/object:Gem::Requirement
49
+ requirement: &70252408713220 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,10 +54,10 @@ dependencies:
43
54
  version: '0'
44
55
  type: :runtime
45
56
  prerelease: false
46
- version_requirements: *70283810071480
57
+ version_requirements: *70252408713220
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: mixlib-authentication
49
- requirement: &70283810070940 !ruby/object:Gem::Requirement
60
+ requirement: &70252408711780 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: 1.1.4
55
66
  type: :runtime
56
67
  prerelease: false
57
- version_requirements: *70283810070940
68
+ version_requirements: *70252408711780
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: yajl-ruby
60
- requirement: &70283810070300 !ruby/object:Gem::Requirement
71
+ requirement: &70252408710960 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,10 +76,10 @@ dependencies:
65
76
  version: 1.1.0
66
77
  type: :runtime
67
78
  prerelease: false
68
- version_requirements: *70283810070300
79
+ version_requirements: *70252408710960
69
80
  - !ruby/object:Gem::Dependency
70
81
  name: rspec
71
- requirement: &70283810069820 !ruby/object:Gem::Requirement
82
+ requirement: &70252408709640 !ruby/object:Gem::Requirement
72
83
  none: false
73
84
  requirements:
74
85
  - - ! '>='
@@ -76,10 +87,10 @@ dependencies:
76
87
  version: 2.6.0
77
88
  type: :development
78
89
  prerelease: false
79
- version_requirements: *70283810069820
90
+ version_requirements: *70252408709640
80
91
  - !ruby/object:Gem::Dependency
81
92
  name: webmock
82
- requirement: &70283810069360 !ruby/object:Gem::Requirement
93
+ requirement: &70252408709080 !ruby/object:Gem::Requirement
83
94
  none: false
84
95
  requirements:
85
96
  - - ! '>='
@@ -87,10 +98,10 @@ dependencies:
87
98
  version: 1.6.2
88
99
  type: :development
89
100
  prerelease: false
90
- version_requirements: *70283810069360
101
+ version_requirements: *70252408709080
91
102
  - !ruby/object:Gem::Dependency
92
103
  name: timecop
93
- requirement: &70283810068880 !ruby/object:Gem::Requirement
104
+ requirement: &70252408708260 !ruby/object:Gem::Requirement
94
105
  none: false
95
106
  requirements:
96
107
  - - ! '>='
@@ -98,10 +109,10 @@ dependencies:
98
109
  version: 0.3.5
99
110
  type: :development
100
111
  prerelease: false
101
- version_requirements: *70283810068880
112
+ version_requirements: *70252408708260
102
113
  - !ruby/object:Gem::Dependency
103
114
  name: guard
104
- requirement: &70283810068280 !ruby/object:Gem::Requirement
115
+ requirement: &70252408707260 !ruby/object:Gem::Requirement
105
116
  none: false
106
117
  requirements:
107
118
  - - ! '>='
@@ -109,10 +120,10 @@ dependencies:
109
120
  version: 0.6.2
110
121
  type: :development
111
122
  prerelease: false
112
- version_requirements: *70283810068280
123
+ version_requirements: *70252408707260
113
124
  - !ruby/object:Gem::Dependency
114
125
  name: guard-rspec
115
- requirement: &70283810067820 !ruby/object:Gem::Requirement
126
+ requirement: &70252408705920 !ruby/object:Gem::Requirement
116
127
  none: false
117
128
  requirements:
118
129
  - - ! '>='
@@ -120,10 +131,10 @@ dependencies:
120
131
  version: 0.4.2
121
132
  type: :development
122
133
  prerelease: false
123
- version_requirements: *70283810067820
134
+ version_requirements: *70252408705920
124
135
  - !ruby/object:Gem::Dependency
125
136
  name: guard-spork
126
- requirement: &70283810067360 !ruby/object:Gem::Requirement
137
+ requirement: &70252408705040 !ruby/object:Gem::Requirement
127
138
  none: false
128
139
  requirements:
129
140
  - - ! '>='
@@ -131,10 +142,10 @@ dependencies:
131
142
  version: 0.2.1
132
143
  type: :development
133
144
  prerelease: false
134
- version_requirements: *70283810067360
145
+ version_requirements: *70252408705040
135
146
  - !ruby/object:Gem::Dependency
136
147
  name: spork
137
- requirement: &70283810066740 !ruby/object:Gem::Requirement
148
+ requirement: &70252408704200 !ruby/object:Gem::Requirement
138
149
  none: false
139
150
  requirements:
140
151
  - - ! '>='
@@ -142,10 +153,10 @@ dependencies:
142
153
  version: 0.9.0.rc8
143
154
  type: :development
144
155
  prerelease: false
145
- version_requirements: *70283810066740
156
+ version_requirements: *70252408704200
146
157
  - !ruby/object:Gem::Dependency
147
158
  name: rb-fsevent
148
- requirement: &70283810066280 !ruby/object:Gem::Requirement
159
+ requirement: &70252408703560 !ruby/object:Gem::Requirement
149
160
  none: false
150
161
  requirements:
151
162
  - - ! '>='
@@ -153,10 +164,10 @@ dependencies:
153
164
  version: 0.4.3.1
154
165
  type: :development
155
166
  prerelease: false
156
- version_requirements: *70283810066280
167
+ version_requirements: *70252408703560
157
168
  - !ruby/object:Gem::Dependency
158
169
  name: growl
159
- requirement: &70283810065820 !ruby/object:Gem::Requirement
170
+ requirement: &70252408703080 !ruby/object:Gem::Requirement
160
171
  none: false
161
172
  requirements:
162
173
  - - ! '>='
@@ -164,7 +175,7 @@ dependencies:
164
175
  version: 1.0.3
165
176
  type: :development
166
177
  prerelease: false
167
- version_requirements: *70283810065820
178
+ version_requirements: *70252408703080
168
179
  description: Spice is a zesty Chef API wrapper. Its primary purpose is to let you
169
180
  easily integrate your apps with a Chef server easily. Spice provides support for
170
181
  the entire released Chef API
@@ -187,7 +198,6 @@ files:
187
198
  - TODO.md
188
199
  - lib/spice.rb
189
200
  - lib/spice/authentication.rb
190
- - lib/spice/chef.rb
191
201
  - lib/spice/client.rb
192
202
  - lib/spice/config.rb
193
203
  - lib/spice/connection.rb
@@ -250,7 +260,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
250
260
  version: '0'
251
261
  segments:
252
262
  - 0
253
- hash: -3673399458224302804
263
+ hash: -2243516170777733948
254
264
  required_rubygems_version: !ruby/object:Gem::Requirement
255
265
  none: false
256
266
  requirements:
@@ -259,7 +269,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
269
  version: 1.3.1
260
270
  requirements: []
261
271
  rubyforge_project: spice
262
- rubygems_version: 1.8.10
272
+ rubygems_version: 1.8.17
263
273
  signing_key:
264
274
  specification_version: 3
265
275
  summary: Chef API wrapper
data/lib/spice/chef.rb DELETED
@@ -1,32 +0,0 @@
1
- module Spice
2
- class Chef
3
- attr_accessor :client_name, :key_file, :connection
4
-
5
- def self.connection
6
- @connection ||= Spice.connection
7
- end
8
-
9
- class << self
10
- def clients
11
- @clients ||= Client.all
12
- end
13
-
14
- def nodes
15
- @nodes ||= Node.all
16
- end
17
-
18
- def data_bags
19
- @data_bags ||= DataBag.all
20
- end
21
-
22
- def roles
23
- @roles ||= Role.all
24
- end
25
-
26
- def cookbooks
27
- @cookbooks ||= Cookbook.all
28
- end
29
- end
30
- end
31
- end
32
-