chef-metal 0.10.2 → 0.11.beta

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -5
  3. data/README.md +3 -3
  4. data/bin/metal +5 -9
  5. data/lib/chef/provider/machine.rb +81 -32
  6. data/lib/chef/provider/machine_batch.rb +67 -58
  7. data/lib/chef/provider/machine_execute.rb +7 -7
  8. data/lib/chef/provider/machine_file.rb +11 -11
  9. data/lib/chef/resource/machine.rb +11 -15
  10. data/lib/chef/resource/machine_batch.rb +1 -1
  11. data/lib/chef/resource/machine_execute.rb +3 -4
  12. data/lib/chef/resource/machine_file.rb +3 -4
  13. data/lib/chef_metal.rb +26 -28
  14. data/lib/chef_metal/action_handler.rb +9 -7
  15. data/lib/chef_metal/add_prefix_action_handler.rb +7 -5
  16. data/lib/chef_metal/chef_machine_spec.rb +64 -0
  17. data/lib/chef_metal/{provider_action_handler.rb → chef_provider_action_handler.rb} +27 -14
  18. data/lib/chef_metal/chef_run_data.rb +68 -7
  19. data/lib/chef_metal/convergence_strategy.rb +14 -3
  20. data/lib/chef_metal/convergence_strategy/install_cached.rb +24 -10
  21. data/lib/chef_metal/convergence_strategy/install_msi.rb +17 -10
  22. data/lib/chef_metal/convergence_strategy/install_sh.rb +20 -10
  23. data/lib/chef_metal/convergence_strategy/no_converge.rb +20 -13
  24. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +51 -47
  25. data/lib/chef_metal/{provider.rb → driver.rb} +103 -79
  26. data/lib/chef_metal/machine.rb +13 -5
  27. data/lib/chef_metal/machine/basic_machine.rb +11 -11
  28. data/lib/chef_metal/machine/unix_machine.rb +6 -6
  29. data/lib/chef_metal/machine/windows_machine.rb +3 -3
  30. data/lib/chef_metal/machine_spec.rb +22 -25
  31. data/lib/chef_metal/recipe_dsl.rb +34 -9
  32. data/lib/chef_metal/transport.rb +7 -2
  33. data/lib/chef_metal/transport/ssh.rb +42 -9
  34. data/lib/chef_metal/transport/winrm.rb +8 -5
  35. data/lib/chef_metal/version.rb +1 -1
  36. data/spec/integration/machine.rb +29 -0
  37. metadata +21 -9
  38. data/lib/chef_metal/aws_credentials.rb +0 -58
  39. data/lib/chef_metal/openstack_credentials.rb +0 -44
  40. data/lib/chef_metal/provisioner.rb +0 -121
@@ -1,60 +1,70 @@
1
1
  module ChefMetal
2
2
  #
3
- # A Provider instance represents a place where machines can be created and found,
3
+ # A Driver instance represents a place where machines can be created and found,
4
4
  # and contains methods to create, delete, start, stop, and find them.
5
5
  #
6
- # For AWS, a Provider instance corresponds to a single account.
6
+ # For AWS, a Driver instance corresponds to a single account.
7
7
  # For Vagrant, it is a directory where VM files are found.
8
8
  #
9
- # == How to Make a Provider
9
+ # == How to Make a Driver
10
10
  #
11
- # To implement a Provider, you must implement the following methods:
11
+ # To implement a Driver, you must implement the following methods:
12
12
  #
13
13
  # - initialize(driver_url) - create a new driver with the given URL
14
- # - driver_url - a URL representing everything unique about your provider.
14
+ # - driver_url - a URL representing everything unique about your driver.
15
15
  # But NOT credentials.
16
16
  # - allocate_machine - ask the driver to allocate a machine to you.
17
17
  # - ready_machine - get the machine "ready" - wait for it to be booted and
18
18
  # accessible (for example, accessible via SSH transport).
19
19
  # - stop_machine - stop the machine.
20
- # - delete_machine - delete the machine.
20
+ # - destroy_machine - delete the machine.
21
21
  # - connect_to_machine - connect to the given machine.
22
22
  #
23
23
  # Optionally, you can also implement:
24
24
  # - allocate_machines - allocate an entire group of machines.
25
- # - resource_created - a hook to tell you when a resource associated with your
26
- # provider has been created.
25
+ # - ready_machines - get a group of machines warm and booted.
26
+ # - stop_machines - stop a group of machines.
27
+ # - destroy_machines - delete a group of machines.
27
28
  #
28
- # Additionally, you must create a file named `chef_metal/provider_init/<scheme>.rb`,
29
+ # Additionally, you must create a file named `chef_metal/driver_init/<scheme>.rb`,
29
30
  # where <scheme> is the name of the scheme you chose for your driver_url. This
30
- # file, when required, must call
31
+ # file, when required, must call ChefMetal.add_registered_driver(<scheme>, <class>).
32
+ # The given <class>.from_url(url, config) will be called with a driver_url and
33
+ # configuration.
31
34
  #
32
35
  # All of these methods must be idempotent - if the work is already done, they
33
36
  # just don't do anything.
34
37
  #
35
- class Provider
38
+ class Driver
36
39
  #
37
- # Inflate a driver from node information; we don't want to force the
38
- # driver to figure out what the driver really needs, since it varies
39
- # from driver to driver.
40
+ # Inflate a driver from a driver URL.
40
41
  #
41
- # ## Parameters
42
+ # == Parameters
42
43
  # driver_url - the URL to inflate the driver
44
+ # config - a configuration hash. See "config" for a list of known keys.
43
45
  #
44
- # ## Returns
45
- # A Provider representing the given driver_url.
46
- def initialize(driver_url)
47
- # We do not save it ... it's up to the driver to extract whatever information
48
- # it wants.
46
+ # == Returns
47
+ # A Driver representing the given driver_url.
48
+ #
49
+ def initialize(driver_url, config)
50
+ @driver_url = driver_url
51
+ @config = config
52
+ end
53
+
54
+ #
55
+ # Override this on specific driver classes
56
+ #
57
+ def self.from_url(driver_url, config)
58
+ ChefMetal.from_url(driver_url, config)
49
59
  end
50
60
 
51
61
  #
52
62
  # A URL representing the driver and the place where machines come from.
53
- # This will be stuffed in attributes in the node so that the node can be
54
- # reinflated. URLs must have a unique scheme identifying the driver
55
- # class, and enough information to identify the place where created machines
56
- # can be found. For AWS, this is the account number; for lxc and vagrant,
57
- # it is the directory in which VMs and containers are.
63
+ # This will be stuffed in machine_spec.location['driver_url'] so that the
64
+ # machine can be reinflated. URLs must have a unique scheme identifying the
65
+ # driver class, and enough information to identify the place where created
66
+ # machines can be found. For AWS, this is the account number; for lxc and
67
+ # vagrant, it is the directory in which VMs and containers are.
58
68
  #
59
69
  # For example:
60
70
  # - fog:AWS:123456789012
@@ -62,15 +72,31 @@ module ChefMetal
62
72
  # - lxc:
63
73
  # - docker:
64
74
  #
65
- def driver_url
66
- raise "#{self.class} does not implement driver_url"
75
+ attr_reader :driver_url
76
+
77
+ # A configuration hash. These keys may be present:
78
+ # - :driver_options: a driver-defined object containing driver config.
79
+ # - :private_keys: a hash of private keys, with a "name" and a "value". Values are either strings (paths) or PrivateKey objects.
80
+ # - :private_key_paths: a list of paths to directories containing private keys.
81
+ # - :write_private_key_path: the path to which we write new keys by default.
82
+ # - :log_level: :debug/:info/:warn/:error/:fatal
83
+ # - :chef_server_url: url to chef server
84
+ # - :node_name: username to talk to chef server
85
+ # - :client_key: path to key used to talk to chef server
86
+ attr_reader :config
87
+
88
+ #
89
+ # Driver configuration. Equivalent to config[:driver_options] || {}
90
+ #
91
+ def driver_options
92
+ config[:driver_options] || {}
67
93
  end
68
94
 
69
95
  #
70
- # Allocate a machine from the PXE/cloud/VM/container provider. This method
96
+ # Allocate a machine from the PXE/cloud/VM/container driver. This method
71
97
  # does not need to wait for the machine to boot or have an IP, but it must
72
- # store enough information in node['normal']['driver_output'] to find
73
- # the machine later in ready_machine.
98
+ # store enough information in machine_spec.location to find the machine
99
+ # later in ready_machine.
74
100
  #
75
101
  # If a machine is powered off or otherwise unusable, this method may start
76
102
  # it, but does not need to wait until it is started. The idea is to get the
@@ -78,7 +104,7 @@ module ChefMetal
78
104
  #
79
105
  # ## Parameters
80
106
  # action_handler - the action_handler object that is calling this method; this
81
- # is generally a provider, but could be anything that can support the
107
+ # is generally a driver, but could be anything that can support the
82
108
  # interface (i.e., in the case of the test kitchen metal driver for
83
109
  # acquiring and destroying VMs).
84
110
  #
@@ -86,13 +112,13 @@ module ChefMetal
86
112
  #
87
113
  # machine_options - a set of options representing the desired provisioning
88
114
  # state of the machine (image name, bootstrap ssh credentials,
89
- # etc.). This will NOT be stored in the node, and is
115
+ # etc.). This will NOT be stored in the machine_spec, and is
90
116
  # ephemeral.
91
117
  #
92
118
  # ## Returns
93
119
  #
94
120
  # Modifies the passed-in machine_spec. Anything in here will be saved
95
- # back to the node.
121
+ # back after allocate_machine completes.
96
122
  #
97
123
  def allocate_machine(action_handler, machine_spec, machine_options)
98
124
  raise "#{self.class} does not implement allocate_machine"
@@ -107,17 +133,21 @@ module ChefMetal
107
133
  #
108
134
  # ## Parameters
109
135
  # action_handler - the action_handler object that is calling this method; this
110
- # is generally a provider, but could be anything that can support the
136
+ # is generally a driver, but could be anything that can support the
111
137
  # interface (i.e., in the case of the test kitchen metal driver for
112
138
  # acquiring and destroying VMs).
113
139
  # machine_spec - MachineSpec representing this machine.
140
+ # machine_options - a set of options representing the desired provisioning
141
+ # state of the machine (image name, bootstrap ssh credentials,
142
+ # etc.). This will NOT be stored in the machine_spec, and is
143
+ # ephemeral.
114
144
  #
115
145
  # ## Returns
116
146
  #
117
147
  # Machine object pointing at the machine, allowing useful actions like setup,
118
148
  # converge, execute, file and directory.
119
149
  #
120
- def ready_machine(action_handler, machine_spec)
150
+ def ready_machine(action_handler, machine_spec, machine_options)
121
151
  raise "#{self.class} does not implement ready_machine"
122
152
  end
123
153
 
@@ -133,7 +163,7 @@ module ChefMetal
133
163
  # Machine object pointing at the machine, allowing useful actions like setup,
134
164
  # converge, execute, file and directory.
135
165
  #
136
- def connect_to_machine(machine_spec)
166
+ def connect_to_machine(machine_spec, machine_options)
137
167
  raise "#{self.class} does not implement connect_to_machine"
138
168
  end
139
169
 
@@ -141,14 +171,14 @@ module ChefMetal
141
171
  # Delete the given machine (idempotent). Should destroy the machine,
142
172
  # returning things to the state before allocate_machine was called.
143
173
  #
144
- def delete_machine(action_handler, machine_spec)
145
- raise "#{self.class} does not implement delete_machine"
174
+ def destroy_machine(action_handler, machine_spec, machine_options)
175
+ raise "#{self.class} does not implement destroy_machine"
146
176
  end
147
177
 
148
178
  #
149
179
  # Stop the given machine.
150
180
  #
151
- def stop_machine(action_handler, machine_spec)
181
+ def stop_machine(action_handler, machine_spec, machine_options)
152
182
  raise "#{self.class} does not implement stop_machine"
153
183
  end
154
184
 
@@ -158,21 +188,22 @@ module ChefMetal
158
188
 
159
189
  #
160
190
  # Allocate a set of machines. This should have the same effect as running
161
- # allocate_machine on all nodes.
191
+ # allocate_machine on all machine_specs.
162
192
  #
163
- # Providers do not need to implement this; the default implementation
193
+ # Drivers do not need to implement this; the default implementation
164
194
  # calls acquire_machine in parallel.
165
195
  #
166
- # ## Parameter
196
+ # ## Parameters
167
197
  # action_handler - the action_handler object that is calling this method; this
168
- # is generally a provider, but could be anything that can support the
198
+ # is generally a driver, but could be anything that can support the
169
199
  # interface (i.e., in the case of the test kitchen metal driver for
170
200
  # acquiring and destroying VMs).
171
- # nodes - a list of nodes representing the nodes to acquire.
201
+ # specs_and_options - a hash of machine_spec -> machine_options representing the
202
+ # machines to allocate.
172
203
  # parallelizer - an object with a parallelize() method that works like this:
173
204
  #
174
- # parallelizer.parallelize(nodes) do |node|
175
- # allocate_machine(action_handler, node)
205
+ # parallelizer.parallelize(specs_and_options) do |machine_spec|
206
+ # allocate_machine(action_handler, machine_spec)
176
207
  # end.to_a
177
208
  # # The to_a at the end causes you to wait until the parallelization is done
178
209
  #
@@ -180,60 +211,53 @@ module ChefMetal
180
211
  # not go over parallelization limits set by the user. Use of the parallelizer
181
212
  # to parallelizer machines is not required.
182
213
  #
183
- def allocate_machines(action_handler, machine_specs, parallelizer)
184
- parallelizer.parallelize(machine_specs) do |machine_spec|
185
- allocate_machine(add_prefix(machine_spec, action_handler), machine_spec)
214
+ # ## Block
215
+ #
216
+ # If you pass a block to this function, each machine will be yielded to you
217
+ # as it completes, and then the function will return when all machines are
218
+ # yielded.
219
+ #
220
+ # allocate_machines(action_handler, specs_and_options, parallelizer) do |machine_spec|
221
+ # ...
222
+ # end
223
+ #
224
+ def allocate_machines(action_handler, specs_and_options, parallelizer)
225
+ parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
226
+ allocate_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
186
227
  yield machine_spec if block_given?
187
- machine
228
+ machine_spec
188
229
  end.to_a
189
230
  end
190
231
 
191
232
  # Acquire machines in batch, in parallel if possible.
192
- def acquire_machines(action_handler, machine_specs, parallelizer)
193
- parallelizer.parallelize(machine_specs) do |machine_spec|
194
- machine = acquire_machine(add_prefix(machine_spec, action_handler), machine_spec)
195
- yield machine_spec, machine if block_given?
233
+ def ready_machines(action_handler, specs_and_options, parallelizer)
234
+ parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
235
+ machine = ready_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
236
+ yield machine if block_given?
196
237
  machine
197
238
  end.to_a
198
239
  end
199
240
 
200
241
  # Stop machines in batch, in parallel if possible.
201
- def stop_machines(action_handler, nodes_json, parallelizer)
202
- parallelizer.parallelize(machine_specs) do |machine_spec|
203
- stop_machine(add_prefix(machine_spec, action_handler), machine_spec)
242
+ def stop_machines(action_handler, specs_and_options, parallelizer)
243
+ parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
244
+ stop_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
204
245
  yield machine_spec if block_given?
205
246
  end.to_a
206
247
  end
207
248
 
208
249
  # Delete machines in batch, in parallel if possible.
209
- def delete_machines(action_handler, machine_specs, parallelizer)
210
- parallelizer.parallelize(machine_specs) do |machine_spec|
211
- delete_machine(add_prefix(machine_spec, action_handler), machine_spec)
250
+ def destroy_machines(action_handler, specs_and_options, parallelizer)
251
+ parallelizer.parallelize(specs_and_options) do |machine_spec, machine_options|
252
+ destroy_machine(add_prefix(machine_spec, action_handler), machine_spec, machine_options)
212
253
  yield machine_spec if block_given?
213
254
  end.to_a
214
255
  end
215
256
 
216
- #
217
- # Provider notification that happens at the point a machine resource is declared
218
- # (after all properties have been set on it)
219
- #
220
- def resource_created(machine)
221
- end
222
-
223
257
  protected
224
258
 
225
- def save_node(provider, node, chef_server)
226
- # Save the node and create the client. TODO strip automatic attributes first so we don't race with "current state"
227
- ChefMetal.inline_resource(provider) do
228
- chef_node node['name'] do
229
- chef_server chef_server
230
- raw_json node
231
- end
232
- end
233
- end
234
-
235
- def add_prefix(node_json, action_handler)
236
- AddPrefixActionHandler.new(action_handler, "[#{node_json['name']}] ")
259
+ def add_prefix(machine_spec, action_handler)
260
+ AddPrefixActionHandler.new(action_handler, "[#{machine_spec.name}] ")
237
261
  end
238
262
  end
239
263
  end
@@ -1,19 +1,27 @@
1
1
  module ChefMetal
2
2
  class Machine
3
- def initialize(node)
4
- @node = node
3
+ def initialize(machine_spec)
4
+ @machine_spec = machine_spec
5
5
  end
6
6
 
7
- attr_reader :node
7
+ attr_reader :machine_spec
8
+
9
+ def name
10
+ machine_spec.name
11
+ end
12
+
13
+ def node
14
+ machine_spec.node
15
+ end
8
16
 
9
17
  # Sets up everything necessary for convergence to happen on the machine.
10
18
  # The node MUST be saved as part of this procedure. Other than that,
11
19
  # nothing is guaranteed except that converge() will work when this is done.
12
- def setup_convergence(action_handler, machine_resource)
20
+ def setup_convergence(action_handler)
13
21
  raise "setup_convergence not overridden on #{self.class}"
14
22
  end
15
23
 
16
- def converge(action_handler, chef_server)
24
+ def converge(action_handler)
17
25
  raise "converge not overridden on #{self.class}"
18
26
  end
19
27
 
@@ -3,8 +3,8 @@ require 'chef_metal/machine'
3
3
  module ChefMetal
4
4
  class Machine
5
5
  class BasicMachine < Machine
6
- def initialize(node, transport, convergence_strategy)
7
- super(node)
6
+ def initialize(machine_spec, transport, convergence_strategy)
7
+ super(machine_spec)
8
8
  @transport = transport
9
9
  @convergence_strategy = convergence_strategy
10
10
  end
@@ -13,18 +13,18 @@ module ChefMetal
13
13
  attr_reader :convergence_strategy
14
14
 
15
15
  # Sets up everything necessary for convergence to happen on the machine.
16
- # The node MUST be saved as part of this procedure. Other than that,
16
+ # The machine_spec MUST be saved as part of this procedure. Other than that,
17
17
  # nothing is guaranteed except that converge() will work when this is done.
18
- def setup_convergence(action_handler, machine_resource)
19
- convergence_strategy.setup_convergence(action_handler, self, machine_resource)
18
+ def setup_convergence(action_handler)
19
+ convergence_strategy.setup_convergence(action_handler, self)
20
20
  end
21
21
 
22
- def converge(action_handler, chef_server)
23
- convergence_strategy.converge(action_handler, self, chef_server)
22
+ def converge(action_handler)
23
+ convergence_strategy.converge(action_handler, self)
24
24
  end
25
25
 
26
26
  def execute(action_handler, command, options = {})
27
- action_handler.perform_action "run '#{command}' on #{node['name']}" do
27
+ action_handler.perform_action "run '#{command}' on #{machine_spec.name}" do
28
28
  result = transport.execute(command, options)
29
29
  result.error!
30
30
  result
@@ -41,7 +41,7 @@ module ChefMetal
41
41
 
42
42
  def download_file(action_handler, path, local_path)
43
43
  if files_different?(path, local_path)
44
- action_handler.perform_action "download file #{path} on #{node['name']} to #{local_path}" do
44
+ action_handler.perform_action "download file #{path} on #{machine_spec.name} to #{local_path}" do
45
45
  transport.download_file(path, local_path)
46
46
  end
47
47
  end
@@ -52,7 +52,7 @@ module ChefMetal
52
52
  if options[:ensure_dir]
53
53
  create_dir(action_handler, dirname_on_machine(path))
54
54
  end
55
- action_handler.perform_action "write file #{path} on #{node['name']}" do
55
+ action_handler.perform_action "write file #{path} on #{machine_spec.name}" do
56
56
  transport.write_file(path, content)
57
57
  end
58
58
  end
@@ -63,7 +63,7 @@ module ChefMetal
63
63
  if options[:ensure_dir]
64
64
  create_dir(action_handler, dirname_on_machine(path))
65
65
  end
66
- action_handler.perform_action "upload file #{local_path} to #{path} on #{node['name']}" do
66
+ action_handler.perform_action "upload file #{local_path} to #{path} on #{machine_spec.name}" do
67
67
  transport.upload_file(local_path, path)
68
68
  end
69
69
  end
@@ -4,7 +4,7 @@ require 'digest'
4
4
  module ChefMetal
5
5
  class Machine
6
6
  class UnixMachine < BasicMachine
7
- def initialize(node, transport, convergence_strategy)
7
+ def initialize(machine_spec, transport, convergence_strategy)
8
8
  super
9
9
 
10
10
  @tmp_dir = '/tmp'
@@ -18,7 +18,7 @@ module ChefMetal
18
18
  # Delete file
19
19
  def delete_file(action_handler, path)
20
20
  if file_exists?(path)
21
- action_handler.perform_action "delete file #{path} on #{node['name']}" do
21
+ action_handler.perform_action "delete file #{path} on #{machine_spec.name}" do
22
22
  transport.execute("rm -f #{path}").error!
23
23
  end
24
24
  end
@@ -61,7 +61,7 @@ module ChefMetal
61
61
 
62
62
  def create_dir(action_handler, path)
63
63
  if !file_exists?(path)
64
- action_handler.perform_action "create directory #{path} on #{node['name']}" do
64
+ action_handler.perform_action "create directory #{path} on #{machine_spec.name}" do
65
65
  transport.execute("mkdir -p #{path}").error!
66
66
  end
67
67
  end
@@ -72,17 +72,17 @@ module ChefMetal
72
72
  if attributes[:mode] || attributes[:owner] || attributes[:group]
73
73
  current_attributes = get_attributes(path)
74
74
  if attributes[:mode] && current_attributes[:mode].to_i != attributes[:mode].to_i
75
- action_handler.perform_action "change mode of #{path} on #{node['name']} from #{current_attributes[:mode].to_i} to #{attributes[:mode].to_i}" do
75
+ action_handler.perform_action "change mode of #{path} on #{machine_spec.name} from #{current_attributes[:mode].to_i} to #{attributes[:mode].to_i}" do
76
76
  transport.execute("chmod #{attributes[:mode].to_i} #{path}").error!
77
77
  end
78
78
  end
79
79
  if attributes[:owner] && current_attributes[:owner] != attributes[:owner]
80
- action_handler.perform_action "change group of #{path} on #{node['name']} from #{current_attributes[:owner]} to #{attributes[:owner]}" do
80
+ action_handler.perform_action "change group of #{path} on #{machine_spec.name} from #{current_attributes[:owner]} to #{attributes[:owner]}" do
81
81
  transport.execute("chown #{attributes[:owner]} #{path}").error!
82
82
  end
83
83
  end
84
84
  if attributes[:group] && current_attributes[:group] != attributes[:group]
85
- action_handler.perform_action "change group of #{path} on #{node['name']} from #{current_attributes[:group]} to #{attributes[:group]}" do
85
+ action_handler.perform_action "change group of #{path} on #{machine_spec.name} from #{current_attributes[:group]} to #{attributes[:group]}" do
86
86
  transport.execute("chgrp #{attributes[:group]} #{path}").error!
87
87
  end
88
88
  end