spice 1.0.0.rc → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +0 -14
  3. data/README.md +1 -1
  4. data/Rakefile +1 -1
  5. data/TODO.md +2 -1
  6. data/lib/spice.rb +47 -72
  7. data/lib/spice/authentication.rb +23 -45
  8. data/lib/spice/base.rb +104 -0
  9. data/lib/spice/client.rb +8 -48
  10. data/lib/spice/config.rb +37 -19
  11. data/lib/spice/connection.rb +33 -117
  12. data/lib/spice/connection/clients.rb +26 -6
  13. data/lib/spice/connection/cookbooks.rb +19 -14
  14. data/lib/spice/connection/data_bags.rb +31 -20
  15. data/lib/spice/connection/environments.rb +6 -7
  16. data/lib/spice/connection/nodes.rb +23 -6
  17. data/lib/spice/connection/roles.rb +5 -5
  18. data/lib/spice/connection/search.rb +12 -12
  19. data/lib/spice/cookbook.rb +3 -29
  20. data/lib/spice/cookbook_version.rb +9 -61
  21. data/lib/spice/core_ext/array.rb +7 -0
  22. data/lib/spice/core_ext/enumerable.rb +11 -0
  23. data/lib/spice/core_ext/hash.rb +49 -0
  24. data/lib/spice/core_ext/mash.rb +219 -0
  25. data/lib/spice/data_bag.rb +3 -37
  26. data/lib/spice/data_bag_item.rb +7 -47
  27. data/lib/spice/environment.rb +12 -27
  28. data/lib/spice/error.rb +22 -10
  29. data/lib/spice/identity_map.rb +8 -0
  30. data/lib/spice/node.rb +13 -34
  31. data/lib/spice/request.rb +73 -11
  32. data/lib/spice/response/parse_json.rb +8 -3
  33. data/lib/spice/role.rb +8 -33
  34. data/lib/spice/version.rb +1 -1
  35. data/spec/fixtures/client.pem +27 -0
  36. data/spec/fixtures/clients/create.json +4 -0
  37. data/spec/fixtures/clients/index.json +5 -0
  38. data/spec/fixtures/clients/reregister.json +5 -0
  39. data/spec/fixtures/clients/show.json +8 -0
  40. data/spec/fixtures/clients/update.json +5 -0
  41. data/spec/fixtures/cookbook_versions/show.json +108 -0
  42. data/spec/fixtures/cookbook_versions/update.json +62 -0
  43. data/spec/fixtures/cookbooks/index-0.10.json +28 -0
  44. data/spec/fixtures/cookbooks/index-0.9.json +4 -0
  45. data/spec/fixtures/cookbooks/show-0.10.json +15 -0
  46. data/spec/fixtures/cookbooks/show-apache2-0.9.json +5 -0
  47. data/spec/fixtures/cookbooks/show-unicorn-0.9.json +5 -0
  48. data/spec/fixtures/data_bag_items/create.json +1 -0
  49. data/spec/fixtures/data_bag_items/show.json +4 -0
  50. data/spec/fixtures/data_bag_items/update.json +4 -0
  51. data/spec/fixtures/data_bags/create.json +3 -0
  52. data/spec/fixtures/data_bags/index.json +3 -0
  53. data/spec/fixtures/data_bags/show.json +3 -0
  54. data/spec/fixtures/environments/cookbook.json +15 -0
  55. data/spec/fixtures/environments/cookbooks.json +28 -0
  56. data/spec/fixtures/environments/create.json +1 -0
  57. data/spec/fixtures/environments/delete.json +8 -0
  58. data/spec/fixtures/environments/index.json +3 -0
  59. data/spec/fixtures/environments/show.json +8 -0
  60. data/spec/fixtures/environments/update.json +8 -0
  61. data/spec/fixtures/nodes/cookbooks.json +41 -0
  62. data/spec/fixtures/nodes/create.json +1 -0
  63. data/spec/fixtures/nodes/delete.json +17 -0
  64. data/spec/fixtures/nodes/index.json +3 -0
  65. data/spec/fixtures/nodes/show.json +17 -0
  66. data/spec/fixtures/nodes/update.json +13 -0
  67. data/spec/fixtures/roles/create.json +1 -0
  68. data/spec/fixtures/roles/delete.json +11 -0
  69. data/spec/fixtures/roles/index.json +3 -0
  70. data/spec/fixtures/roles/show.json +12 -0
  71. data/spec/fixtures/roles/update.json +11 -0
  72. data/spec/fixtures/search/client.json +1 -0
  73. data/spec/fixtures/search/data_bag.json +1 -0
  74. data/spec/fixtures/search/environment.json +1 -0
  75. data/spec/fixtures/search/node.json +1 -0
  76. data/spec/fixtures/search/role.json +1 -0
  77. data/spec/spec_helper.rb +5 -4
  78. data/spec/spice/base_spec.rb +34 -0
  79. data/spec/spice/client_spec.rb +0 -61
  80. data/spec/spice/config_spec.rb +14 -0
  81. data/spec/spice/connection/clients_spec.rb +82 -0
  82. data/spec/spice/connection/cookbooks_spec.rb +86 -0
  83. data/spec/spice/connection/data_bags_spec.rb +126 -0
  84. data/spec/spice/connection_spec.rb +63 -0
  85. data/spec/spice/cookbook_spec.rb +0 -61
  86. data/spec/spice/data_bag_item_spec.rb +7 -0
  87. data/spec/spice/data_bag_spec.rb +0 -160
  88. data/spec/spice/environment_spec.rb +0 -89
  89. data/spec/spice/node_spec.rb +0 -74
  90. data/spec/spice/role_spec.rb +0 -61
  91. data/spec/spice_spec.rb +8 -116
  92. data/spec/support/helpers.rb +58 -0
  93. data/spice.gemspec +13 -15
  94. metadata +227 -87
  95. data/.watchr +0 -22
  96. data/lib/spice/connection/authentication.rb +0 -47
  97. data/lib/spice/mock.rb +0 -46
  98. data/lib/spice/persistence.rb +0 -62
  99. data/spec/spice/chef_spec.rb +0 -52
  100. data/spec/spice/search_spec.rb +0 -2
  101. data/spec/support/chef_requests.rb +0 -0
  102. data/spec/support/client_requests.rb +0 -95
  103. data/spec/support/cookbook_requests.rb +0 -95
  104. data/spec/support/data_bag_item_requests.rb +0 -138
  105. data/spec/support/data_bag_requests.rb +0 -95
  106. data/spec/support/environment_requests.rb +0 -221
  107. data/spec/support/node_requests.rb +0 -138
  108. data/spec/support/respond_with_matcher.rb +0 -53
  109. data/spec/support/role_requests.rb +0 -95
@@ -1,8 +1,10 @@
1
- require 'yajl'
2
- require 'uri'
3
-
1
+ require 'spice/config'
4
2
  require 'spice/request'
5
- require 'spice/connection/authentication'
3
+ require 'spice/authentication'
4
+ require 'spice/error'
5
+
6
+ require 'spice/core_ext/enumerable'
7
+
6
8
  require 'spice/connection/search'
7
9
  require 'spice/connection/clients'
8
10
  require 'spice/connection/cookbooks'
@@ -10,15 +12,18 @@ require 'spice/connection/data_bags'
10
12
  require 'spice/connection/environments'
11
13
  require 'spice/connection/nodes'
12
14
  require 'spice/connection/roles'
13
- require 'spice/connection/search'
14
-
15
15
 
16
+ require 'spice/client'
17
+ require 'spice/cookbook'
18
+ require 'spice/cookbook_version'
19
+ require 'spice/data_bag'
20
+ require 'spice/data_bag_item'
21
+ require 'spice/environment'
22
+ require 'spice/node'
23
+ require 'spice/role'
16
24
 
17
25
  module Spice
18
26
  class Connection
19
- include Virtus
20
- include Aequitas
21
-
22
27
  include Spice::Connection::Clients
23
28
  include Spice::Connection::Cookbooks
24
29
  include Spice::Connection::DataBags
@@ -26,118 +31,29 @@ module Spice
26
31
  include Spice::Connection::Nodes
27
32
  include Spice::Connection::Roles
28
33
  include Spice::Connection::Search
29
- include Spice::Connection::Authentication
30
- include Spice::Connection::Search
31
34
  include Spice::Request
32
-
33
- # @macro [attach] attribute
34
- # @attribute [rw]
35
- # @return [$2] the $1 attribute
36
- attribute :client_name, String
37
- attribute :key_file, String
38
- attribute :key, String
39
- attribute :server_url, String
40
- attribute :sign_on_redirect, Boolean, :default => true
41
- attribute :sign_request, Boolean, :default => true
42
- attribute :endpoint, String
43
-
44
- validates_presence_of :client_name, :key_file, :server_url
35
+ include Spice::Authentication
36
+
37
+ # @private
45
38
 
46
- def connection
47
- self
39
+ Config::VALID_OPTIONS_KEYS.each do |key|
40
+ attr_accessor key
48
41
  end
49
42
 
50
- def parsed_url
51
- URI.parse(server_url)
52
- end
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")
59
- def get(path)
60
- response = request(:headers => build_headers(
61
- :GET,
62
- "#{parsed_url.path}#{path}")
63
- ).get(
64
- "#{server_url}#{path}"
65
- )
66
- return response
67
- end # def get
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")
75
- def post(path, payload)
76
- response = request(:headers => build_headers(
77
- :POST,
78
- "#{parsed_url.path}#{path}",
79
- Yajl.dump(payload))
80
- ).post(
81
- "#{server_url}#{path}",
82
- Yajl.dump(payload)
83
- )
84
- return response
85
- end # def post
86
-
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)
94
- response = request(:headers => build_headers(
95
- :PUT,
96
- "#{parsed_url.path}#{path}",
97
- Yajl.dump(payload))
98
- ).put(
99
- "#{server_url}#{path}",
100
- Yajl.dump(payload)
101
- )
102
- return response
103
- end # def put
43
+ def initialize(attrs=Mash.new)
44
+ attrs = Spice.options.merge(attrs)
45
+ Config::VALID_OPTIONS_KEYS.each do |key|
46
+ instance_variable_set("@#{key}".to_sym, attrs[key])
47
+ end
48
+ end # def initialize
49
+
50
+ def sign_on_redirect
51
+ @sign_on_redirect ||= true
52
+ end # def sign_on_redirect
53
+
54
+ def sign_request
55
+ @sign_request ||= true
56
+ end # def sign_request
104
57
 
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)
111
- response = request(:headers => build_headers(
112
- :DELETE,
113
- "#{parsed_url.path}#{path}")
114
- ).delete(
115
- "#{server_url}#{path}"
116
- )
117
- return response
118
- end # def delete
119
-
120
- private
121
-
122
- def authentication_headers(method, path, json_body=nil)
123
- request_params = {
124
- :http_method => method,
125
- :path => path.gsub(/\?.*$/, ''),
126
- :body => json_body,
127
- :host => "#{parsed_url.host}:#{parsed_url.port}"
128
- }
129
- request_params[:body] ||= ""
130
- signature_headers(request_params)
131
- end # def authentication_headers
132
-
133
- def build_headers(method, path, json_body=nil)
134
- headers={}
135
- # headers['Accept'] = "application/json"
136
- # headers["Content-Type"] = 'application/json' if json_body
137
- headers['Content-Length'] = json_body.bytesize.to_s if json_body
138
- headers.merge!(authentication_headers(method, path, json_body)) if sign_requests?
139
- headers
140
- end # def build_headers
141
-
142
58
  end # class Connection
143
59
  end # module Spice
@@ -7,9 +7,9 @@ module Spice
7
7
  # @see Search#search
8
8
  # @example Retrieve all clients with names that begin with "app"
9
9
  # Spice.clients(:q => "name:app*")
10
- def clients(options={})
11
- connection.search('client', options)
12
- end
10
+ def clients(options=Mash.new)
11
+ search('client', options)
12
+ end # def clients
13
13
 
14
14
  # Retrieve a single client
15
15
  # @param [String] name The client name
@@ -18,10 +18,30 @@ module Spice
18
18
  # @example Retrieve the client named "admin"
19
19
  # Spice.client("name")
20
20
  def client(name)
21
- attributes = connection.get("/clients/#{name}").body
22
- Spice::Client.new(attributes)
23
- end
21
+ attributes = get("/clients/#{name}")
22
+ Spice::Client.get_or_new(attributes)
23
+ end # def client
24
+
25
+ def create_client(params=Mash.new)
26
+ attributes = post("/clients", params)
27
+ Spice::Client.get_or_new(attributes)
28
+ end # def create_client
29
+
30
+ def update_client(name, params=Mash.new)
31
+ attributes = put("/clients/#{name}", params)
32
+ Spice::Client.get_or_new(attributes)
33
+ end # def update_client
34
+
35
+ def delete_client(name)
36
+ client = delete("/clients/#{name}")
37
+ nil
38
+ end # def delete_client
24
39
 
40
+ def reregister_client(name)
41
+ attributes = put("/clients/#{name}", :private_key => true)
42
+ Spice::Client.get_or_new(attributes)
43
+ end # def reregister_client
44
+
25
45
  end # module Clients
26
46
  end # class Connection
27
47
  end # module Spice
@@ -8,18 +8,18 @@ module Spice
8
8
  def cookbooks
9
9
  if Gem::Version.new(Spice.chef_version) >= Gem::Version.new("0.10.0")
10
10
  cookbooks = []
11
- connection.get("/cookbooks").body.each_pair do |key, value|
11
+ get("/cookbooks?num_versions=all").each_pair do |key, value|
12
12
  versions = value['versions'].map{ |v| v['version'] }
13
- cookbooks << Spice::Cookbook.new(:name => key, :versions => versions)
13
+ cookbooks << Spice::Cookbook.get_or_new(:name => key, :versions => versions)
14
14
  end
15
15
  cookbooks
16
16
  else
17
- connection.get("/cookbooks").body.keys.map do |cookbook|
18
- attributes = connection.get("/cookbooks/#{cookbook}").body.to_a[0]
19
- Spice::Cookbook.new(:name => attributes[0], :versions => attributes[1])
17
+ get("/cookbooks").keys.map do |cookbook|
18
+ cb = get("/cookbooks/#{cookbook}")
19
+ Spice::Cookbook.get_or_new(:name => cookbook, :versions => cb[cookbook])
20
20
  end
21
21
  end
22
- end
22
+ end # def cookbooks
23
23
 
24
24
  # Retrieve a single cookbook
25
25
  # @param [String] name The name of the cookbook
@@ -27,15 +27,15 @@ module Spice
27
27
  # @raise [Spice::Error::NotFound] raised when cookbook does not exist
28
28
  def cookbook(name)
29
29
  if Gem::Version.new(Spice.chef_version) >= Gem::Version.new("0.10.0")
30
- cookbook = connection.get("/cookbooks/#{name}").body
30
+ cookbook = get("/cookbooks/#{name}")
31
31
  versions = cookbook[name]['versions'].map{ |v| v['version'] }
32
32
 
33
- Spice::Cookbook.new(:name => name, :versions => versions)
33
+ Spice::Cookbook.get_or_new(:name => name, :versions => versions)
34
34
  else
35
- cookbook = connection.get("/cookbooks/#{name}").body
36
- Spice::Cookbook.new(:name => name, :versions => cookbook[name])
35
+ cookbook = get("/cookbooks/#{name}")
36
+ Spice::Cookbook.get_or_new(:name => name, :versions => cookbook[name])
37
37
  end
38
- end
38
+ end # def cookbook
39
39
 
40
40
  # Retrieve a single cookbook version
41
41
  # @param [String] name The cookbook name
@@ -43,11 +43,16 @@ module Spice
43
43
  # @return [Spice::CookbookVersion]
44
44
  # @raise [Spice::Error::NotFound] raised when cookbook version does not exist
45
45
  def cookbook_version(name, version)
46
- attributes = connection.get("/cookbooks/#{name}/#{version}").body
46
+ attributes = get("/cookbooks/#{name}/#{version}")
47
47
  duped_attributes = attributes.dup
48
48
  duped_attributes[:_attributes] = attributes['attributes']
49
- Spice::CookbookVersion.new(duped_attributes)
50
- end
49
+ Spice::CookbookVersion.get_or_new(duped_attributes)
50
+ end # def cookbook_version
51
+
52
+ def delete_cookbook_version(name, version)
53
+ delete("/cookbooks/#{name}/#{version}")
54
+ nil
55
+ end # def delete_cookbook_version
51
56
 
52
57
  end # module Cookbooks
53
58
  end # class Connection
@@ -6,11 +6,11 @@ module Spice
6
6
  # @example Retrieve all data bags
7
7
  # Spice.data_bags
8
8
  def data_bags
9
- connection.get("/data").body.keys.map do |data_bag|
10
- items = connection.search(data_bag)
11
- Spice::DataBag.new(:name => data_bag, :items => items)
9
+ get("/data").keys.map do |data_bag|
10
+ items = search(data_bag)
11
+ Spice::DataBag.get_or_new(:name => data_bag, :items => items)
12
12
  end
13
- end
13
+ end # def data_bags
14
14
 
15
15
  alias :data :data_bags
16
16
 
@@ -19,9 +19,9 @@ module Spice
19
19
  # @return [Spice::DataBag]
20
20
  # @raise [Spice::Error::NotFound] raised when data bag does not exist
21
21
  def data_bag(name)
22
- items = connection.search(name)
23
- Spice::DataBag.new(:name => name, :items => items)
24
- end
22
+ items = search(name)
23
+ Spice::DataBag.get_or_new(:name => name, :items => items)
24
+ end # def data_bag
25
25
 
26
26
  # Retrieve a single data bag item
27
27
  # @param [String] name The data bag name
@@ -29,20 +29,31 @@ module Spice
29
29
  # @return [Spice::DataBagItem]
30
30
  # @raise [Spice::Error::NotFound] raised when data bag item does not exist
31
31
  def data_bag_item(name, id)
32
- data = connection.get("/data/#{name}/#{id}")
33
- data.delete('id')
34
- Spice::DataBagItem.new(:_id => id, :data => data, :name => name)
35
- end
36
-
37
- private
38
-
39
- def get_data_bag_items(name)
40
- connection.get("/data/#{name}").body.keys.map do |id|
41
- data = connection.get("/data/#{name}/#{id}").body
42
- Spice::DataBagItem.new(:_id => id, :data => data, :name => name)
43
- end
44
- end
32
+ data_bag_item = get("/data/#{name}/#{id}")
33
+ Spice::DataBagItem.get_or_new(data_bag_item)
34
+ end # def data_bag_item
45
35
 
36
+ def create_data_bag(name)
37
+ attributes = post("/data", :name => name)
38
+ Spice::DataBag.get_or_new(:name => name, :items => [])
39
+ end # def create_data_bag
40
+
41
+ def create_data_bag_item(name, params=Mash.new)
42
+ attributes = post("/data/#{name}", params)
43
+ Spice::DataBagItem.get_or_new(attributes)
44
+ end # def create_data_bag_item
45
+
46
+ def update_data_bag_item(name, id, params=Mash.new)
47
+ params.merge!(:id => id)
48
+ attributes = put("/data/#{name}/#{id}", params)
49
+ Spice::DataBagItem.get_or_new(attributes)
50
+ end # update_data_bag_item
51
+
52
+ def delete_data_bag_item(name, id)
53
+ delete("/data/#{name}/#{id}")
54
+ nil
55
+ end # def delete_data_bag_item
56
+
46
57
  end # module DataBags
47
58
  end # class Connection
48
59
  end # module Spice
@@ -7,9 +7,9 @@ module Spice
7
7
  # @see Search#search
8
8
  # @example Retrieve all environments with names that begin with "prod"
9
9
  # Spice.environments(:q => "name:prod*")
10
- def environments(options={})
11
- connection.search('environment', options)
12
- end
10
+ def environments(options=Mash.new)
11
+ search('environment', options)
12
+ end # def environment
13
13
 
14
14
  # Retrieve a single environment
15
15
  # @param [String] name The environment name
@@ -18,10 +18,9 @@ module Spice
18
18
  # @example Retrieve the environment named "production"
19
19
  # Spice.environment("production")
20
20
  def environment(name)
21
- attributes = connection.get("/environments/#{name}").body
22
- attributes['attrs'] = attributes.delete('attributes')
23
- Spice::Environment.new(attributes)
24
- end
21
+ attributes = get("/environments/#{name}")
22
+ Spice::Environment.get_or_new(attributes)
23
+ end # def environment
25
24
 
26
25
  end # module Environments
27
26
  end # class Connection
@@ -9,9 +9,9 @@ module Spice
9
9
  # Spice.nodes(:q => "roles:base")
10
10
  # @example Retrieve nodes with role "base" in the "production" environment
11
11
  # Spice.nodes(:q => "roles:base AND environment:production")
12
- def nodes(options={})
13
- connection.search('node', options)
14
- end
12
+ def nodes(options=Mash.new)
13
+ search('node', options)
14
+ end # def nodes
15
15
 
16
16
  # Retrieve a single client
17
17
  # @param [String] name The node name
@@ -20,10 +20,27 @@ module Spice
20
20
  # @example Retrieve the node named "app.example.com"
21
21
  # Spice.node("app.example.com")
22
22
  def node(name)
23
- node_attributes = connection.get("/nodes/#{name}").body
24
- Spice::Node.new(node_attributes)
25
- end
23
+ attributes = get("/nodes/#{name}")
24
+ Spice::Node.get_or_new(attributes)
25
+ end # def node
26
26
 
27
+ alias :get_node :node
28
+
29
+ def create_node(params=Mash.new)
30
+ node = Spice::Node.new(params)
31
+ node_attributes = post("/nodes", node.to_hash)
32
+ get_node("/nodes/#{node.name}")
33
+ Spice::Node.get_or_new(attributes)
34
+ end # def create_node
35
+
36
+ # TODO(dryan): be able to update a node
37
+ # def update_node(params=Mash.new)
38
+ # node = Spice::Node.new(params)
39
+ # node.attrs.update get_node(node.name)
40
+ # attributes = put("/nodes/#{node.name}", node.attrs)
41
+ # Spice::Node.get_or_new(attributes)
42
+ # end
43
+
27
44
  end # module Nodes
28
45
  end # class Connection
29
46
  end # module Spice
@@ -7,9 +7,9 @@ module Spice
7
7
  # @see Search#search
8
8
  # @example Retrieve all roles that start with "app_"
9
9
  # Spice.roles(:q => "name:app_*")
10
- def roles(options={})
11
- connection.search('role', options)
12
- end
10
+ def roles(options=Mash.new)
11
+ search('role', options)
12
+ end # def roles
13
13
 
14
14
  # Retrieve a single role
15
15
  # @param [String] name The role name
@@ -18,9 +18,9 @@ module Spice
18
18
  # @example Retrieve the role "app_server"
19
19
  # Spice.role("app_server")
20
20
  def role(name)
21
- attributes = connection.get("/roles/#{name}").body
21
+ attributes = get("/roles/#{name}")
22
22
  Spice::Role.new(attributes)
23
- end
23
+ end # def role
24
24
 
25
25
  end # module Roles
26
26
  end # class Connection