spice 1.0.0.pre → 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
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
-