chef-metal-fog 0.4 → 0.5.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/chef/provider/fog_key_pair.rb +45 -21
- data/lib/chef/resource/fog_key_pair.rb +2 -7
- data/lib/chef_metal/driver_init/fog.rb +3 -0
- data/lib/chef_metal_fog/aws_credentials.rb +65 -0
- data/lib/chef_metal_fog/fog_driver.rb +622 -0
- data/lib/chef_metal_fog/fog_driver_aws.rb +133 -0
- data/lib/chef_metal_fog/recipe_dsl.rb +23 -0
- data/lib/chef_metal_fog/version.rb +1 -1
- data/lib/chef_metal_fog.rb +2 -19
- metadata +11 -8
- data/lib/chef_metal/provisioner_init/fog_init.rb +0 -4
- data/lib/chef_metal_fog/fog_provisioner.rb +0 -558
@@ -1,558 +0,0 @@
|
|
1
|
-
require 'chef_metal/provisioner'
|
2
|
-
require 'chef_metal/aws_credentials'
|
3
|
-
require 'chef_metal/openstack_credentials'
|
4
|
-
require 'chef_metal/machine/windows_machine'
|
5
|
-
require 'chef_metal/machine/unix_machine'
|
6
|
-
require 'chef_metal/convergence_strategy/install_msi'
|
7
|
-
require 'chef_metal/convergence_strategy/install_cached'
|
8
|
-
require 'chef_metal/transport/ssh'
|
9
|
-
require 'chef_metal_fog/version'
|
10
|
-
require 'fog'
|
11
|
-
require 'fog/core'
|
12
|
-
require 'fog/compute'
|
13
|
-
require 'fog/aws'
|
14
|
-
|
15
|
-
module ChefMetalFog
|
16
|
-
# Provisions machines in vagrant.
|
17
|
-
class FogProvisioner < ChefMetal::Provisioner
|
18
|
-
|
19
|
-
include Chef::Mixin::ShellOut
|
20
|
-
|
21
|
-
DEFAULT_OPTIONS = {
|
22
|
-
:create_timeout => 600,
|
23
|
-
:start_timeout => 600,
|
24
|
-
:ssh_timeout => 20
|
25
|
-
}
|
26
|
-
|
27
|
-
def self.inflate(node)
|
28
|
-
url = node['normal']['provisioner_output']['provisioner_url']
|
29
|
-
scheme, provider, id = url.split(':', 3)
|
30
|
-
FogProvisioner.new({ :provider => provider }, id)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Create a new fog provisioner.
|
34
|
-
#
|
35
|
-
# ## Parameters
|
36
|
-
# compute_options - hash of options to be passed to Fog::Compute.new
|
37
|
-
# Special options:
|
38
|
-
# - :base_bootstrap_options is merged with bootstrap_options in acquire_machine
|
39
|
-
# to present the full set of bootstrap options. Write down any bootstrap_options
|
40
|
-
# you intend to apply universally here.
|
41
|
-
# - :aws_credentials is an AWS CSV file (created with Download Credentials)
|
42
|
-
# containing your aws key information. If you do not specify aws_access_key_id
|
43
|
-
# and aws_secret_access_key explicitly, the first line from this file
|
44
|
-
# will be used. You may pass a Cheffish::AWSCredentials object.
|
45
|
-
# - :create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
|
46
|
-
# - :start_timeout - the time to wait for the instance to start (defaults to 600)
|
47
|
-
# - :ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
|
48
|
-
# id - the ID in the provisioner_url (fog:PROVIDER:ID)
|
49
|
-
def initialize(compute_options, id=nil)
|
50
|
-
@compute_options = compute_options
|
51
|
-
@base_bootstrap_options = compute_options.delete(:base_bootstrap_options) || {}
|
52
|
-
|
53
|
-
case compute_options[:provider]
|
54
|
-
when 'AWS'
|
55
|
-
aws_credentials = compute_options.delete(:aws_credentials)
|
56
|
-
if aws_credentials
|
57
|
-
@aws_credentials = aws_credentials
|
58
|
-
else
|
59
|
-
@aws_credentials = ChefMetal::AWSCredentials.new
|
60
|
-
@aws_credentials.load_default
|
61
|
-
end
|
62
|
-
compute_options[:aws_access_key_id] ||= @aws_credentials.default[:access_key_id]
|
63
|
-
compute_options[:aws_secret_access_key] ||= @aws_credentials.default[:secret_access_key]
|
64
|
-
# TODO actually find a key with the proper id
|
65
|
-
# TODO let the user specify credentials and provider profiles that we can use
|
66
|
-
if id && aws_login_info[0] != id
|
67
|
-
raise "Default AWS credentials point at AWS account #{aws_login_info[0]}, but inflating from URL #{id}"
|
68
|
-
end
|
69
|
-
when 'OpenStack'
|
70
|
-
openstack_credentials = compute_options.delete(:openstack_credentials)
|
71
|
-
if openstack_credentials
|
72
|
-
@openstack_credentials = openstack_credentials
|
73
|
-
else
|
74
|
-
@openstack_credentials = ChefMetal::OpenstackCredentials.new
|
75
|
-
@openstack_credentials.load_default
|
76
|
-
end
|
77
|
-
|
78
|
-
compute_options[:openstack_username] ||= @openstack_credentials.default[:openstack_username]
|
79
|
-
compute_options[:openstack_api_key] ||= @openstack_credentials.default[:openstack_api_key]
|
80
|
-
compute_options[:openstack_auth_url] ||= @openstack_credentials.default[:openstack_auth_url]
|
81
|
-
compute_options[:openstack_tenant] ||= @openstack_credentials.default[:openstack_tenant]
|
82
|
-
end
|
83
|
-
@key_pairs = {}
|
84
|
-
@base_bootstrap_options_for = {}
|
85
|
-
end
|
86
|
-
|
87
|
-
attr_reader :compute_options
|
88
|
-
attr_reader :aws_credentials
|
89
|
-
attr_reader :openstack_credentials
|
90
|
-
attr_reader :key_pairs
|
91
|
-
|
92
|
-
def current_base_bootstrap_options
|
93
|
-
result = @base_bootstrap_options.dup
|
94
|
-
if key_pairs.size > 0
|
95
|
-
last_pair_name = key_pairs.keys.last
|
96
|
-
last_pair = key_pairs[last_pair_name]
|
97
|
-
result[:key_name] ||= last_pair_name
|
98
|
-
result[:private_key_path] ||= last_pair.private_key_path
|
99
|
-
result[:public_key_path] ||= last_pair.public_key_path
|
100
|
-
end
|
101
|
-
result
|
102
|
-
end
|
103
|
-
|
104
|
-
# Inflate a provisioner from node information; we don't want to force the
|
105
|
-
# driver to figure out what the provisioner really needs, since it varies
|
106
|
-
# from provisioner to provisioner.
|
107
|
-
#
|
108
|
-
# ## Parameters
|
109
|
-
# node - node to inflate the provisioner for
|
110
|
-
#
|
111
|
-
# returns a FogProvisioner
|
112
|
-
# TODO: def self.inflate(node)
|
113
|
-
# right now, not implemented, will raise error from base class until overridden
|
114
|
-
|
115
|
-
# Acquire a machine, generally by provisioning it. Returns a Machine
|
116
|
-
# object pointing at the machine, allowing useful actions like setup,
|
117
|
-
# converge, execute, file and directory. The Machine object will have a
|
118
|
-
# "node" property which must be saved to the server (if it is any
|
119
|
-
# different from the original node object).
|
120
|
-
#
|
121
|
-
# ## Parameters
|
122
|
-
# action_handler - the action_handler object that is calling this method; this
|
123
|
-
# is generally a action_handler, but could be anything that can support the
|
124
|
-
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
125
|
-
# kitchen metal driver for acquiring and destroying VMs; see the base
|
126
|
-
# class for what needs providing).
|
127
|
-
# node - node object (deserialized json) representing this machine. If
|
128
|
-
# the node has a provisioner_options hash in it, these will be used
|
129
|
-
# instead of options provided by the provisioner. TODO compare and
|
130
|
-
# fail if different?
|
131
|
-
# node will have node['normal']['provisioner_options'] in it with any options.
|
132
|
-
# It is a hash with this format:
|
133
|
-
#
|
134
|
-
# -- provisioner_url: fog:<relevant_fog_options>
|
135
|
-
# -- bootstrap_options: hash of options to pass to compute.servers.create
|
136
|
-
# -- is_windows: true if windows. TODO detect this from ami?
|
137
|
-
# -- create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
|
138
|
-
# -- start_timeout - the time to wait for the instance to start (defaults to 600)
|
139
|
-
# -- ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
|
140
|
-
#
|
141
|
-
# Example bootstrap_options for ec2:
|
142
|
-
# 'bootstrap_options' => {
|
143
|
-
# 'image_id' =>'ami-311f2b45',
|
144
|
-
# 'flavor_id' =>'t1.micro',
|
145
|
-
# 'key_name' => 'key-pair-name'
|
146
|
-
# }
|
147
|
-
#
|
148
|
-
# node['normal']['provisioner_output'] will be populated with information
|
149
|
-
# about the created machine. For vagrant, it is a hash with this
|
150
|
-
# format:
|
151
|
-
#
|
152
|
-
# -- provisioner_url: fog:<relevant_fog_options>
|
153
|
-
# -- server_id: the ID of the server so it can be found again
|
154
|
-
#
|
155
|
-
def acquire_machine(action_handler, node)
|
156
|
-
# Set up the modified node data
|
157
|
-
creator = case compute_options[:provider]
|
158
|
-
when 'AWS'
|
159
|
-
aws_login_info[1]
|
160
|
-
when 'OpenStack'
|
161
|
-
compute_options[:openstack_username]
|
162
|
-
end
|
163
|
-
|
164
|
-
provisioner_output = node['normal']['provisioner_output'] || {
|
165
|
-
'provisioner_url' => provisioner_url,
|
166
|
-
'provisioner_version' => ChefMetalFog::VERSION,
|
167
|
-
'creator' => creator
|
168
|
-
}
|
169
|
-
|
170
|
-
if provisioner_output['provisioner_url'] != provisioner_url
|
171
|
-
raise "Switching a machine's provider from #{provisioner_output['provisioner_url']} to #{provisioner_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new provisioner."
|
172
|
-
end
|
173
|
-
|
174
|
-
node['normal']['provisioner_output'] = provisioner_output
|
175
|
-
|
176
|
-
if provisioner_output['server_id']
|
177
|
-
|
178
|
-
# If the server already exists, make sure it is up
|
179
|
-
|
180
|
-
# TODO verify that the server info matches the specification (ami, etc.)\
|
181
|
-
server = server_for(node)
|
182
|
-
if !server
|
183
|
-
Chef::Log.warn "Machine #{node['name']} (#{provisioner_output['server_id']} on #{provisioner_url}) is not associated with the ec2 account. Recreating ..."
|
184
|
-
need_to_create = true
|
185
|
-
elsif %w(terminated archive).include?(server.state) # Can't come back from that
|
186
|
-
Chef::Log.warn "Machine #{node['name']} (#{server.id} on #{provisioner_url}) is terminated. Recreating ..."
|
187
|
-
need_to_create = true
|
188
|
-
else
|
189
|
-
need_to_create = false
|
190
|
-
if !server.ready?
|
191
|
-
action_handler.perform_action "start machine #{node['name']} (#{server.id} on #{provisioner_url})" do
|
192
|
-
server.start
|
193
|
-
end
|
194
|
-
action_handler.perform_action "wait for machine #{node['name']} (#{server.id} on #{provisioner_url}) to be ready" do
|
195
|
-
wait_until_ready(server, option_for(node, :start_timeout))
|
196
|
-
end
|
197
|
-
else
|
198
|
-
wait_until_ready(server, option_for(node, :ssh_timeout))
|
199
|
-
end
|
200
|
-
end
|
201
|
-
else
|
202
|
-
need_to_create = true
|
203
|
-
end
|
204
|
-
|
205
|
-
if need_to_create
|
206
|
-
# If the server does not exist, create it
|
207
|
-
bootstrap_options = bootstrap_options_for(action_handler.new_resource, node)
|
208
|
-
bootstrap_options.merge(:name => action_handler.new_resource.name)
|
209
|
-
|
210
|
-
start_time = Time.now
|
211
|
-
timeout = option_for(node, :create_timeout)
|
212
|
-
|
213
|
-
description = [ "create machine #{node['name']} on #{provisioner_url}" ]
|
214
|
-
bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
|
215
|
-
server = nil
|
216
|
-
action_handler.perform_action description do
|
217
|
-
server = compute.servers.create(bootstrap_options)
|
218
|
-
provisioner_output['server_id'] = server.id
|
219
|
-
# Save quickly in case something goes wrong
|
220
|
-
save_node(action_handler, node, action_handler.new_resource.chef_server)
|
221
|
-
end
|
222
|
-
|
223
|
-
if server
|
224
|
-
@@ip_pool_lock = Mutex.new
|
225
|
-
# Re-retrieve the server in a more malleable form and wait for it to be ready
|
226
|
-
server = compute.servers.get(server.id)
|
227
|
-
if bootstrap_options[:floating_ip_pool]
|
228
|
-
Chef::Log.info 'Attaching IP from pool'
|
229
|
-
server.wait_for { ready? }
|
230
|
-
action_handler.perform_action "attach floating IP from #{bootstrap_options[:floating_ip_pool]} pool" do
|
231
|
-
attach_ip_from_pool(server, bootstrap_options[:floating_ip_pool])
|
232
|
-
end
|
233
|
-
elsif bootstrap_options[:floating_ip]
|
234
|
-
Chef::Log.info 'Attaching given IP'
|
235
|
-
server.wait_for { ready? }
|
236
|
-
action_handler.perform_action "attach floating IP #{bootstrap_options[:floating_ip]}" do
|
237
|
-
attach_ip(server, bootstrap_options[:allocation_id], bootstrap_options[:floating_ip])
|
238
|
-
end
|
239
|
-
end
|
240
|
-
action_handler.perform_action "machine #{node['name']} created as #{server.id} on #{provisioner_url}" do
|
241
|
-
end
|
242
|
-
# Wait for the machine to come up and for ssh to start listening
|
243
|
-
transport = nil
|
244
|
-
_self = self
|
245
|
-
action_handler.perform_action "wait for machine #{node['name']} to boot" do
|
246
|
-
server.wait_for(timeout - (Time.now - start_time)) do
|
247
|
-
if ready?
|
248
|
-
transport ||= _self.transport_for(server)
|
249
|
-
begin
|
250
|
-
transport.execute('pwd')
|
251
|
-
true
|
252
|
-
rescue ChefMetal::Transport::SSH::InitialConnectTimeout, Errno::ECONNREFUSED, Net::SSH::Disconnect, Errno::EHOSTUNREACH
|
253
|
-
false
|
254
|
-
rescue
|
255
|
-
true
|
256
|
-
end
|
257
|
-
else
|
258
|
-
false
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
# If there is some other error, we just wait patiently for SSH
|
264
|
-
begin
|
265
|
-
server.wait_for(option_for(node, :ssh_timeout)) { transport.available? }
|
266
|
-
rescue Fog::Errors::TimeoutError
|
267
|
-
# Sometimes (on EC2) the machine comes up but gets stuck or has
|
268
|
-
# some other problem. If this is the case, we restart the server
|
269
|
-
# to unstick it. Reboot covers a multitude of sins.
|
270
|
-
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 ..."
|
271
|
-
action_handler.perform_action "reboot machine #{node['name']} to try to unstick it" do
|
272
|
-
server.reboot
|
273
|
-
end
|
274
|
-
action_handler.perform_action "wait for machine #{node['name']} to be ready after reboot" do
|
275
|
-
wait_until_ready(server, option_for(node, :start_timeout))
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
# Create machine object for callers to use
|
282
|
-
machine_for(node, server)
|
283
|
-
end
|
284
|
-
|
285
|
-
# Attach IP to machine from IP pool
|
286
|
-
# Code taken from kitchen-openstack driver
|
287
|
-
# https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L196-L207
|
288
|
-
def attach_ip_from_pool(server, pool)
|
289
|
-
@@ip_pool_lock.synchronize do
|
290
|
-
Chef::Log.info "Attaching floating IP from <#{pool}> pool"
|
291
|
-
free_addrs = compute.addresses.collect do |i|
|
292
|
-
i.ip if i.fixed_ip.nil? and i.instance_id.nil? and i.pool == pool
|
293
|
-
end.compact
|
294
|
-
if free_addrs.empty?
|
295
|
-
raise ActionFailed, "No available IPs in pool <#{pool}>"
|
296
|
-
end
|
297
|
-
attach_ip(server, free_addrs[0])
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
# Attach given IP to machine
|
302
|
-
# Code taken from kitchen-openstack driver
|
303
|
-
# https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L209-L213
|
304
|
-
def attach_ip(server, allocation_id, ip)
|
305
|
-
Chef::Log.info "Attaching floating IP <#{ip}>"
|
306
|
-
compute.associate_address(:instance_id => server.id,
|
307
|
-
:allocation_id => allocation_id,
|
308
|
-
:public_ip => ip)
|
309
|
-
end
|
310
|
-
|
311
|
-
# Connect to machine without acquiring it
|
312
|
-
def connect_to_machine(node)
|
313
|
-
machine_for(node)
|
314
|
-
end
|
315
|
-
|
316
|
-
def delete_machine(action_handler, node)
|
317
|
-
if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
|
318
|
-
server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
|
319
|
-
if server
|
320
|
-
action_handler.perform_action "destroy machine #{node['name']} (#{node['normal']['provisioner_output']['server_id']} at #{provisioner_url})" do
|
321
|
-
server.destroy
|
322
|
-
end
|
323
|
-
end
|
324
|
-
convergence_strategy_for(node).cleanup_convergence(action_handler, node)
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
def stop_machine(action_handler, node)
|
329
|
-
# If the machine doesn't exist, we silently do nothing
|
330
|
-
if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
|
331
|
-
server = compute.servers.get(node['normal']['provisioner_output']['server_id'])
|
332
|
-
action_handler.perform_action "stop machine #{node['name']} (#{server.id} at #{provisioner_url})" do
|
333
|
-
server.stop
|
334
|
-
end
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
def resource_created(machine)
|
339
|
-
@base_bootstrap_options_for[machine] = current_base_bootstrap_options
|
340
|
-
end
|
341
|
-
|
342
|
-
def compute
|
343
|
-
@compute ||= Fog::Compute.new(compute_options)
|
344
|
-
end
|
345
|
-
|
346
|
-
def provisioner_url
|
347
|
-
provider_identifier = case compute_options[:provider]
|
348
|
-
when 'AWS'
|
349
|
-
aws_login_info[0]
|
350
|
-
when 'DigitalOcean'
|
351
|
-
compute_options[:digitalocean_client_id]
|
352
|
-
when 'OpenStack'
|
353
|
-
compute_options[:openstack_auth_url]
|
354
|
-
else
|
355
|
-
'???'
|
356
|
-
end
|
357
|
-
"fog:#{compute_options[:provider]}:#{provider_identifier}"
|
358
|
-
end
|
359
|
-
|
360
|
-
# Not meant to be part of public interface
|
361
|
-
def transport_for(server)
|
362
|
-
# TODO winrm
|
363
|
-
create_ssh_transport(server)
|
364
|
-
end
|
365
|
-
|
366
|
-
protected
|
367
|
-
|
368
|
-
def option_for(node, key)
|
369
|
-
if node['normal']['provisioner_options'] && node['normal']['provisioner_options'][key.to_s]
|
370
|
-
node['normal']['provisioner_options'][key.to_s]
|
371
|
-
elsif compute_options[key]
|
372
|
-
compute_options[key]
|
373
|
-
else
|
374
|
-
DEFAULT_OPTIONS[key]
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
# Returns [ Account ID, User ]
|
379
|
-
# Account ID is the 12 digit identifier on your Manage Account page in AWS Console. It is used as part of all ARNs identifying resources.
|
380
|
-
# User is an identifier like "root" or "user/username" or "federated-user/username"
|
381
|
-
def aws_login_info
|
382
|
-
@aws_login_info ||= begin
|
383
|
-
iam = Fog::AWS::IAM.new(:aws_access_key_id => compute_options[:aws_access_key_id], :aws_secret_access_key => compute_options[:aws_secret_access_key])
|
384
|
-
arn = begin
|
385
|
-
# TODO it would be nice if Fog let you do this normally ...
|
386
|
-
iam.send(:request, {
|
387
|
-
'Action' => 'GetUser',
|
388
|
-
:parser => Fog::Parsers::AWS::IAM::GetUser.new
|
389
|
-
}).body['User']['Arn']
|
390
|
-
rescue Fog::AWS::IAM::Error
|
391
|
-
# TODO Someone tell me there is a better way to find out your current
|
392
|
-
# user ID than this! This is what happens when you use an IAM user
|
393
|
-
# with default privileges.
|
394
|
-
if $!.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
|
395
|
-
arn = $1
|
396
|
-
else
|
397
|
-
raise
|
398
|
-
end
|
399
|
-
end
|
400
|
-
arn.split(':')[4..5]
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
def symbolize_keys(options)
|
405
|
-
options.inject({}) { |result,(key,value)| result[key.to_sym] = value; result }
|
406
|
-
end
|
407
|
-
|
408
|
-
def server_for(node)
|
409
|
-
if node['normal']['provisioner_output'] && node['normal']['provisioner_output']['server_id']
|
410
|
-
compute.servers.get(node['normal']['provisioner_output']['server_id'])
|
411
|
-
else
|
412
|
-
nil
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
def bootstrap_options_for(machine, node)
|
417
|
-
provisioner_options = node['normal']['provisioner_options'] || {}
|
418
|
-
bootstrap_options = @base_bootstrap_options_for[machine] || current_base_bootstrap_options
|
419
|
-
bootstrap_options = bootstrap_options.merge(symbolize_keys(provisioner_options['bootstrap_options'] || {}))
|
420
|
-
require 'socket'
|
421
|
-
require 'etc'
|
422
|
-
tags = {
|
423
|
-
'Name' => node['name'],
|
424
|
-
'BootstrapChefServer' => machine.chef_server[:chef_server_url],
|
425
|
-
'BootstrapHost' => Socket.gethostname,
|
426
|
-
'BootstrapUser' => Etc.getlogin,
|
427
|
-
'BootstrapNodeName' => node['name']
|
428
|
-
}
|
429
|
-
if machine.chef_server[:options] && machine.chef_server[:options][:data_store]
|
430
|
-
tags['ChefLocalRepository'] = machine.chef_server[:options][:data_store].chef_fs.fs_description
|
431
|
-
end
|
432
|
-
# User-defined tags override the ones we set
|
433
|
-
tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
|
434
|
-
bootstrap_options.merge!({ :tags => tags })
|
435
|
-
|
436
|
-
# Provide reasonable defaults for DigitalOcean
|
437
|
-
if compute_options[:provider] == 'DigitalOcean'
|
438
|
-
if !bootstrap_options[:image_id]
|
439
|
-
bootstrap_options[:image_name] ||= 'CentOS 6.4 x32'
|
440
|
-
bootstrap_options[:image_id] = compute.images.select { |image| image.name == bootstrap_options[:image_name] }.first.id
|
441
|
-
end
|
442
|
-
if !bootstrap_options[:flavor_id]
|
443
|
-
bootstrap_options[:flavor_name] ||= '512MB'
|
444
|
-
bootstrap_options[:flavor_id] = compute.flavors.select { |flavor| flavor.name == bootstrap_options[:flavor_name] }.first.id
|
445
|
-
end
|
446
|
-
if !bootstrap_options[:region_id]
|
447
|
-
bootstrap_options[:region_name] ||= 'San Francisco 1'
|
448
|
-
bootstrap_options[:region_id] = compute.regions.select { |region| region.name == bootstrap_options[:region_name] }.first.id
|
449
|
-
end
|
450
|
-
bootstrap_options[:ssh_key_ids] ||= [ compute.ssh_keys.select { |k| k.name == bootstrap_options[:key_name] }.first.id ]
|
451
|
-
|
452
|
-
# You don't get to specify name yourself
|
453
|
-
bootstrap_options[:name] = node['name']
|
454
|
-
end
|
455
|
-
|
456
|
-
bootstrap_options
|
457
|
-
end
|
458
|
-
|
459
|
-
def machine_for(node, server = nil)
|
460
|
-
server ||= server_for(node)
|
461
|
-
if !server
|
462
|
-
raise "Server for node #{node['name']} has not been created!"
|
463
|
-
end
|
464
|
-
|
465
|
-
if node['normal']['provisioner_options'] && node['normal']['provisioner_options']['is_windows']
|
466
|
-
ChefMetal::Machine::WindowsMachine.new(node, transport_for(server), convergence_strategy_for(node))
|
467
|
-
else
|
468
|
-
ChefMetal::Machine::UnixMachine.new(node, transport_for(server), convergence_strategy_for(node))
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
def convergence_strategy_for(node)
|
473
|
-
if node['normal']['provisioner_options'] && node['normal']['provisioner_options']['is_windows']
|
474
|
-
@windows_convergence_strategy ||= begin
|
475
|
-
options = {}
|
476
|
-
provisioner_options = node['normal']['provisioner_options'] || {}
|
477
|
-
options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
|
478
|
-
ChefMetal::ConvergenceStrategy::InstallMsi.new(options)
|
479
|
-
end
|
480
|
-
else
|
481
|
-
@unix_convergence_strategy ||= begin
|
482
|
-
options = {}
|
483
|
-
provisioner_options = node['normal']['provisioner_options'] || {}
|
484
|
-
options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
|
485
|
-
ChefMetal::ConvergenceStrategy::InstallCached.new(options)
|
486
|
-
end
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
def ssh_options_for(server)
|
491
|
-
result = {
|
492
|
-
# TODO create a user known hosts file
|
493
|
-
# :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'],
|
494
|
-
# :paranoid => true,
|
495
|
-
:auth_methods => [ 'publickey' ],
|
496
|
-
:keys_only => true,
|
497
|
-
:host_key_alias => "#{server.id}.#{compute_options[:provider]}"
|
498
|
-
}
|
499
|
-
if server.respond_to?(:private_key) && server.private_key
|
500
|
-
result[:key_data] = [ server.private_key ]
|
501
|
-
elsif server.respond_to?(:key_name) && key_pairs[server.key_name]
|
502
|
-
# TODO generalize for others?
|
503
|
-
result[:keys] ||= [ key_pairs[server.key_name].private_key_path ]
|
504
|
-
else
|
505
|
-
# TODO need a way to know which key if there were multiple
|
506
|
-
result[:keys] = [ key_pairs.first[1].private_key_path ]
|
507
|
-
end
|
508
|
-
result
|
509
|
-
end
|
510
|
-
|
511
|
-
def create_ssh_transport(server)
|
512
|
-
ssh_options = ssh_options_for(server)
|
513
|
-
# If we're on AWS, the default is to use ubuntu, not root
|
514
|
-
if compute_options[:provider] == 'AWS'
|
515
|
-
username = compute_options[:ssh_username] || 'ubuntu'
|
516
|
-
else
|
517
|
-
username = compute_options[:ssh_username] || 'root'
|
518
|
-
end
|
519
|
-
options = {}
|
520
|
-
if compute_options[:sudo] || (!compute_options.has_key?(:sudo) && username != 'root')
|
521
|
-
options[:prefix] = 'sudo '
|
522
|
-
end
|
523
|
-
|
524
|
-
remote_host = nil
|
525
|
-
if compute_options[:use_private_ip_for_ssh]
|
526
|
-
remote_host = server.private_ip_address
|
527
|
-
elsif !server.public_ip_address
|
528
|
-
Chef::Log.warn("Server has no public ip address. Using private ip '#{server.private_ip_address}'. Set provisioner option 'use_private_ip_for_ssh' => true if this will always be the case ...")
|
529
|
-
remote_host = server.private_ip_address
|
530
|
-
elsif server.public_ip_address
|
531
|
-
remote_host = server.public_ip_address
|
532
|
-
else
|
533
|
-
raise "Server #{server.id} has no private or public IP address!"
|
534
|
-
end
|
535
|
-
|
536
|
-
#Enable pty by default
|
537
|
-
options[:ssh_pty_enable] = true
|
538
|
-
|
539
|
-
ChefMetal::Transport::SSH.new(remote_host, username, ssh_options, options)
|
540
|
-
end
|
541
|
-
|
542
|
-
def wait_until_ready(server, timeout)
|
543
|
-
transport = nil
|
544
|
-
_self = self
|
545
|
-
server.wait_for(timeout) do
|
546
|
-
if transport
|
547
|
-
transport.available?
|
548
|
-
elsif ready?
|
549
|
-
# Don't create the transport until the machine is ready (we won't have the host till then)
|
550
|
-
transport = _self.transport_for(server)
|
551
|
-
transport.available?
|
552
|
-
else
|
553
|
-
false
|
554
|
-
end
|
555
|
-
end
|
556
|
-
end
|
557
|
-
end
|
558
|
-
end
|