clc-fork-chef-metal 0.11.beta.5

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +106 -0
  3. data/LICENSE +201 -0
  4. data/README.md +201 -0
  5. data/Rakefile +6 -0
  6. data/bin/metal +276 -0
  7. data/lib/chef/provider/machine.rb +147 -0
  8. data/lib/chef/provider/machine_batch.rb +130 -0
  9. data/lib/chef/provider/machine_execute.rb +30 -0
  10. data/lib/chef/provider/machine_file.rb +49 -0
  11. data/lib/chef/resource/machine.rb +95 -0
  12. data/lib/chef/resource/machine_batch.rb +20 -0
  13. data/lib/chef/resource/machine_execute.rb +22 -0
  14. data/lib/chef/resource/machine_file.rb +28 -0
  15. data/lib/chef_metal.rb +62 -0
  16. data/lib/chef_metal/action_handler.rb +63 -0
  17. data/lib/chef_metal/add_prefix_action_handler.rb +29 -0
  18. data/lib/chef_metal/chef_machine_spec.rb +64 -0
  19. data/lib/chef_metal/chef_provider_action_handler.rb +72 -0
  20. data/lib/chef_metal/chef_run_data.rb +80 -0
  21. data/lib/chef_metal/convergence_strategy.rb +26 -0
  22. data/lib/chef_metal/convergence_strategy/install_cached.rb +157 -0
  23. data/lib/chef_metal/convergence_strategy/install_msi.rb +56 -0
  24. data/lib/chef_metal/convergence_strategy/install_sh.rb +51 -0
  25. data/lib/chef_metal/convergence_strategy/no_converge.rb +38 -0
  26. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +180 -0
  27. data/lib/chef_metal/driver.rb +267 -0
  28. data/lib/chef_metal/machine.rb +110 -0
  29. data/lib/chef_metal/machine/basic_machine.rb +82 -0
  30. data/lib/chef_metal/machine/unix_machine.rb +276 -0
  31. data/lib/chef_metal/machine/windows_machine.rb +102 -0
  32. data/lib/chef_metal/machine_spec.rb +78 -0
  33. data/lib/chef_metal/recipe_dsl.rb +84 -0
  34. data/lib/chef_metal/transport.rb +87 -0
  35. data/lib/chef_metal/transport/ssh.rb +235 -0
  36. data/lib/chef_metal/transport/winrm.rb +109 -0
  37. data/lib/chef_metal/version.rb +3 -0
  38. metadata +223 -0
@@ -0,0 +1,38 @@
1
+ require 'chef_metal/convergence_strategy'
2
+ require 'pathname'
3
+ require 'cheffish'
4
+
5
+ module ChefMetal
6
+ class ConvergenceStrategy
7
+ class NoConverge < ConvergenceStrategy
8
+ def initialize(convergence_options, config)
9
+ super
10
+ end
11
+
12
+ def chef_server
13
+ @chef_server ||= convergence_options[:chef_server] || Cheffish.default_chef_server(config)
14
+ end
15
+
16
+ def setup_convergence(action_handler, machine)
17
+ machine_spec.save(action_handler)
18
+ end
19
+
20
+ def converge(action_handler, machine)
21
+ end
22
+
23
+ def cleanup_convergence(action_handler, machine_spec)
24
+ _self = self
25
+ ChefMetal.inline_resource(action_handler) do
26
+ chef_node machine_spec.name do
27
+ chef_server _self.chef_server
28
+ action :delete
29
+ end
30
+ chef_client machine_spec.name do
31
+ chef_server _self.chef_server
32
+ action :delete
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,180 @@
1
+ require 'chef_metal/convergence_strategy'
2
+ require 'pathname'
3
+ require 'cheffish'
4
+
5
+ module ChefMetal
6
+ class ConvergenceStrategy
7
+ class PrecreateChefObjects < ConvergenceStrategy
8
+ def initialize(convergence_options, config)
9
+ super
10
+ end
11
+
12
+ def chef_server
13
+ @chef_server ||= convergence_options[:chef_server] || Cheffish.default_chef_server(config)
14
+ end
15
+
16
+ def setup_convergence(action_handler, machine)
17
+ # Create keys on machine
18
+ public_key = create_keys(action_handler, machine)
19
+ # Create node and client on chef server
20
+ create_chef_objects(action_handler, machine, public_key)
21
+
22
+ # If the chef server lives on localhost, tunnel the port through to the guest
23
+ # (we need to know what got tunneled!)
24
+ chef_server_url = chef_server[:chef_server_url]
25
+ chef_server_url = machine.make_url_available_to_remote(chef_server_url)
26
+
27
+ # Support for multiple ohai hints, required on some platforms
28
+ create_ohai_files(action_handler, machine)
29
+
30
+ # Create client.rb and client.pem on machine
31
+ content = client_rb_content(chef_server_url, machine.node['name'])
32
+ machine.write_file(action_handler, convergence_options[:client_rb_path], content, :ensure_dir => true)
33
+ end
34
+
35
+ def converge(action_handler, machine)
36
+ machine.make_url_available_to_remote(chef_server[:chef_server_url])
37
+ end
38
+
39
+ def cleanup_convergence(action_handler, machine_spec)
40
+ _self = self
41
+ ChefMetal.inline_resource(action_handler) do
42
+ chef_node machine_spec.name do
43
+ chef_server _self.chef_server
44
+ action :delete
45
+ end
46
+ chef_client machine_spec.name do
47
+ chef_server _self.chef_server
48
+ action :delete
49
+ end
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def create_keys(action_handler, machine)
56
+ server_private_key = machine.read_file(convergence_options[:client_pem_path])
57
+ if server_private_key
58
+ begin
59
+ server_private_key, format = Cheffish::KeyFormatter.decode(server_private_key)
60
+ rescue
61
+ server_private_key = nil
62
+ end
63
+ end
64
+
65
+ if server_private_key
66
+ if source_key && server_private_key.to_pem != source_key.to_pem
67
+ # If the server private key does not match our source key, overwrite it
68
+ server_private_key = source_key
69
+ if convergence_options[:allow_overwrite_keys]
70
+ machine.write_file(action_handler, convergence_options[:client_pem_path], server_private_key.to_pem, :ensure_dir => true)
71
+ else
72
+ raise "Private key on machine #{machine.name} does not match desired input key."
73
+ end
74
+ end
75
+
76
+ else
77
+
78
+ # If the server does not already have keys, create them and upload
79
+ _convergence_options = convergence_options
80
+ ChefMetal.inline_resource(action_handler) do
81
+ private_key 'in_memory' do
82
+ path :none
83
+ if _convergence_options[:private_key_options]
84
+ _convergence_options[:private_key_options].each_pair do |key,value|
85
+ send(key, value)
86
+ end
87
+ end
88
+ after { |resource, private_key| server_private_key = private_key }
89
+ end
90
+ end
91
+
92
+ machine.write_file(action_handler, convergence_options[:client_pem_path], server_private_key.to_pem, :ensure_dir => true)
93
+ end
94
+
95
+ server_private_key.public_key
96
+ end
97
+
98
+ def is_localhost(host)
99
+ host == '127.0.0.1' || host == 'localhost' || host == '[::1]'
100
+ end
101
+
102
+ def source_key
103
+ if convergence_options[:source_key].is_a?(String)
104
+ key, format = Cheffish::KeyFormatter.decode(convergence_options[:source_key], convergence_options[:source_key_pass_phrase])
105
+ key
106
+ elsif convergence_options[:source_key]
107
+ convergence_options[:source_key]
108
+ elsif convergence_options[:source_key_path]
109
+ key, format = Cheffish::KeyFormatter.decode(IO.read(convergence_options[:source_key_path]), convergence_options[:source_key_pass_phrase], convergence_options[:source_key_path])
110
+ key
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ # Create the ohai file(s)
117
+ def create_ohai_files(action_handler, machine)
118
+ if convergence_options[:ohai_hints]
119
+ convergence_options[:ohai_hints].each_pair do |hint, data|
120
+ # The location of the ohai hint
121
+ ohai_hint = "/etc/chef/ohai/hints/#{hint}.json"
122
+ machine.write_file(action_handler, ohai_hint, data.to_json, :ensure_dir => true)
123
+ end
124
+ end
125
+ end
126
+
127
+ def create_chef_objects(action_handler, machine, public_key)
128
+ _convergence_options = convergence_options
129
+ _chef_server = chef_server
130
+ # Save the node and create the client keys and client.
131
+ ChefMetal.inline_resource(action_handler) do
132
+ # Create client
133
+ chef_client machine.name do
134
+ chef_server _chef_server
135
+ source_key public_key
136
+ output_key_path _convergence_options[:public_key_path]
137
+ output_key_format _convergence_options[:public_key_format]
138
+ admin _convergence_options[:admin]
139
+ validator _convergence_options[:validator]
140
+ end
141
+
142
+ # Create node
143
+ # TODO strip automatic attributes first so we don't race with "current state"
144
+ chef_node machine.name do
145
+ chef_server _chef_server
146
+ raw_json machine.node
147
+ end
148
+ end
149
+
150
+ # If using enterprise/hosted chef, fix acls
151
+ if chef_server[:chef_server_url] =~ /\/+organizations\/.+/
152
+ grant_client_node_permissions(action_handler, chef_server, machine.name, ["read", "update"])
153
+ end
154
+ end
155
+
156
+ # Grant the client permissions to the node
157
+ # This procedure assumes that the client name and node name are the same
158
+ def grant_client_node_permissions(action_handler, chef_server, node_name, perms)
159
+ api = Cheffish.chef_server_api(chef_server)
160
+ node_perms = api.get("/nodes/#{node_name}/_acl")
161
+ perms.each do |p|
162
+ if !node_perms[p]['actors'].include?(node_name)
163
+ action_handler.perform_action "Add #{node_name} to client #{p} ACLs" do
164
+ node_perms[p]['actors'] << node_name
165
+ api.put("/nodes/#{node_name}/_acl/#{p}", p => node_perms[p])
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ def client_rb_content(chef_server_url, node_name)
172
+ <<EOM
173
+ chef_server_url #{chef_server_url.inspect}
174
+ node_name #{node_name.inspect}
175
+ client_key #{convergence_options[:client_pem_path].inspect}
176
+ EOM
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,267 @@
1
+ module ChefMetal
2
+ #
3
+ # A Driver instance represents a place where machines can be created and found,
4
+ # and contains methods to create, delete, start, stop, and find them.
5
+ #
6
+ # For AWS, a Driver instance corresponds to a single account.
7
+ # For Vagrant, it is a directory where VM files are found.
8
+ #
9
+ # == How to Make a Driver
10
+ #
11
+ # To implement a Driver, you must implement the following methods:
12
+ #
13
+ # - initialize(driver_url) - create a new driver with the given URL
14
+ # - driver_url - a URL representing everything unique about your driver.
15
+ # But NOT credentials.
16
+ # - allocate_machine - ask the driver to allocate a machine to you.
17
+ # - ready_machine - get the machine "ready" - wait for it to be booted and
18
+ # accessible (for example, accessible via SSH transport).
19
+ # - stop_machine - stop the machine.
20
+ # - destroy_machine - delete the machine.
21
+ # - connect_to_machine - connect to the given machine.
22
+ #
23
+ # Optionally, you can also implement:
24
+ # - allocate_machines - allocate an entire group of machines.
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.
28
+ #
29
+ # Additionally, you must create a file named `chef_metal/driver_init/<scheme>.rb`,
30
+ # where <scheme> is the name of the scheme you chose for your driver_url. This
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.
34
+ #
35
+ # All of these methods must be idempotent - if the work is already done, they
36
+ # just don't do anything.
37
+ #
38
+ class Driver
39
+ #
40
+ # Inflate a driver from a driver URL.
41
+ #
42
+ # == Parameters
43
+ # driver_url - the URL to inflate the driver
44
+ # config - a configuration hash. See "config" for a list of known keys.
45
+ #
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)
59
+ end
60
+
61
+ #
62
+ # A URL representing the driver and the place where machines come from.
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.
68
+ #
69
+ # For example:
70
+ # - fog:AWS:123456789012
71
+ # - vagrant:/var/vms
72
+ # - lxc:
73
+ # - docker:
74
+ #
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] || {}
93
+ end
94
+
95
+ #
96
+ # Allocate a machine from the PXE/cloud/VM/container driver. This method
97
+ # does not need to wait for the machine to boot or have an IP, but it must
98
+ # store enough information in machine_spec.location to find the machine
99
+ # later in ready_machine.
100
+ #
101
+ # If a machine is powered off or otherwise unusable, this method may start
102
+ # it, but does not need to wait until it is started. The idea is to get the
103
+ # gears moving, but the job doesn't need to be done :)
104
+ #
105
+ # ## Parameters
106
+ # action_handler - the action_handler object that is calling this method; this
107
+ # is generally a driver, but could be anything that can support the
108
+ # interface (i.e., in the case of the test kitchen metal driver for
109
+ # acquiring and destroying VMs).
110
+ #
111
+ # existing_machine - a MachineSpec representing the existing machine (if any).
112
+ #
113
+ # machine_options - a set of options representing the desired provisioning
114
+ # state of the machine (image name, bootstrap ssh credentials,
115
+ # etc.). This will NOT be stored in the machine_spec, and is
116
+ # ephemeral.
117
+ #
118
+ # ## Returns
119
+ #
120
+ # Modifies the passed-in machine_spec. Anything in here will be saved
121
+ # back after allocate_machine completes.
122
+ #
123
+ def allocate_machine(action_handler, machine_spec, machine_options)
124
+ raise "#{self.class} does not implement allocate_machine"
125
+ end
126
+
127
+ #
128
+ # Ready a machine, to the point where it is running and accessible via a
129
+ # transport. This will NOT allocate a machine, but may kick it if it is down.
130
+ # This method waits for the machine to be usable, returning a Machine object
131
+ # pointing at the machine, allowing useful actions like setup, converge,
132
+ # execute, file and directory.
133
+ #
134
+ # ## Parameters
135
+ # action_handler - the action_handler object that is calling this method; this
136
+ # is generally a driver, but could be anything that can support the
137
+ # interface (i.e., in the case of the test kitchen metal driver for
138
+ # acquiring and destroying VMs).
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.
144
+ #
145
+ # ## Returns
146
+ #
147
+ # Machine object pointing at the machine, allowing useful actions like setup,
148
+ # converge, execute, file and directory.
149
+ #
150
+ def ready_machine(action_handler, machine_spec, machine_options)
151
+ raise "#{self.class} does not implement ready_machine"
152
+ end
153
+
154
+ #
155
+ # Connect to a machine without allocating or readying it. This method will
156
+ # NOT make any changes to anything, or attempt to wait.
157
+ #
158
+ # ## Parameters
159
+ # machine_spec - MachineSpec representing this machine.
160
+ #
161
+ # ## Returns
162
+ #
163
+ # Machine object pointing at the machine, allowing useful actions like setup,
164
+ # converge, execute, file and directory.
165
+ #
166
+ def connect_to_machine(machine_spec, machine_options)
167
+ raise "#{self.class} does not implement connect_to_machine"
168
+ end
169
+
170
+ #
171
+ # Delete the given machine (idempotent). Should destroy the machine,
172
+ # returning things to the state before allocate_machine was called.
173
+ #
174
+ def destroy_machine(action_handler, machine_spec, machine_options)
175
+ raise "#{self.class} does not implement destroy_machine"
176
+ end
177
+
178
+ #
179
+ # Stop the given machine.
180
+ #
181
+ def stop_machine(action_handler, machine_spec, machine_options)
182
+ raise "#{self.class} does not implement stop_machine"
183
+ end
184
+
185
+ #
186
+ # Optional interface methods
187
+ #
188
+
189
+ #
190
+ # Allocate a set of machines. This should have the same effect as running
191
+ # allocate_machine on all machine_specs.
192
+ #
193
+ # Drivers do not need to implement this; the default implementation
194
+ # calls acquire_machine in parallel.
195
+ #
196
+ # ## Parameters
197
+ # action_handler - the action_handler object that is calling this method; this
198
+ # is generally a driver, but could be anything that can support the
199
+ # interface (i.e., in the case of the test kitchen metal driver for
200
+ # acquiring and destroying VMs).
201
+ # specs_and_options - a hash of machine_spec -> machine_options representing the
202
+ # machines to allocate.
203
+ # parallelizer - an object with a parallelize() method that works like this:
204
+ #
205
+ # parallelizer.parallelize(specs_and_options) do |machine_spec|
206
+ # allocate_machine(action_handler, machine_spec)
207
+ # end.to_a
208
+ # # The to_a at the end causes you to wait until the parallelization is done
209
+ #
210
+ # This object is shared among other chef-metal actions, ensuring that you do
211
+ # not go over parallelization limits set by the user. Use of the parallelizer
212
+ # to parallelizer machines is not required.
213
+ #
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)
227
+ yield machine_spec if block_given?
228
+ machine_spec
229
+ end.to_a
230
+ end
231
+
232
+ # Ready machines in batch, in parallel if possible.
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?
237
+ machine
238
+ end.to_a
239
+ end
240
+
241
+ # Stop machines in batch, in parallel if possible.
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)
245
+ yield machine_spec if block_given?
246
+ end.to_a
247
+ end
248
+
249
+ # Delete machines in batch, in parallel if possible.
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)
253
+ yield machine_spec if block_given?
254
+ end.to_a
255
+ end
256
+
257
+ protected
258
+
259
+ def add_prefix(machine_spec, action_handler)
260
+ AddPrefixActionHandler.new(action_handler, "[#{machine_spec.name}] ")
261
+ end
262
+
263
+ def get_private_key(name)
264
+ Cheffish.get_private_key(name, config)
265
+ end
266
+ end
267
+ end