ridley 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +20 -0
  5. data/LICENSE +201 -0
  6. data/README.md +273 -0
  7. data/Thorfile +48 -0
  8. data/lib/ridley.rb +48 -0
  9. data/lib/ridley/connection.rb +131 -0
  10. data/lib/ridley/context.rb +25 -0
  11. data/lib/ridley/dsl.rb +58 -0
  12. data/lib/ridley/errors.rb +82 -0
  13. data/lib/ridley/log.rb +10 -0
  14. data/lib/ridley/middleware.rb +19 -0
  15. data/lib/ridley/middleware/chef_auth.rb +45 -0
  16. data/lib/ridley/middleware/chef_response.rb +28 -0
  17. data/lib/ridley/middleware/parse_json.rb +107 -0
  18. data/lib/ridley/resource.rb +305 -0
  19. data/lib/ridley/resources/client.rb +75 -0
  20. data/lib/ridley/resources/cookbook.rb +27 -0
  21. data/lib/ridley/resources/data_bag.rb +75 -0
  22. data/lib/ridley/resources/data_bag_item.rb +186 -0
  23. data/lib/ridley/resources/environment.rb +45 -0
  24. data/lib/ridley/resources/node.rb +34 -0
  25. data/lib/ridley/resources/role.rb +33 -0
  26. data/lib/ridley/version.rb +3 -0
  27. data/ridley.gemspec +39 -0
  28. data/spec/acceptance/client_resource_spec.rb +135 -0
  29. data/spec/acceptance/cookbook_resource_spec.rb +46 -0
  30. data/spec/acceptance/data_bag_item_resource_spec.rb +171 -0
  31. data/spec/acceptance/data_bag_resource_spec.rb +51 -0
  32. data/spec/acceptance/environment_resource_spec.rb +171 -0
  33. data/spec/acceptance/node_resource_spec.rb +218 -0
  34. data/spec/acceptance/role_resource_spec.rb +200 -0
  35. data/spec/fixtures/reset.pem +27 -0
  36. data/spec/spec_helper.rb +25 -0
  37. data/spec/support/each_matcher.rb +12 -0
  38. data/spec/support/shared_examples/ridley_resource.rb +237 -0
  39. data/spec/support/spec_helpers.rb +11 -0
  40. data/spec/unit/ridley/connection_spec.rb +167 -0
  41. data/spec/unit/ridley/errors_spec.rb +34 -0
  42. data/spec/unit/ridley/middleware/chef_auth_spec.rb +14 -0
  43. data/spec/unit/ridley/middleware/chef_response_spec.rb +213 -0
  44. data/spec/unit/ridley/middleware/parse_json_spec.rb +74 -0
  45. data/spec/unit/ridley/resource_spec.rb +214 -0
  46. data/spec/unit/ridley/resources/client_spec.rb +47 -0
  47. data/spec/unit/ridley/resources/cookbook_spec.rb +5 -0
  48. data/spec/unit/ridley/resources/data_bag_item_spec.rb +42 -0
  49. data/spec/unit/ridley/resources/data_bag_spec.rb +15 -0
  50. data/spec/unit/ridley/resources/environment_spec.rb +73 -0
  51. data/spec/unit/ridley/resources/node_spec.rb +5 -0
  52. data/spec/unit/ridley/resources/role_spec.rb +5 -0
  53. data/spec/unit/ridley_spec.rb +32 -0
  54. metadata +451 -0
@@ -0,0 +1,27 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class Cookbook
4
+ include Ridley::Resource
5
+
6
+ set_chef_id "name"
7
+ set_chef_type "cookbook"
8
+ set_chef_json_class "Chef::Cookbook"
9
+ set_resource_path "cookbooks"
10
+
11
+ attribute :name
12
+ validates_presence_of :name
13
+ end
14
+
15
+ module DSL
16
+ # Coerces instance functions into class functions on Ridley::Cookbook. This coercion
17
+ # sends an instance of the including class along to the class function.
18
+ #
19
+ # @see Ridley::Context
20
+ #
21
+ # @return [Ridley::Context]
22
+ # a context object to delegate instance functions to class functions on Ridley::Cookbook
23
+ def cookbook
24
+ Context.new(Ridley::Cookbook, self)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,75 @@
1
+ module Ridley
2
+ # @api private
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
4
+ class DBIContext
5
+ attr_reader :data_bag
6
+ attr_reader :connection
7
+
8
+ # @param [Ridley::DataBag] data_bag
9
+ def initialize(data_bag, connection)
10
+ @data_bag = data_bag
11
+ @connection = connection
12
+ end
13
+
14
+ def new(*args)
15
+ Ridley::DataBagItem.send(:new, connection, data_bag, *args)
16
+ end
17
+
18
+ def method_missing(fun, *args, &block)
19
+ Ridley::DataBagItem.send(fun, connection, data_bag, *args, &block)
20
+ end
21
+ end
22
+
23
+ # @author Jamie Winsor <jamie@vialstudios.com>
24
+ class DataBag
25
+ include Ridley::Resource
26
+
27
+ class << self
28
+ # @param [Ridley::Connection] connection
29
+ # @param [String, #chef_id] object
30
+ #
31
+ # @return [nil, Ridley::DataBag]
32
+ def find(connection, object)
33
+ find!(connection, object)
34
+ rescue Errors::HTTPNotFound
35
+ nil
36
+ end
37
+
38
+ # @param [Ridley::Connection] connection
39
+ # @param [String, #chef_id] object
40
+ #
41
+ # @raise [Errors::HTTPNotFound]
42
+ # if a resource with the given chef_id is not found
43
+ #
44
+ # @return [Ridley::DataBag]
45
+ def find!(connection, object)
46
+ chef_id = object.respond_to?(:chef_id) ? object.chef_id : object
47
+ name, uri = connection.get("#{self.resource_path}/#{chef_id}").body.first
48
+ new(connection, name: name)
49
+ end
50
+ end
51
+
52
+ set_chef_id "name"
53
+ set_resource_path "data"
54
+
55
+ attribute :name
56
+ validates_presence_of :name
57
+
58
+ def item
59
+ @dbi_context ||= DBIContext.new(self, connection)
60
+ end
61
+ end
62
+
63
+ module DSL
64
+ # Coerces instance functions into class functions on Ridley::DataBag. This coercion
65
+ # sends an instance of the including class along to the class function.
66
+ #
67
+ # @see Ridley::Context
68
+ #
69
+ # @return [Ridley::Context]
70
+ # a context object to delegate instance functions to class functions on Ridley::DataBag
71
+ def data_bag
72
+ Context.new(Ridley::DataBag, self)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,186 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class DataBagItem
4
+ include ActiveModel::Validations
5
+ include ActiveModel::Serialization
6
+
7
+ class << self
8
+ # @param [Ridley::Connection] connection
9
+ #
10
+ # @return [Array<Object>]
11
+ def all(connection, data_bag)
12
+ connection.get("#{data_bag.class.resource_path}/#{data_bag.name}").body.collect do |id, location|
13
+ new(connection, data_bag, id: id)
14
+ end
15
+ end
16
+
17
+ # @param [Ridley::Connection] connection
18
+ # @param [Ridley::DataBag] data_bag
19
+ # @param [String, #chef_id] object
20
+ #
21
+ # @return [nil, Ridley::DataBagItem]
22
+ def find(connection, data_bag, object)
23
+ find!(connection, data_bag, object)
24
+ rescue Errors::HTTPNotFound
25
+ nil
26
+ end
27
+
28
+ # @param [Ridley::Connection] connection
29
+ # @param [Ridley::DataBag] data_bag
30
+ # @param [String, #chef_id] object
31
+ #
32
+ # @raise [Errors::HTTPNotFound]
33
+ # if a resource with the given chef_id is not found
34
+ #
35
+ # @return [Ridley::DataBagItem]
36
+ def find!(connection, data_bag, object)
37
+ chef_id = object.respond_to?(:chef_id) ? object.chef_id : object
38
+ new(connection, data_bag).from_hash(connection.get("#{data_bag.class.resource_path}/#{data_bag.name}/#{chef_id}").body)
39
+ end
40
+
41
+ # @param [Ridley::Connection] connection
42
+ # @param [Ridley::DataBag] data_bag
43
+ # @param [#to_hash] object
44
+ #
45
+ # @return [Ridley::DataBagItem]
46
+ def create(connection, data_bag, object)
47
+ resource = new(connection, data_bag, object.to_hash)
48
+ unless resource.valid?
49
+ raise Errors::InvalidResource.new(resource.errors)
50
+ end
51
+
52
+ new_attributes = connection.post("#{data_bag.class.resource_path}/#{data_bag.name}", resource.to_json).body
53
+ resource.from_hash(resource.attributes.merge(new_attributes))
54
+ resource
55
+ end
56
+
57
+ # @param [Ridley::Connection] connection
58
+ # @param [Ridley::DataBag] data_bag
59
+ # @param [String, #chef_id] object
60
+ #
61
+ # @return [Ridley::DataBagItem]
62
+ def delete(connection, data_bag, object)
63
+ chef_id = object.respond_to?(:chef_id) ? object.chef_id : object
64
+ new(connection, data_bag).from_hash(connection.delete("#{data_bag.class.resource_path}/#{data_bag.name}/#{chef_id}").body)
65
+ end
66
+
67
+ # @param [Ridley::Connection] connection
68
+ # @param [Ridley::DataBag] data_bag
69
+ #
70
+ # @return [Array<Ridley::DataBagItem>]
71
+ def delete_all(connection, data_bag)
72
+ mutex = Mutex.new
73
+ deleted = []
74
+ resources = all(connection, data_bag)
75
+
76
+ connection.thread_count.times.collect do
77
+ Thread.new(connection, data_bag, resources, deleted) do |connection, data_bag, resources, deleted|
78
+ while resource = mutex.synchronize { resources.pop }
79
+ result = delete(connection, data_bag, resource)
80
+ mutex.synchronize { deleted << result }
81
+ end
82
+ end
83
+ end.each(&:join)
84
+
85
+ deleted
86
+ end
87
+
88
+ # @param [Ridley::Connection] connection
89
+ # @param [Ridley::DataBag] data_bag
90
+ # @param [#to_hash] object
91
+ #
92
+ # @return [Ridley::DataBagItem]
93
+ def update(connection, data_bag, object)
94
+ resource = new(connection, data_bag, object.to_hash)
95
+ new(connection, data_bag).from_hash(
96
+ connection.put("#{data_bag.class.resource_path}/#{data_bag.name}/#{resource.chef_id}", resource.to_json).body
97
+ )
98
+ end
99
+ end
100
+
101
+ attr_reader :data_bag
102
+
103
+ attr_accessor :attributes
104
+ validates_presence_of :id
105
+
106
+ def initialize(connection, data_bag, attributes = {})
107
+ @connection = connection
108
+ @data_bag = data_bag
109
+ @attributes = attributes
110
+ end
111
+
112
+ # Alias for accessing the value of the 'id' attribute
113
+ #
114
+ # @return [String]
115
+ def chef_id
116
+ @attributes[:id]
117
+ end
118
+ alias_method :id, :chef_id
119
+
120
+ # @param [String, Symbol] key
121
+ #
122
+ # @return [Object]
123
+ def attribute(key)
124
+ @attributes[key]
125
+ end
126
+ alias_method :[], :attribute
127
+
128
+ # @param [String, Symbol] key
129
+ # @param [Object] value
130
+ #
131
+ # @return [Object]
132
+ def attribute=(key, value)
133
+ @attributes[key] = value
134
+ end
135
+ alias_method :[]=, :attribute=
136
+
137
+ # Creates a resource on the target remote or updates one if the resource
138
+ # already exists.
139
+ #
140
+ # @raise [Errors::InvalidResource]
141
+ # if the resource does not pass validations
142
+ #
143
+ # @return [Boolean]
144
+ # true if successful and false for failure
145
+ def save
146
+ raise Errors::InvalidResource.new(self.errors) unless valid?
147
+
148
+ self.attributes = self.class.create(connection, data_bag, self).attributes
149
+ true
150
+ rescue Errors::HTTPConflict
151
+ self.attributes = self.class.update(connection, data_bag, self).attributes
152
+ true
153
+ end
154
+
155
+ # @param [#to_hash] hash
156
+ #
157
+ # @return [Object]
158
+ def from_hash(hash)
159
+ hash = hash.to_hash
160
+
161
+ self.attributes = hash.has_key?(:raw_data) ? hash[:raw_data] : hash
162
+ self
163
+ end
164
+
165
+ # @option options [Boolean] :symbolize_keys
166
+ # @option options [Class, Symbol, String] :adapter
167
+ #
168
+ # @return [String]
169
+ def to_json(options = {})
170
+ MultiJson.dump(self.attributes, options)
171
+ end
172
+ alias_method :as_json, :to_json
173
+
174
+ def to_hash
175
+ self.attributes
176
+ end
177
+
178
+ def to_s
179
+ self.attributes
180
+ end
181
+
182
+ private
183
+
184
+ attr_reader :connection
185
+ end
186
+ end
@@ -0,0 +1,45 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class Environment
4
+ include Ridley::Resource
5
+
6
+ class << self
7
+ # Delete all of the environments on the remote connection. The
8
+ # '_default' environment will never be deleted.
9
+ #
10
+ # @param [Ridley::Connection] connection
11
+ #
12
+ # @return [Array<Ridley::Environment>]
13
+ def delete_all(connection)
14
+ envs = all(connection).reject { |env| env.name.to_s == '_default' }
15
+ envs.collect { |obj| delete(connection, obj) }
16
+ end
17
+ end
18
+
19
+ set_chef_id "name"
20
+ set_chef_type "environment"
21
+ set_chef_json_class "Chef::Environment"
22
+ set_resource_path "environments"
23
+
24
+ attribute :name
25
+ validates_presence_of :name
26
+
27
+ attribute :description, default: String.new
28
+ attribute :default_attributes, default: Hash.new
29
+ attribute :override_attributes, default: Hash.new
30
+ attribute :cookbook_versions, default: Hash.new
31
+ end
32
+
33
+ module DSL
34
+ # Coerces instance functions into class functions on Ridley::Environment. This coercion
35
+ # sends an instance of the including class along to the class function.
36
+ #
37
+ # @see Ridley::Context
38
+ #
39
+ # @return [Ridley::Context]
40
+ # a context object to delegate instance functions to class functions on Ridley::Environment
41
+ def environment
42
+ Context.new(Ridley::Environment, self)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class Node
4
+ include Ridley::Resource
5
+
6
+ set_chef_id "name"
7
+ set_chef_type "node"
8
+ set_chef_json_class "Chef::Node"
9
+ set_resource_path "nodes"
10
+
11
+ attribute :name
12
+ validates_presence_of :name
13
+
14
+ attribute :chef_environment, default: "_default"
15
+ attribute :automatic, default: Hash.new
16
+ attribute :normal, default: Hash.new
17
+ attribute :default, default: Hash.new
18
+ attribute :override, default: Hash.new
19
+ attribute :run_list, default: Array.new
20
+ end
21
+
22
+ module DSL
23
+ # Coerces instance functions into class functions on Ridley::Node. This coercion
24
+ # sends an instance of the including class along to the class function.
25
+ #
26
+ # @see Ridley::Context
27
+ #
28
+ # @return [Ridley::Context]
29
+ # a context object to delegate instance functions to class functions on Ridley::Node
30
+ def node
31
+ Context.new(Ridley::Node, self)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class Role
4
+ include Ridley::Resource
5
+
6
+ set_chef_id "name"
7
+ set_chef_type "role"
8
+ set_chef_json_class "Chef::Role"
9
+ set_resource_path "roles"
10
+
11
+ attribute :name
12
+ validates_presence_of :name
13
+
14
+ attribute :description, default: String.new
15
+ attribute :default_attributes, default: Hash.new
16
+ attribute :override_attributes, default: Hash.new
17
+ attribute :run_list, default: Array.new
18
+ attribute :env_run_lists, default: Hash.new
19
+ end
20
+
21
+ module DSL
22
+ # Coerces instance functions into class functions on Ridley::Role. This coercion
23
+ # sends an instance of the including class along to the class function.
24
+ #
25
+ # @see Ridley::Context
26
+ #
27
+ # @return [Ridley::Context]
28
+ # a context object to delegate instance functions to class functions on Ridley::Role
29
+ def role
30
+ Context.new(Ridley::Role, self)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Ridley
2
+ VERSION = "0.0.1"
3
+ end
data/ridley.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/ridley/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.authors = ["Jamie Winsor"]
6
+ s.email = ["jamie@vialstudios.com"]
7
+ s.description = %q{A reliable Chef API client with a clean syntax}
8
+ s.summary = s.description
9
+ s.homepage = "https://github.com/reset/ridley"
10
+
11
+ s.files = `git ls-files`.split($\)
12
+ s.executables = Array.new
13
+ s.test_files = s.files.grep(%r{^(spec)/})
14
+ s.name = "ridley"
15
+ s.require_paths = ["lib"]
16
+ s.version = Ridley::VERSION
17
+
18
+ s.add_runtime_dependency 'yajl-ruby'
19
+ s.add_runtime_dependency 'mixlib-log'
20
+ s.add_runtime_dependency 'mixlib-authentication'
21
+ s.add_runtime_dependency 'addressable'
22
+ s.add_runtime_dependency 'faraday'
23
+ s.add_runtime_dependency 'multi_json'
24
+ s.add_runtime_dependency 'activemodel'
25
+
26
+ s.add_development_dependency 'rake'
27
+ s.add_development_dependency 'rspec'
28
+ s.add_development_dependency 'fuubar'
29
+ s.add_development_dependency 'spork'
30
+ s.add_development_dependency 'yard'
31
+ s.add_development_dependency 'guard'
32
+ s.add_development_dependency 'guard-rspec'
33
+ s.add_development_dependency 'guard-spork'
34
+ s.add_development_dependency 'guard-yard'
35
+ s.add_development_dependency 'coolline'
36
+ s.add_development_dependency 'redcarpet'
37
+ s.add_development_dependency 'json_spec'
38
+ s.add_development_dependency 'webmock'
39
+ end