chef-metal 0.1

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 (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