ridley 0.7.0.beta → 0.7.0.rc1

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.
Files changed (54) hide show
  1. data/README.md +51 -54
  2. data/lib/ridley.rb +7 -13
  3. data/lib/ridley/client.rb +251 -0
  4. data/lib/ridley/connection.rb +32 -188
  5. data/lib/ridley/middleware/chef_auth.rb +4 -1
  6. data/lib/ridley/resource.rb +36 -42
  7. data/lib/ridley/resources.rb +3 -0
  8. data/lib/ridley/resources/{client.rb → client_resource.rb} +7 -20
  9. data/lib/ridley/resources/cookbook_resource.rb +121 -0
  10. data/lib/ridley/resources/{data_bag_item.rb → data_bag_item_resource.rb} +52 -63
  11. data/lib/ridley/resources/data_bag_resource.rb +74 -0
  12. data/lib/ridley/resources/encrypted_data_bag_item_resource.rb +55 -0
  13. data/lib/ridley/resources/{environment.rb → environment_resource.rb} +8 -21
  14. data/lib/ridley/resources/{node.rb → node_resource.rb} +24 -37
  15. data/lib/ridley/resources/{role.rb → role_resource.rb} +1 -14
  16. data/lib/ridley/resources/sandbox_resource.rb +86 -0
  17. data/lib/ridley/resources/search.rb +24 -55
  18. data/lib/ridley/sandbox_uploader.rb +118 -0
  19. data/lib/ridley/ssh.rb +2 -2
  20. data/lib/ridley/ssh/worker.rb +2 -1
  21. data/lib/ridley/version.rb +1 -1
  22. data/ridley.gemspec +1 -1
  23. data/spec/acceptance/bootstrapping_spec.rb +1 -1
  24. data/spec/acceptance/client_resource_spec.rb +18 -20
  25. data/spec/acceptance/cookbook_resource_spec.rb +4 -22
  26. data/spec/acceptance/data_bag_item_resource_spec.rb +5 -7
  27. data/spec/acceptance/data_bag_resource_spec.rb +4 -6
  28. data/spec/acceptance/environment_resource_spec.rb +14 -16
  29. data/spec/acceptance/node_resource_spec.rb +12 -14
  30. data/spec/acceptance/role_resource_spec.rb +13 -15
  31. data/spec/acceptance/sandbox_resource_spec.rb +7 -9
  32. data/spec/acceptance/search_resource_spec.rb +6 -8
  33. data/spec/support/shared_examples/ridley_resource.rb +23 -22
  34. data/spec/unit/ridley/client_spec.rb +153 -0
  35. data/spec/unit/ridley/connection_spec.rb +8 -221
  36. data/spec/unit/ridley/resources/{client_spec.rb → client_resource_spec.rb} +4 -4
  37. data/spec/unit/ridley/resources/cookbook_resource_spec.rb +5 -0
  38. data/spec/unit/ridley/resources/{data_bag_item_spec.rb → data_bag_item_resource_spec.rb} +2 -2
  39. data/spec/unit/ridley/resources/{data_bag_spec.rb → data_bag_resource_spec.rb} +3 -3
  40. data/spec/unit/ridley/resources/{environment_spec.rb → environment_resource_spec.rb} +4 -4
  41. data/spec/unit/ridley/resources/{node_spec.rb → node_resource_spec.rb} +4 -4
  42. data/spec/unit/ridley/resources/{role_spec.rb → role_resource_spec.rb} +3 -3
  43. data/spec/unit/ridley/resources/sandbox_resource_spec.rb +172 -0
  44. data/spec/unit/ridley/resources/search_spec.rb +34 -30
  45. data/spec/unit/ridley/sandbox_uploader_spec.rb +99 -0
  46. data/spec/unit/ridley/ssh_spec.rb +2 -2
  47. data/spec/unit/ridley_spec.rb +4 -12
  48. metadata +36 -28
  49. data/lib/ridley/dsl.rb +0 -58
  50. data/lib/ridley/resources/cookbook.rb +0 -51
  51. data/lib/ridley/resources/data_bag.rb +0 -81
  52. data/lib/ridley/resources/encrypted_data_bag_item.rb +0 -54
  53. data/lib/ridley/resources/sandbox.rb +0 -154
  54. data/spec/unit/ridley/resources/cookbook_spec.rb +0 -5
@@ -1,16 +1,16 @@
1
1
  module Ridley
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
- class Environment < Ridley::Resource
3
+ class EnvironmentResource < Ridley::Resource
4
4
  class << self
5
- # Delete all of the environments on the remote connection. The
6
- # '_default' environment will never be deleted.
5
+ # Delete all of the environments on the client. The '_default' environment
6
+ # will never be deleted.
7
7
  #
8
- # @param [Ridley::Connection] connection
8
+ # @param [Ridley::Client] client
9
9
  #
10
- # @return [Array<Ridley::Environment>]
11
- def delete_all(connection)
12
- envs = all(connection).reject { |env| env.name.to_s == '_default' }
13
- envs.collect { |obj| delete(connection, obj) }
10
+ # @return [Array<Ridley::EnvironmentResource>]
11
+ def delete_all(client)
12
+ envs = all(client).reject { |env| env.name.to_s == '_default' }
13
+ envs.collect { |obj| delete(client, obj) }
14
14
  end
15
15
  end
16
16
 
@@ -70,17 +70,4 @@ module Ridley
70
70
  self.override_attributes = self.override_attributes.deep_merge(attr_hash)
71
71
  end
72
72
  end
73
-
74
- module DSL
75
- # Coerces instance functions into class functions on Ridley::Environment. This coercion
76
- # sends an instance of the including class along to the class function.
77
- #
78
- # @see Ridley::ChainLink
79
- #
80
- # @return [Ridley::ChainLink]
81
- # a context object to delegate instance functions to class functions on Ridley::Environment
82
- def environment
83
- ChainLink.new(self, Ridley::Environment)
84
- end
85
- end
86
73
  end
@@ -1,9 +1,9 @@
1
1
  module Ridley
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
- class Node < Ridley::Resource
3
+ class NodeResource < Ridley::Resource
4
4
  class << self
5
- # @overload bootstrap(connection, nodes, options = {})
6
- # @param [Ridley::Connection] connection
5
+ # @overload bootstrap(client, nodes, options = {})
6
+ # @param [Ridley::Client] client
7
7
  # @param [Array<String>, String] nodes
8
8
  # @param [Hash] ssh
9
9
  # * :user (String) a shell user that will login to each node and perform the bootstrap command on (required)
@@ -33,15 +33,15 @@ module Ridley
33
33
  # bootstrap template to use (default: omnibus)
34
34
  #
35
35
  # @return [SSH::ResponseSet]
36
- def bootstrap(connection, *args)
36
+ def bootstrap(client, *args)
37
37
  options = args.extract_options!
38
38
 
39
39
  default_options = {
40
- server_url: connection.server_url,
41
- validator_path: connection.validator_path,
42
- validator_client: connection.validator_client,
43
- encrypted_data_bag_secret_path: connection.encrypted_data_bag_secret_path,
44
- ssh: connection.ssh
40
+ server_url: client.server_url,
41
+ validator_path: client.validator_path,
42
+ validator_client: client.validator_client,
43
+ encrypted_data_bag_secret_path: client.encrypted_data_bag_secret_path,
44
+ ssh: client.ssh
45
45
  }
46
46
 
47
47
  options = default_options.merge(options)
@@ -51,8 +51,8 @@ module Ridley
51
51
 
52
52
  # Merges the given data with the the data of the target node on the remote
53
53
  #
54
- # @param [Ridley::Cnonection] connection
55
- # @param [Ridley::Node, String] target
54
+ # @param [Ridley::Client] client
55
+ # @param [Ridley::NodeResource, String] target
56
56
  # node or identifier of the node to merge
57
57
  # @option options [Array] :run_list
58
58
  # run list items to merge
@@ -62,9 +62,9 @@ module Ridley
62
62
  # @raise [Errors::HTTPNotFound]
63
63
  # if the target node is not found
64
64
  #
65
- # @return [Ridley::Node]
66
- def merge_data(connection, target, options = {})
67
- find!(connection, target).merge_data(options)
65
+ # @return [Ridley::NodeResource]
66
+ def merge_data(client, target, options = {})
67
+ find!(client, target).merge_data(options)
68
68
  end
69
69
  end
70
70
 
@@ -119,9 +119,9 @@ module Ridley
119
119
  # @param [String] key
120
120
  # @param [Object] value
121
121
  #
122
- # @return [HashWithIndifferentAccess]
122
+ # @return [Hashie::Mash]
123
123
  def set_chef_attribute(key, value)
124
- attr_hash = HashWithIndifferentAccess.from_dotted_path(key, value)
124
+ attr_hash = Hashie::Mash.from_dotted_path(key, value)
125
125
  self.normal = self.normal.deep_merge(attr_hash)
126
126
  end
127
127
 
@@ -197,7 +197,7 @@ module Ridley
197
197
  #
198
198
  # @return [SSH::Response]
199
199
  def chef_client(options = {})
200
- options = connection.ssh.merge(options)
200
+ options = client.ssh.merge(options)
201
201
 
202
202
  Ridley.log.debug "Running Chef Client on: #{self.public_hostname}"
203
203
  Ridley::SSH.start(self, options) do |ssh|
@@ -205,8 +205,8 @@ module Ridley
205
205
  end
206
206
  end
207
207
 
208
- # Put the connection's encrypted data bag secret onto the instantiated node. If no
209
- # encrypted data bag key path is set on the resource's connection then nil will be
208
+ # Put the client's encrypted data bag secret onto the instantiated node. If no
209
+ # encrypted data bag key path is set on the resource's client then nil will be
210
210
  # returned
211
211
  #
212
212
  # @param [Hash] options
@@ -214,14 +214,14 @@ module Ridley
214
214
  #
215
215
  # @return [SSH::Response, nil]
216
216
  def put_secret(options = {})
217
- if connection.encrypted_data_bag_secret_path.nil? ||
218
- !File.exists?(connection.encrypted_data_bag_secret_path)
217
+ if client.encrypted_data_bag_secret_path.nil? ||
218
+ !File.exists?(client.encrypted_data_bag_secret_path)
219
219
 
220
220
  return nil
221
221
  end
222
222
 
223
- options = connection.ssh.merge(options)
224
- secret = File.read(connection.encrypted_data_bag_secret_path).chomp
223
+ options = client.ssh.merge(options)
224
+ secret = File.read(client.encrypted_data_bag_secret_path).chomp
225
225
  command = "echo '#{secret}' > /etc/chef/encrypted_data_bag_secret; chmod 0600 /etc/chef/encrypted_data_bag_secret"
226
226
 
227
227
  Ridley.log.debug "Writing Encrypted Data Bag Secret to: #{self.public_hostname}"
@@ -238,7 +238,7 @@ module Ridley
238
238
  # @option options [Hash] :attributes
239
239
  # attributes of normal precedence to merge
240
240
  #
241
- # @return [Ridley::Node]
241
+ # @return [Ridley::NodeResource]
242
242
  def merge_data(options = {})
243
243
  unless options[:run_list].nil?
244
244
  self.run_list = (self.run_list + Array(options[:run_list])).uniq
@@ -252,17 +252,4 @@ module Ridley
252
252
  self
253
253
  end
254
254
  end
255
-
256
- module DSL
257
- # Coerces instance functions into class functions on Ridley::Node. This coercion
258
- # sends an instance of the including class along to the class function.
259
- #
260
- # @see Ridley::ChainLink
261
- #
262
- # @return [Ridley::ChainLink]
263
- # a context object to delegate instance functions to class functions on Ridley::Node
264
- def node
265
- ChainLink.new(self, Ridley::Node)
266
- end
267
- end
268
255
  end
@@ -1,6 +1,6 @@
1
1
  module Ridley
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
- class Role < Ridley::Resource
3
+ class RoleResource < Ridley::Resource
4
4
  set_chef_id "name"
5
5
  set_chef_type "role"
6
6
  set_chef_json_class "Chef::Role"
@@ -60,17 +60,4 @@ module Ridley
60
60
  self.default_attributes = self.default_attributes.deep_merge(attr_hash)
61
61
  end
62
62
  end
63
-
64
- module DSL
65
- # Coerces instance functions into class functions on Ridley::Role. This coercion
66
- # sends an instance of the including class along to the class function.
67
- #
68
- # @see Ridley::ChainLink
69
- #
70
- # @return [Ridley::ChainLink]
71
- # a context object to delegate instance functions to class functions on Ridley::Role
72
- def role
73
- ChainLink.new(self, Ridley::Role)
74
- end
75
- end
76
63
  end
@@ -0,0 +1,86 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class SandboxResource
4
+ class << self
5
+ # Create a new Sandbox on the client's Chef Server. A Sandbox requires an
6
+ # array of file checksums which lets the Chef Server know what the signature
7
+ # of the contents to be uploaded will look like.
8
+ #
9
+ # @param [Ridley::Client] client
10
+ # @param [Array] checksums
11
+ # a hash of file checksums
12
+ #
13
+ # @example using the Ridley client to create a sandbox
14
+ # client.sandbox.create([
15
+ # "385ea5490c86570c7de71070bce9384a",
16
+ # "f6f73175e979bd90af6184ec277f760c",
17
+ # "2e03dd7e5b2e6c8eab1cf41ac61396d5"
18
+ # ])
19
+ #
20
+ # @return [Array<Ridley::SandboxResource>]
21
+ def create(client, checksums = [])
22
+ sumhash = { checksums: Hash.new }.tap do |chks|
23
+ Array(checksums).each { |chk| chks[:checksums][chk] = nil }
24
+ end
25
+
26
+ new(client, client.connection.post("sandboxes", MultiJson.encode(sumhash)).body)
27
+ end
28
+ end
29
+
30
+ include Chozo::VariaModel
31
+
32
+ attribute :sandbox_id,
33
+ type: String
34
+
35
+ attribute :uri,
36
+ type: String
37
+
38
+ attribute :checksums,
39
+ type: Hash
40
+
41
+ attribute :is_completed,
42
+ type: Boolean,
43
+ default: false
44
+
45
+ attr_reader :client
46
+
47
+ # @param [Ridley::Client] client
48
+ # @param [Hash] new_attrs
49
+ def initialize(client, new_attrs = {})
50
+ @client = client
51
+ mass_assign(new_attrs)
52
+ end
53
+
54
+ # Return information about the given checksum
55
+ #
56
+ # @example
57
+ # sandbox.checksum("e5a0f6b48d0712382295ff30bec1f9cc") => {
58
+ # needs_upload: true,
59
+ # url: "https://s3.amazonaws.com/opscode-platform-production-data/organization"
60
+ # }
61
+ #
62
+ # @param [#to_sym] chk_id
63
+ # checksum to retrieve information about
64
+ #
65
+ # @return [Hash]
66
+ # a hash containing the checksum information
67
+ def checksum(chk_id)
68
+ checksums[chk_id.to_sym]
69
+ end
70
+
71
+ # Concurrently upload all of this sandboxes files into the checksum containers of the sandbox
72
+ def upload(checksums)
73
+ SandboxUploader.upload(self, checksums)
74
+ end
75
+
76
+ # Notify the Chef Server that uploading to this sandbox has completed
77
+ def commit
78
+ response = client.connection.put("sandboxes/#{sandbox_id}", MultiJson.encode(is_completed: true)).body
79
+ set_attribute(:is_completed, response[:is_completed])
80
+ end
81
+
82
+ def to_s
83
+ "#{sandbox_id}: #{checksums}"
84
+ end
85
+ end
86
+ end
@@ -3,20 +3,20 @@ module Ridley
3
3
  class << self
4
4
  # Returns an array of possible search indexes to be search on
5
5
  #
6
- # @param [Ridley::Connection] connection
6
+ # @param [Ridley::Client] client
7
7
  #
8
8
  # @example
9
9
  #
10
- # Search.indexes(connection) => [ :client, :environment, :node, :role ]
10
+ # Search.indexes(client) => [ :client, :environment, :node, :role ]
11
11
  #
12
12
  # @return [Array<String, Symbol>]
13
- def indexes(connection)
14
- connection.get("search").body.collect { |name, _| name }
13
+ def indexes(client)
14
+ client.connection.get("search").body.collect { |name, _| name }
15
15
  end
16
16
  end
17
17
 
18
- # @return [Ridley::Connection]
19
- attr_reader :connection
18
+ # @return [Ridley::Client]
19
+ attr_reader :client
20
20
  attr_reader :index
21
21
  attr_reader :query
22
22
 
@@ -24,7 +24,7 @@ module Ridley
24
24
  attr_accessor :rows
25
25
  attr_accessor :start
26
26
 
27
- # @param [Ridley::Connection] connection
27
+ # @param [Ridley::Client] client
28
28
  # @param [#to_sym] index
29
29
  # @param [#to_s] query
30
30
  #
@@ -34,20 +34,19 @@ module Ridley
34
34
  # how many rows to return
35
35
  # @option options [Integer] :start
36
36
  # the result number to start from
37
- def initialize(connection, index, query, options = {})
38
- @connection = connection
39
- @index = index.to_sym
40
- @query = query
41
-
42
- @sort = options[:sort]
43
- @rows = options[:rows]
44
- @start = options[:start]
37
+ def initialize(client, index, query, options = {})
38
+ @client = client
39
+ @index = index.to_sym
40
+ @query = query
41
+ @sort = options[:sort]
42
+ @rows = options[:rows]
43
+ @start = options[:start]
45
44
  end
46
45
 
47
- # Executes the built up query on the search's connection
46
+ # Executes the built up query on the search's client
48
47
  #
49
48
  # @example
50
- # Search.new(connection, :role)
49
+ # Search.new(client, :role)
51
50
  # search.run =>
52
51
  # {
53
52
  # total: 1,
@@ -68,17 +67,17 @@ module Ridley
68
67
  #
69
68
  # @return [Hash]
70
69
  def run
71
- response = connection.get(query_uri, query_options).body
70
+ response = client.connection.get(query_uri, query_options).body
72
71
 
73
72
  case index
74
73
  when :node
75
- response[:rows].collect { |row| Node.new(connection, row) }
74
+ response[:rows].collect { |row| Ridley::NodeResource.new(client, row) }
76
75
  when :role
77
- response[:rows].collect { |row| Role.new(connection, row) }
76
+ response[:rows].collect { |row| Ridley::RoleResource.new(client, row) }
78
77
  when :client
79
- response[:rows].collect { |row| Client.new(connection, row) }
78
+ response[:rows].collect { |row| Ridley::ClientResource.new(client, row) }
80
79
  when :environment
81
- response[:rows].collect { |row| Environment.new(connection, row) }
80
+ response[:rows].collect { |row| Ridley::EnvironmentResource.new(client, row) }
82
81
  else
83
82
  response[:rows]
84
83
  end
@@ -92,41 +91,11 @@ module Ridley
92
91
 
93
92
  def query_options
94
93
  {}.tap do |options|
95
- options[:q] = self.query unless self.query.nil?
96
- options[:sort] = self.sort unless self.sort.nil?
97
- options[:rows] = self.rows unless self.rows.nil?
94
+ options[:q] = self.query unless self.query.nil?
95
+ options[:sort] = self.sort unless self.sort.nil?
96
+ options[:rows] = self.rows unless self.rows.nil?
98
97
  options[:start] = self.start unless self.start.nil?
99
98
  end
100
99
  end
101
100
  end
102
-
103
- module DSL
104
- # Creates an runs a new Ridley::Search
105
- #
106
- # @see Ridley::Search#run
107
- #
108
- # @param [String, Symbol] index
109
- # @param [String, nil] query
110
- #
111
- # @option options [String] :sort
112
- # @option options [Integer] :rows
113
- # @option options [Integer] :start
114
- #
115
- # @return [Hash]
116
- def search(index, query = nil, options = {})
117
- Search.new(self, index, query, options).run
118
- end
119
-
120
- # Return the array of all possible search indexes for the including connection
121
- #
122
- # @example
123
- # conn = Ridley.connection(...)
124
- # conn.search_indexes =>
125
- # [:client, :environment, :node, :role, :"ridley-two", :"ridley-one"]
126
- #
127
- # @return [Array<Symbol, String>]
128
- def search_indexes
129
- Search.indexes(self)
130
- end
131
- end
132
101
  end
@@ -0,0 +1,118 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ # @api private
4
+ class SandboxUploader
5
+ class << self
6
+ # Concurrently upload all of the files in the given sandbox and then clean up
7
+ # after ourselves
8
+ #
9
+ # @param [Ridley::SandboxResource] sandbox
10
+ # @param [Hash] checksums
11
+ #
12
+ # @option options [Integer] :pool_size (12)
13
+ # the amount of concurrent uploads to perform
14
+ def upload(sandbox, checksums, options = {})
15
+ options = options.reverse_merge(
16
+ pool_size: 12
17
+ )
18
+ uploader = pool(size: options[:pool_size], args: [sandbox])
19
+ uploader.multi_upload(checksums)
20
+ ensure
21
+ uploader.terminate if uploader && uploader.alive?
22
+ end
23
+
24
+ # Return the checksum of the contents of the file at the given filepath
25
+ #
26
+ # @param [String] path
27
+ # file to checksum
28
+ #
29
+ # @return [String]
30
+ # the binary checksum of the contents of the file
31
+ def checksum(path)
32
+ File.open(path, 'rb') { |f| checksum_io(f, Digest::MD5.new) }
33
+ end
34
+
35
+ # Return a base64 encoded checksum of the contents of hte given file. This is the expected
36
+ # format of sandbox checksums given to the Chef Server.
37
+ #
38
+ # @param [String] path
39
+ #
40
+ # @return [String]
41
+ # a base64 encoded checksum
42
+ def checksum64(path)
43
+ Base64.encode64([checksum(path)].pack("H*")).strip
44
+ end
45
+
46
+ # @param [String] io
47
+ # @param [Object] digest
48
+ #
49
+ # @return [String]
50
+ def checksum_io(io, digest)
51
+ while chunk = io.read(1024 * 8)
52
+ digest.update(chunk)
53
+ end
54
+ digest.hexdigest
55
+ end
56
+ end
57
+
58
+ extend Forwardable
59
+ include Celluloid
60
+
61
+ attr_reader :sandbox
62
+
63
+ def_delegator :sandbox, :client
64
+ def_delegator :sandbox, :checksum
65
+
66
+ def initialize(sandbox)
67
+ @sandbox = sandbox
68
+ end
69
+
70
+ # Concurrently upload multiple files into a sandbox
71
+ #
72
+ # @param [Hash] checksums
73
+ # a hash of file checksums and file paths
74
+ #
75
+ # @example uploading multiple checksums
76
+ #
77
+ # sandbox.multi_upload(
78
+ # "e5a0f6b48d0712382295ff30bec1f9cc" => "/Users/reset/code/rbenv-cookbook/recipes/default.rb",
79
+ # "de6532a7fbe717d52020dc9f3ae47dbe" => "/Users/reset/code/rbenv-cookbook/recipes/ohai_plugin.rb"
80
+ # )
81
+ def multi_upload(checksums)
82
+ checksums.collect do |chk_id, path|
83
+ future.upload(chk_id, path)
84
+ end.map(&:value)
85
+ end
86
+
87
+ # Upload one file into the sandbox for the given checksum id
88
+ #
89
+ # @param [String] chk_id
90
+ # checksum of the file being uploaded
91
+ # @param [String] path
92
+ # path to the file to upload
93
+ #
94
+ # @return [Hash, nil]
95
+ def upload(chk_id, path)
96
+ checksum = self.checksum(chk_id)
97
+
98
+ unless checksum[:needs_upload]
99
+ return nil
100
+ end
101
+
102
+ headers = {
103
+ 'Content-Type' => 'application/x-binary',
104
+ 'content-md5' => self.class.checksum64(path)
105
+ }
106
+ contents = File.open(path, 'rb') { |f| f.read }
107
+
108
+ url = URI(checksum[:url])
109
+ upload_path = url.path
110
+ url.path = ""
111
+
112
+ Faraday.new(url) do |c|
113
+ c.request :chef_auth, client.client_name, client.client_key
114
+ c.adapter :net_http
115
+ end.put(upload_path, contents, headers)
116
+ end
117
+ end
118
+ end