chef-metal 0.10.2 → 0.11.beta

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