chef-metal 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +160 -0
  4. data/Rakefile +6 -0
  5. data/lib/chef/provider/fog_key_pair.rb +106 -0
  6. data/lib/chef/provider/machine.rb +60 -0
  7. data/lib/chef/provider/machine_file.rb +39 -0
  8. data/lib/chef/provider/vagrant_box.rb +44 -0
  9. data/lib/chef/provider/vagrant_cluster.rb +39 -0
  10. data/lib/chef/resource/fog_key_pair.rb +34 -0
  11. data/lib/chef/resource/machine.rb +56 -0
  12. data/lib/chef/resource/machine_file.rb +25 -0
  13. data/lib/chef/resource/vagrant_box.rb +18 -0
  14. data/lib/chef/resource/vagrant_cluster.rb +16 -0
  15. data/lib/chef_metal.rb +84 -0
  16. data/lib/chef_metal/aws_credentials.rb +55 -0
  17. data/lib/chef_metal/convergence_strategy.rb +15 -0
  18. data/lib/chef_metal/convergence_strategy/install_msi.rb +41 -0
  19. data/lib/chef_metal/convergence_strategy/install_sh.rb +36 -0
  20. data/lib/chef_metal/convergence_strategy/precreate_chef_objects.rb +140 -0
  21. data/lib/chef_metal/inline_resource.rb +88 -0
  22. data/lib/chef_metal/machine.rb +79 -0
  23. data/lib/chef_metal/machine/basic_machine.rb +79 -0
  24. data/lib/chef_metal/machine/unix_machine.rb +108 -0
  25. data/lib/chef_metal/machine/windows_machine.rb +94 -0
  26. data/lib/chef_metal/provisioner.rb +71 -0
  27. data/lib/chef_metal/provisioner/fog_provisioner.rb +378 -0
  28. data/lib/chef_metal/provisioner/vagrant_provisioner.rb +327 -0
  29. data/lib/chef_metal/recipe_dsl.rb +26 -0
  30. data/lib/chef_metal/transport.rb +36 -0
  31. data/lib/chef_metal/transport/ssh.rb +157 -0
  32. data/lib/chef_metal/transport/winrm.rb +91 -0
  33. data/lib/chef_metal/version.rb +3 -0
  34. metadata +175 -0
@@ -0,0 +1,108 @@
1
+ require 'chef_metal/machine/basic_machine'
2
+ require 'digest'
3
+
4
+ module ChefMetal
5
+ class Machine
6
+ class UnixMachine < BasicMachine
7
+ def initialize(node, transport, convergence_strategy)
8
+ super
9
+ end
10
+
11
+ # Options include:
12
+ #
13
+ # command_prefix - prefix to put in front of any command, e.g. sudo
14
+ attr_reader :options
15
+
16
+ # Delete file
17
+ def delete_file(provider, path)
18
+ if file_exists?(path)
19
+ provider.converge_by "delete file #{path} on #{node['name']}" do
20
+ transport.execute("rm -f #{path}").error!
21
+ end
22
+ end
23
+ end
24
+
25
+ # Return true or false depending on whether file exists
26
+ def file_exists?(path)
27
+ result = transport.execute("ls -d #{path}")
28
+ result.exitstatus == 0 && result.stdout != ''
29
+ end
30
+
31
+ def files_different?(path, local_path, content=nil)
32
+ if !file_exists?(path)
33
+ return true
34
+ end
35
+
36
+ # Get remote checksum of file
37
+ result = transport.execute("md5sum -b #{path}")
38
+ result.error!
39
+ remote_sum = result.stdout.split(' ')[0]
40
+
41
+ digest = Digest::MD5.new
42
+ if content
43
+ digest.update(content)
44
+ else
45
+ File.open(local_path, 'rb') do |io|
46
+ while (buf = io.read(4096)) && buf.length > 0
47
+ digest.update(buf)
48
+ end
49
+ end
50
+ end
51
+ remote_sum != digest.hexdigest
52
+ end
53
+
54
+ def create_dir(provider, path)
55
+ if !file_exists?(path)
56
+ provider.converge_by "create directory #{path} on #{node['name']}" do
57
+ transport.execute("mkdir #{path}").error!
58
+ end
59
+ end
60
+ end
61
+
62
+ # Set file attributes { mode, :owner, :group }
63
+ def set_attributes(provider, path, attributes)
64
+ if attributes[:mode] || attributes[:owner] || attributes[:group]
65
+ current_attributes = get_file_attributes(path)
66
+ if attributes[:mode] && current_attributes[:mode] != attributes[:mode]
67
+ provider.converge_by "change mode of #{path} on #{node['name']} from #{current_attributes[:mode].to_i(8)} to #{attributes[:mode].to_i(8)}" do
68
+ transport.execute("chmod #{attributes[:mode].to_i(8)} #{path}").error!
69
+ end
70
+ end
71
+ if attributes[:owner] && current_attributes[:owner] != attributes[:owner]
72
+ provider.converge_by "change group of #{path} on #{node['name']} from #{current_attributes[:owner]} to #{attributes[:owner]}" do
73
+ transport.execute("chown #{attributes[:owner]} #{path}").error!
74
+ end
75
+ end
76
+ if attributes[:group] && current_attributes[:group] != attributes[:group]
77
+ provider.converge_by "change group of #{path} on #{node['name']} from #{current_attributes[:group]} to #{attributes[:group]}" do
78
+ transport.execute("chgrp #{attributes[:group]} #{path}").error!
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Get file attributes { :mode, :owner, :group }
85
+ def get_attributes(path)
86
+ file_info = transport.execute("ls -ld #{path}").stdout.split(/\s+/)
87
+ if file_info.size <= 1
88
+ raise "#{path} does not exist in set_attributes()"
89
+ end
90
+ result = {
91
+ :mode => 0,
92
+ :owner => file_info[2],
93
+ :group => file_info[3]
94
+ }
95
+ attribute_string = file_info[0]
96
+ 0.upto(attribute_string.length-1).each do |i|
97
+ result[:mode] <<= 1
98
+ result[:mode] += (attribute_string[i] == '-' ? 0 : 1)
99
+ end
100
+ result
101
+ end
102
+
103
+ def dirname_on_machine(path)
104
+ path.split('/')[0..-2].join('/')
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,94 @@
1
+ require 'chef_metal/machine/basic_machine'
2
+
3
+ module ChefMetal
4
+ class Machine
5
+ class WindowsMachine < BasicMachine
6
+ def initialize(node, transport, convergence_strategy)
7
+ super
8
+ end
9
+
10
+ # Options include:
11
+ #
12
+ # command_prefix - prefix to put in front of any command, e.g. sudo
13
+ attr_reader :options
14
+
15
+ # Delete file
16
+ def delete_file(provider, path)
17
+ if file_exists?(path)
18
+ provider.converge_by "delete file #{escape(path)} on #{node['name']}" do
19
+ transport.execute("Remove-Item #{escape(path)}").error!
20
+ end
21
+ end
22
+ end
23
+
24
+ # Return true or false depending on whether file exists
25
+ def file_exists?(path)
26
+ parse_boolean(transport.execute("Test-Path #{escape(path)}").stdout)
27
+ end
28
+
29
+ def files_different?(path, local_path, content=nil)
30
+ # Get remote checksum of file (from http://stackoverflow.com/a/13926809)
31
+ result = transport.execute <<-EOM
32
+ $md5 = [System.Security.Cryptography.MD5]::Create("MD5")
33
+ $fd = [System.IO.File]::OpenRead(#{path.inspect})
34
+ $buf = new-object byte[] (1024*1024*8) # 8mb buffer
35
+ while (($read_len = $fd.Read($buf,0,$buf.length)) -eq $buf.length){
36
+ $total += $buf.length
37
+ $md5.TransformBlock($buf,$offset,$buf.length,$buf,$offset)
38
+ }
39
+ # finalize the last read
40
+ $md5.TransformFinalBlock($buf,0,$read_len)
41
+ $hash = $md5.Hash
42
+ # convert hash bytes to hex formatted string
43
+ $hash | foreach { $hash_txt += $_.ToString("x2") }
44
+ $hash_txt
45
+ EOM
46
+ result.error!
47
+ remote_sum = result.stdout.split(' ')[0]
48
+ digest = Digest::SHA256.new
49
+ if content
50
+ digest.update(content)
51
+ else
52
+ File.open(local_path, 'rb') do |io|
53
+ while (buf = io.read(4096)) && buf.length > 0
54
+ digest.update(buf)
55
+ end
56
+ end
57
+ end
58
+ remote_sum != digest.hexdigest
59
+ end
60
+
61
+ def create_dir(provider, path)
62
+ if !file_exists?(path)
63
+ provider.converge_by "create directory #{path} on #{node['name']}" do
64
+ transport.execute("New-Item #{escape(path)} -Type directory")
65
+ end
66
+ end
67
+ end
68
+
69
+ # Set file attributes { :owner, :group, :rights }
70
+ # def set_attributes(provider, path, attributes)
71
+ # end
72
+
73
+ # Get file attributes { :owner, :group, :rights }
74
+ # def get_attributes(path)
75
+ # end
76
+
77
+ def dirname_on_machine(path)
78
+ path.split(/[\\\/]/)[0..-2].join('\\')
79
+ end
80
+
81
+ def escape(string)
82
+ transport.escape(string)
83
+ end
84
+
85
+ def parse_boolean(string)
86
+ if string =~ /^\s*true\s*$/mi
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,71 @@
1
+ module ChefMetal
2
+ class Provisioner
3
+ # Acquire a machine, generally by provisioning it. Returns a Machine
4
+ # object pointing at the machine, allowing useful actions like setup,
5
+ # converge, execute, file and directory. The Machine object will have a
6
+ # "node" property which must be saved to the server (if it is any
7
+ # different from the original node object).
8
+ #
9
+ # ## Parameters
10
+ # provider - the provider object that is calling this method.
11
+ # node - node object (deserialized json) representing this machine. If
12
+ # the node has a provisioner_options hash in it, these will be used
13
+ # instead of options provided by the provisioner. TODO compare and
14
+ # fail if different?
15
+ # node will have node['normal']['provisioner_options'] in it with any
16
+ # options. It is a hash with at least these options:
17
+ #
18
+ # -- provisioner_url: <provisioner url>
19
+ #
20
+ # node['normal']['provisioner_output'] will be populated with
21
+ # information about the created machine. For vagrant, it is a hash
22
+ # with at least these options:
23
+ #
24
+ # -- provisioner_url: <provisioner url>
25
+ #
26
+ def acquire_machine(provider, node)
27
+ raise "#{self.class} does not override acquire_machine"
28
+ end
29
+
30
+ # Connect to a machine without acquiring it. This method will NOT make any
31
+ # changes to anything.
32
+ #
33
+ # ## Parameters
34
+ # node - node object (deserialized json) representing this machine. The
35
+ # node may have normal attributes "provisioner_options" and
36
+ # "provisioner_output" in it, representing the input and output of
37
+ # any prior "acquire_machine" process (if any).
38
+ #
39
+ def connect_to_machine(node)
40
+ raise "#{self.class} does not override connect_to_machine"
41
+ end
42
+
43
+ # Delete the given machine (idempotent). Should destroy the machine,
44
+ # returning things to the state before acquire_machine was called.
45
+ def delete_machine(provider, node)
46
+ raise "#{self.class} does not override delete_machine"
47
+ end
48
+
49
+ # Stop the given machine.
50
+ def stop_machine(provider, node)
51
+ raise "#{self.class} does not override stop_machine"
52
+ end
53
+
54
+ # Provider notification that happens at the point a resource is declared
55
+ # (after all properties have been set on it)
56
+ def resource_created(machine)
57
+ end
58
+
59
+ protected
60
+
61
+ def save_node(provider, node, chef_server)
62
+ # Save the node and create the client. TODO strip automatic attributes first so we don't race with "current state"
63
+ ChefMetal.inline_resource(provider) do
64
+ chef_node node['name'] do
65
+ chef_server chef_server
66
+ raw_json node
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,378 @@
1
+ require 'chef_metal/provisioner'
2
+ require 'chef_metal/aws_credentials'
3
+
4
+ module ChefMetal
5
+ class Provisioner
6
+
7
+ # Provisions machines in vagrant.
8
+ class FogProvisioner < Provisioner
9
+
10
+ include Chef::Mixin::ShellOut
11
+
12
+ DEFAULT_OPTIONS = {
13
+ :create_timeout => 600,
14
+ :start_timeout => 600,
15
+ :ssh_timeout => 20
16
+ }
17
+
18
+ # Create a new vagrant provisioner.
19
+ #
20
+ # ## Parameters
21
+ # compute_options - hash of options to be passed to Fog::Compute.new
22
+ # Special options:
23
+ # - :base_bootstrap_options is merged with bootstrap_options in acquire_machine
24
+ # to present the full set of bootstrap options. Write down any bootstrap_options
25
+ # you intend to apply universally here.
26
+ # - :aws_credentials is an AWS CSV file (created with Download Credentials)
27
+ # containing your aws key information. If you do not specify aws_access_key_id
28
+ # and aws_secret_access_key explicitly, the first line from this file
29
+ # will be used. You may pass a Cheffish::AWSCredentials object.
30
+ # - :create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
31
+ # - :start_timeout - the time to wait for the instance to start (defaults to 600)
32
+ # - :ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
33
+ def initialize(compute_options)
34
+ @base_bootstrap_options = compute_options.delete(:base_bootstrap_options) || {}
35
+ if compute_options[:provider] == 'AWS'
36
+ aws_credentials = compute_options.delete(:aws_credentials)
37
+ if aws_credentials
38
+ @aws_credentials = aws_credentials
39
+ else
40
+ @aws_credentials = ChefMetal::AWSCredentials.new
41
+ @aws_credentials.load_default
42
+ end
43
+ compute_options[:aws_access_key_id] ||= @aws_credentials.default[:access_key_id]
44
+ compute_options[:aws_secret_access_key] ||= @aws_credentials.default[:secret_access_key]
45
+ end
46
+ @key_pairs = {}
47
+ @compute_options = compute_options
48
+ @base_bootstrap_options_for = {}
49
+ end
50
+
51
+ attr_reader :compute_options
52
+ attr_reader :aws_credentials
53
+ attr_reader :key_pairs
54
+
55
+ def current_base_bootstrap_options
56
+ result = @base_bootstrap_options.dup
57
+ if compute_options[:provider] == 'AWS'
58
+ if key_pairs.size > 0
59
+ last_pair_name = key_pairs.keys.last
60
+ last_pair = key_pairs[last_pair_name]
61
+ result[:key_name] ||= last_pair_name
62
+ result[:private_key_path] ||= last_pair.private_key_path
63
+ result[:public_key_path] ||= last_pair.public_key_path
64
+ end
65
+ end
66
+ result
67
+ end
68
+
69
+ # Acquire a machine, generally by provisioning it. Returns a Machine
70
+ # object pointing at the machine, allowing useful actions like setup,
71
+ # converge, execute, file and directory. The Machine object will have a
72
+ # "node" property which must be saved to the server (if it is any
73
+ # different from the original node object).
74
+ #
75
+ # ## Parameters
76
+ # provider - the provider object that is calling this method.
77
+ # node - node object (deserialized json) representing this machine. If
78
+ # the node has a provisioner_options hash in it, these will be used
79
+ # instead of options provided by the provisioner. TODO compare and
80
+ # fail if different?
81
+ # node will have node['normal']['provisioner_options'] in it with any options.
82
+ # It is a hash with this format:
83
+ #
84
+ # -- provisioner_url: fog:<relevant_fog_options>
85
+ # -- bootstrap_options: hash of options to pass to compute.servers.create
86
+ # -- is_windows: true if windows. TODO detect this from ami?
87
+ # -- create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
88
+ # -- start_timeout - the time to wait for the instance to start (defaults to 600)
89
+ # -- ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
90
+ #
91
+ # Example bootstrap_options for ec2:
92
+ # :image_id =>'ami-311f2b45',
93
+ # :flavor_id =>'t1.micro',
94
+ # :key_name => 'pey-pair-name'
95
+ #
96
+ # node['normal']['provisioner_output'] will be populated with information
97
+ # about the created machine. For vagrant, it is a hash with this
98
+ # format:
99
+ #
100
+ # -- provisioner_url: fog:<relevant_fog_options>
101
+ # -- server_id: the ID of the server so it can be found again
102
+ #
103
+ def acquire_machine(provider, node)
104
+ # Set up the modified node data
105
+ provisioner_options = node['normal']['provisioner_options'] || {}
106
+ provisioner_output = node['normal']['provisioner_output'] || {
107
+ 'provisioner_url' => provisioner_url
108
+ }
109
+
110
+ if provisioner_output['provisioner_url'] != provisioner_url
111
+ raise "Switching providers for a machine is not currently supported! Use machine :destroy and then re-create the machine on the new provider."
112
+ end
113
+
114
+ node['normal']['provisioner_output'] = provisioner_output
115
+
116
+ if provisioner_output['server_id']
117
+
118
+ # If the server already exists, make sure it is up
119
+
120
+ # TODO verify that the server info matches the specification (ami, etc.)\
121
+ server = server_for(node)
122
+ if !server
123
+ Chef::Log.warn "Machine #{node['name']} (#{provisioner_output['server_id']} on #{provisioner_url}) is not associated with the ec2 account. Recreating ..."
124
+ need_to_create = true
125
+ elsif server.state == 'terminated' # Can't come back from that
126
+ Chef::Log.warn "Machine #{node['name']} (#{server.id} on #{provisioner_url}) is terminated. Recreating ..."
127
+ need_to_create = true
128
+ else
129
+ need_to_create = false
130
+ if !server.ready?
131
+ provider.converge_by "start machine #{node['name']} (#{server.id} on #{provisioner_url})" do
132
+ server.start
133
+ end
134
+ provider.converge_by "wait for machine #{node['name']} (#{server.id} on #{provisioner_url}) to be ready" do
135
+ wait_until_ready(server, option_for(node, :start_timeout))
136
+ end
137
+ else
138
+ wait_until_ready(server, option_for(node, :ssh_timeout))
139
+ end
140
+ end
141
+ else
142
+ need_to_create = true
143
+ end
144
+
145
+ if need_to_create
146
+ # If the server does not exist, create it
147
+ bootstrap_options = bootstrap_options_for(provider.new_resource, node)
148
+
149
+ start_time = Time.now
150
+ timeout = option_for(node, :create_timeout)
151
+
152
+ description = [ "create machine #{node['name']} on #{provisioner_url}" ]
153
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
154
+ server = nil
155
+ provider.converge_by description do
156
+ server = compute.servers.create(bootstrap_options)
157
+ provisioner_output['server_id'] = server.id
158
+ # Save quickly in case something goes wrong
159
+ save_node(provider, node, provider.new_resource.chef_server)
160
+ end
161
+
162
+ if server
163
+ # Re-retrieve the server in a more malleable form and wait for it to be ready
164
+ server = compute.servers.get(server.id)
165
+ provider.converge_by "machine #{node['name']} created as #{server.id} on #{provisioner_url}" do
166
+ end
167
+ # Wait for the machine to come up and for ssh to start listening
168
+ transport = nil
169
+ _self = self
170
+ provider.converge_by "wait for machine #{node['name']} to boot" do
171
+ server.wait_for(timeout - (Time.now - start_time)) do
172
+ if ready?
173
+ transport ||= _self.transport_for(server)
174
+ begin
175
+ transport.execute('pwd')
176
+ true
177
+ rescue Errno::ECONNREFUSED, Net::SSH::Disconnect
178
+ false
179
+ rescue
180
+ true
181
+ end
182
+ else
183
+ false
184
+ end
185
+ end
186
+ end
187
+
188
+ # If there is some other error, we just wait patiently for SSH
189
+ begin
190
+ server.wait_for(option_for(node, :ssh_timeout)) { transport.available? }
191
+ rescue Fog::Errors::TimeoutError
192
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
193
+ # some other problem. If this is the case, we restart the server
194
+ # to unstick it. Reboot covers a multitude of sins.
195
+ Chef::Log.warn "Machine #{node['name']} (#{server.id} on #{provisioner_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
196
+ provider.converge_by "reboot machine #{node['name']} to try to unstick it" do
197
+ server.reboot
198
+ end
199
+ provider.converge_by "wait for machine #{node['name']} to be ready after reboot" do
200
+ wait_until_ready(server, option_for(node, :start_timeout))
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ # Create machine object for callers to use
207
+ machine_for(node, server)
208
+ end
209
+
210
+ # Connect to machine without acquiring it
211
+ def connect_to_machine(node)
212
+ machine_for(node)
213
+ end
214
+
215
+ def delete_machine(provider, node)
216
+ if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
217
+ server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
218
+ provider.converge_by "destroy machine #{node['name']} (#{server.id} at #{provisioner_url}" do
219
+ server.destroy
220
+ end
221
+ convergence_strategy_for(node).delete_chef_objects(provider, node)
222
+ end
223
+ end
224
+
225
+ def stop_machine(provider, node)
226
+ # If the machine doesn't exist, we silently do nothing
227
+ if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
228
+ server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
229
+ provider.converge_by "stop machine #{node['name']} (#{server.id} at #{provisioner_url}" do
230
+ server.stop
231
+ end
232
+ end
233
+ end
234
+
235
+ def resource_created(machine)
236
+ @base_bootstrap_options_for[machine] = current_base_bootstrap_options
237
+ end
238
+
239
+
240
+ def compute
241
+ @compute ||= begin
242
+ require 'fog/compute'
243
+ require 'fog'
244
+ Fog::Compute.new(compute_options)
245
+ end
246
+ end
247
+
248
+ def provisioner_url
249
+ provider_identifier = case compute_options[:provider]
250
+ when 'AWS'
251
+ compute_options[:aws_access_key_id]
252
+ else
253
+ '???'
254
+ end
255
+ "fog:#{compute_options['provider']}:#{provider_identifier}"
256
+ end
257
+
258
+ def transport_for(server)
259
+ # TODO winrm
260
+ create_ssh_transport(server)
261
+ end
262
+
263
+ protected
264
+
265
+ def option_for(node, key)
266
+ if node['normal']['provisioner_options'] && node['normal']['provisioner_options'][key.to_s]
267
+ node['normal']['provisioner_options'][key.to_s]
268
+ elsif compute_options[key]
269
+ compute_options[key]
270
+ else
271
+ DEFAULT_OPTIONS[key]
272
+ end
273
+ end
274
+
275
+ def symbolize_keys(options)
276
+ options.inject({}) { |result,key,value| result[key.to_sym] = value; result }
277
+ end
278
+
279
+ def server_for(node)
280
+ if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
281
+ compute.servers.get(node['normal']['provisioner_output']['server_id'])
282
+ else
283
+ nil
284
+ end
285
+ end
286
+
287
+ def bootstrap_options_for(machine, node)
288
+ provisioner_options = node['normal']['provisioner_options'] || {}
289
+ bootstrap_options = @base_bootstrap_options_for[machine] || current_base_bootstrap_options
290
+ bootstrap_options = bootstrap_options.merge(symbolize_keys(provisioner_options['bootstrap_options'] || {}))
291
+ require 'socket'
292
+ require 'etc'
293
+ tags = {
294
+ 'Name' => node['name'],
295
+ 'BootstrapChefServer' => machine.chef_server[:chef_server_url],
296
+ 'BootstrapHost' => Socket.gethostname,
297
+ 'BootstrapUser' => Etc.getlogin,
298
+ 'BootstrapNodeName' => node['name']
299
+ }
300
+ if machine.chef_server[:options] && machine.chef_server[:options][:data_store]
301
+ tags['ChefLocalRepository'] = machine.chef_server[:options][:data_store].chef_fs.fs_description
302
+ end
303
+ # User-defined tags override the ones we set
304
+ tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
305
+ bootstrap_options.merge!({ :tags => tags })
306
+ end
307
+
308
+ def machine_for(node, server = nil)
309
+ server ||= server_for(node)
310
+ if !server
311
+ raise "Server for node #{node['name']} has not been created!"
312
+ end
313
+
314
+ if node['normal']['provisioner_options'] && node['normal']['provisioner_options']['is_windows']
315
+ require 'chef_metal/machine/windows_machine'
316
+ ChefMetal::Machine::WindowsMachine.new(node, transport_for(server), convergence_strategy_for(node))
317
+ else
318
+ require 'chef_metal/machine/unix_machine'
319
+ ChefMetal::Machine::UnixMachine.new(node, transport_for(server), convergence_strategy_for(node))
320
+ end
321
+ end
322
+
323
+ def convergence_strategy_for(node)
324
+ if node['normal']['provisioner_options'] && node['normal']['provisioner_options']['is_windows']
325
+ require 'chef_metal/convergence_strategy/install_msi'
326
+ ChefMetal::ConvergenceStrategy::InstallMsi.new
327
+ else
328
+ require 'chef_metal/convergence_strategy/install_sh'
329
+ ChefMetal::ConvergenceStrategy::InstallSh.new
330
+ end
331
+ end
332
+
333
+ def ssh_options_for(server)
334
+ result = {
335
+ # :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'],
336
+ # :paranoid => yes_or_no(vagrant_ssh_config['StrictHostKeyChecking']),
337
+ :auth_methods => [ 'publickey' ],
338
+ :keys => [ server.private_key ],
339
+ :keys_only => true
340
+ }
341
+ if compute_options[:provider] == 'AWS'
342
+ # TODO generalize for others?
343
+ result[:keys] = [ server.private_key || key_pairs[server.key_name].private_key_path ]
344
+ result[:host_key_alias] = "#{server.id}.ec2"
345
+ else
346
+ private_key_path = nil
347
+ end
348
+ result
349
+ end
350
+
351
+ def create_ssh_transport(server)
352
+ require 'chef_metal/transport/ssh'
353
+
354
+ ssh_options = ssh_options_for(server)
355
+ options = {
356
+ :prefix => 'sudo '
357
+ }
358
+ ChefMetal::Transport::SSH.new(server.public_ip_address, 'ubuntu', ssh_options, options)
359
+ end
360
+
361
+ def wait_until_ready(server, timeout)
362
+ transport = nil
363
+ _self = self
364
+ server.wait_for(timeout) do
365
+ if transport
366
+ transport.available?
367
+ elsif ready?
368
+ # Don't create the transport until the machine is ready (we won't have the host till then)
369
+ transport = _self.transport_for(server)
370
+ transport.available?
371
+ else
372
+ false
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end