ridley 0.7.0.beta → 0.7.0.rc1

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