chef-metal-fog 0.5.4 → 0.6
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.
- checksums.yaml +4 -4
- data/lib/chef/provider/fog_key_pair.rb +13 -2
- data/lib/chef_metal_fog/fog_driver.rb +64 -194
- data/lib/chef_metal_fog/providers/aws/credentials.rb +71 -0
- data/lib/chef_metal_fog/providers/aws.rb +214 -0
- data/lib/chef_metal_fog/providers/cloudstack.rb +36 -0
- data/lib/chef_metal_fog/providers/digitalocean.rb +106 -0
- data/lib/chef_metal_fog/providers/openstack.rb +37 -0
- data/lib/chef_metal_fog/providers/rackspace.rb +38 -0
- data/lib/chef_metal_fog/version.rb +1 -1
- data/spec/spec_helper.rb +18 -0
- data/spec/support/aws/config-file.csv +2 -0
- data/spec/support/aws/ini-file.ini +7 -0
- data/spec/support/chef_metal_fog/providers/testdriver.rb +14 -0
- data/spec/unit/fog_driver_spec.rb +32 -0
- data/spec/unit/providers/aws/credentials_spec.rb +42 -0
- data/spec/unit/providers/rackspace_spec.rb +16 -0
- metadata +29 -4
- data/lib/chef_metal_fog/aws_credentials.rb +0 -67
- data/lib/chef_metal_fog/fog_driver_aws.rb +0 -142
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1fdb71aedeacd1389f9b204d2518a566cd129f8
|
4
|
+
data.tar.gz: 06a458753dad712619205ad5cccee51c8cbd00c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3b39d661695873096fd28868f735e8d73266cc19b08e5ab646a092e0a8cee2dceebe4ab09aaf9c4f4de04248dafd42fc9bc5eba34d172480a2d5aec37e53329
|
7
|
+
data.tar.gz: 1e0f3e6766d5aa9da0b38a2f8e236764bea3de9b68c54b4d70d13188e29ef4da2f5dcf47e11ac0b6e863aea7478b7d598a154341cf758f271f062c1368e5f563
|
@@ -70,8 +70,19 @@ class Chef::Provider::FogKeyPair < Chef::Provider::LWRPBase
|
|
70
70
|
# them matches.
|
71
71
|
new_fingerprints = [Cheffish::KeyFormatter.encode(desired_key, :format => :fingerprint)]
|
72
72
|
if RUBY_VERSION.to_f < 2.0
|
73
|
-
|
74
|
-
|
73
|
+
if @@use_pkcs8.nil?
|
74
|
+
begin
|
75
|
+
require 'openssl_pkcs8'
|
76
|
+
@@use_pkcs8 = true
|
77
|
+
rescue LoadError
|
78
|
+
Chef::Log.warn("The openssl_pkcs8 gem is not loaded: you may not be able to read key fingerprints created by some cloud providers. gem install openssl_pkcs8 to fix!")
|
79
|
+
@@use_pkcs8 = false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if @@use_pkcs8
|
83
|
+
new_fingerprints << Cheffish::KeyFormatter.encode(desired_private_key,
|
84
|
+
:format => :pkcs8sha1fingerprint)
|
85
|
+
end
|
75
86
|
end
|
76
87
|
end
|
77
88
|
|
@@ -8,11 +8,9 @@ require 'chef_metal/convergence_strategy/install_cached'
|
|
8
8
|
require 'chef_metal/convergence_strategy/no_converge'
|
9
9
|
require 'chef_metal/transport/ssh'
|
10
10
|
require 'chef_metal_fog/version'
|
11
|
-
require 'chef_metal_fog/fog_driver_aws'
|
12
11
|
require 'fog'
|
13
12
|
require 'fog/core'
|
14
13
|
require 'fog/compute'
|
15
|
-
require 'fog/aws'
|
16
14
|
require 'socket'
|
17
15
|
require 'etc'
|
18
16
|
require 'time'
|
@@ -25,14 +23,8 @@ module ChefMetalFog
|
|
25
23
|
# ## Fog Driver URLs
|
26
24
|
#
|
27
25
|
# All Metal drivers use URLs to uniquely identify a driver's "bucket" of machines.
|
28
|
-
# Fog URLs are of the form fog:<provider>:<identifier
|
29
|
-
#
|
30
|
-
# fog:AWS:<account_id>:<region>
|
31
|
-
# fog:AWS:<profile_name>
|
32
|
-
# fog:AWS:<profile_name>:<region>
|
33
|
-
# fog:OpenStack:https://identityHost:portNumber/v2.0
|
34
|
-
# fog:DigitalOcean:<client id>
|
35
|
-
# fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
|
26
|
+
# Fog URLs are of the form fog:<provider>:<identifier:> - see individual providers
|
27
|
+
# for sample URLs.
|
36
28
|
#
|
37
29
|
# Identifier is generally something uniquely identifying the account. If multiple
|
38
30
|
# users can access the account, the identifier should be the same for all of
|
@@ -102,6 +94,31 @@ module ChefMetalFog
|
|
102
94
|
:ssh_timeout => 20
|
103
95
|
}
|
104
96
|
|
97
|
+
class << self
|
98
|
+
alias :__new__ :new
|
99
|
+
|
100
|
+
def inherited(klass)
|
101
|
+
class << klass
|
102
|
+
alias :new :__new__
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
@@registered_provider_classes = {}
|
108
|
+
def self.register_provider_class(name, driver)
|
109
|
+
@@registered_provider_classes[name] = driver
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.provider_class_for(provider)
|
113
|
+
require "chef_metal_fog/providers/#{provider.downcase}"
|
114
|
+
@@registered_provider_classes[provider]
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.new(driver_url, config)
|
118
|
+
provider = driver_url.split(':')[1]
|
119
|
+
provider_class_for(provider).new(driver_url, config)
|
120
|
+
end
|
121
|
+
|
105
122
|
# Passed in a driver_url, and a config in the format of Driver.config.
|
106
123
|
def self.from_url(driver_url, config)
|
107
124
|
FogDriver.new(driver_url, config)
|
@@ -109,7 +126,7 @@ module ChefMetalFog
|
|
109
126
|
|
110
127
|
def self.canonicalize_url(driver_url, config)
|
111
128
|
_, provider, id = driver_url.split(':', 3)
|
112
|
-
config, id = compute_options_for(provider, id, config)
|
129
|
+
config, id = provider_class_for(provider).compute_options_for(provider, id, config)
|
113
130
|
[ "fog:#{provider}:#{id}", config ]
|
114
131
|
end
|
115
132
|
|
@@ -117,7 +134,7 @@ module ChefMetalFog
|
|
117
134
|
# know what it is yet) but which has the same keys
|
118
135
|
def self.from_provider(provider, config)
|
119
136
|
# Figure out the options and merge them into the config
|
120
|
-
config, id = compute_options_for(provider, nil, config)
|
137
|
+
config, id = provider_class_for(provider).compute_options_for(provider, nil, config)
|
121
138
|
|
122
139
|
driver_url = "fog:#{provider}:#{id}"
|
123
140
|
|
@@ -230,6 +247,10 @@ module ChefMetalFog
|
|
230
247
|
machine_options[key] || DEFAULT_OPTIONS[key]
|
231
248
|
end
|
232
249
|
|
250
|
+
def creator
|
251
|
+
raise "unsupported fog provider #{provider} (please implement #creator)"
|
252
|
+
end
|
253
|
+
|
233
254
|
def create_server(action_handler, machine_spec, machine_options)
|
234
255
|
if machine_spec.location
|
235
256
|
if machine_spec.location['driver_url'] != driver_url
|
@@ -255,21 +276,13 @@ module ChefMetalFog
|
|
255
276
|
server = nil
|
256
277
|
action_handler.report_progress description
|
257
278
|
if action_handler.should_perform_actions
|
258
|
-
creator = case provider
|
259
|
-
when 'AWS'
|
260
|
-
driver_options[:aws_account_info][:aws_username]
|
261
|
-
when 'OpenStack'
|
262
|
-
compute_options[:openstack_username]
|
263
|
-
when 'Rackspace'
|
264
|
-
compute_options[:rackspace_username]
|
265
|
-
end
|
266
279
|
server = compute.servers.create(bootstrap_options)
|
267
280
|
machine_spec.location = {
|
268
281
|
'driver_url' => driver_url,
|
269
282
|
'driver_version' => ChefMetalFog::VERSION,
|
270
283
|
'server_id' => server.id,
|
271
284
|
'creator' => creator,
|
272
|
-
'allocated_at' => Time.now.
|
285
|
+
'allocated_at' => Time.now.to_i
|
273
286
|
}
|
274
287
|
machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
|
275
288
|
%w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
|
@@ -291,7 +304,7 @@ module ChefMetalFog
|
|
291
304
|
if server.state == 'stopped'
|
292
305
|
action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
|
293
306
|
server.start
|
294
|
-
machine_spec.location['started_at'] = Time.now.
|
307
|
+
machine_spec.location['started_at'] = Time.now.to_i
|
295
308
|
end
|
296
309
|
machine_spec.save(action_handler)
|
297
310
|
end
|
@@ -300,20 +313,28 @@ module ChefMetalFog
|
|
300
313
|
def restart_server(action_handler, machine_spec, server)
|
301
314
|
action_handler.perform_action "restart machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
|
302
315
|
server.reboot
|
303
|
-
machine_spec.location['started_at'] = Time.now.
|
316
|
+
machine_spec.location['started_at'] = Time.now.to_i
|
304
317
|
end
|
305
318
|
machine_spec.save(action_handler)
|
306
319
|
end
|
307
320
|
|
308
321
|
def remaining_wait_time(machine_spec, machine_options)
|
309
322
|
if machine_spec.location['started_at']
|
310
|
-
timeout = option_for(machine_options, :start_timeout) - (Time.now.utc -
|
323
|
+
timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.location['started_at']))
|
311
324
|
else
|
312
|
-
timeout = option_for(machine_options, :create_timeout) - (Time.now.utc -
|
325
|
+
timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.location['allocated_at']))
|
313
326
|
end
|
314
327
|
timeout > 0 ? timeout : 0.01
|
315
328
|
end
|
316
329
|
|
330
|
+
def parse_time(value)
|
331
|
+
if value.is_a?(String)
|
332
|
+
Time.parse(value)
|
333
|
+
else
|
334
|
+
Time.at(value)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
317
338
|
def wait_until_ready(action_handler, machine_spec, machine_options, server)
|
318
339
|
if !server.ready?
|
319
340
|
if action_handler.should_perform_actions
|
@@ -418,9 +439,15 @@ module ChefMetalFog
|
|
418
439
|
|
419
440
|
def bootstrap_options_for(action_handler, machine_spec, machine_options)
|
420
441
|
bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
|
421
|
-
|
422
|
-
|
423
|
-
|
442
|
+
|
443
|
+
bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
|
444
|
+
|
445
|
+
bootstrap_options[:name] ||= machine_spec.name
|
446
|
+
|
447
|
+
bootstrap_options
|
448
|
+
end
|
449
|
+
|
450
|
+
def default_tags(machine_spec, bootstrap_tags = {})
|
424
451
|
tags = {
|
425
452
|
'Name' => machine_spec.name,
|
426
453
|
'BootstrapId' => machine_spec.id,
|
@@ -428,36 +455,7 @@ module ChefMetalFog
|
|
428
455
|
'BootstrapUser' => Etc.getlogin
|
429
456
|
}
|
430
457
|
# User-defined tags override the ones we set
|
431
|
-
tags.merge
|
432
|
-
bootstrap_options.merge!({ :tags => tags })
|
433
|
-
|
434
|
-
# Provide reasonable defaults for DigitalOcean
|
435
|
-
if provider == 'DigitalOcean'
|
436
|
-
if !bootstrap_options[:image_id]
|
437
|
-
bootstrap_options[:image_name] ||= 'CentOS 6.4 x32'
|
438
|
-
bootstrap_options[:image_id] = compute.images.select { |image| image.name == bootstrap_options[:image_name] }.first.id
|
439
|
-
end
|
440
|
-
if !bootstrap_options[:flavor_id]
|
441
|
-
bootstrap_options[:flavor_name] ||= '512MB'
|
442
|
-
bootstrap_options[:flavor_id] = compute.flavors.select { |flavor| flavor.name == bootstrap_options[:flavor_name] }.first.id
|
443
|
-
end
|
444
|
-
if !bootstrap_options[:region_id]
|
445
|
-
bootstrap_options[:region_name] ||= 'San Francisco 1'
|
446
|
-
bootstrap_options[:region_id] = compute.regions.select { |region| region.name == bootstrap_options[:region_name] }.first.id
|
447
|
-
end
|
448
|
-
found_key = compute.ssh_keys.select { |k| k.name == bootstrap_options[:key_name] }.first
|
449
|
-
if !found_key
|
450
|
-
raise "Could not find key named '#{bootstrap_options[:key_name]}' on #{driver_url}"
|
451
|
-
end
|
452
|
-
bootstrap_options[:ssh_key_ids] ||= [ found_key.id ]
|
453
|
-
|
454
|
-
# You don't get to specify name yourself
|
455
|
-
bootstrap_options[:name] = machine_spec.name
|
456
|
-
end
|
457
|
-
|
458
|
-
bootstrap_options[:name] ||= machine_spec.name
|
459
|
-
|
460
|
-
bootstrap_options
|
458
|
+
tags.merge(bootstrap_tags)
|
461
459
|
end
|
462
460
|
|
463
461
|
def machine_for(machine_spec, machine_options, server = nil)
|
@@ -503,6 +501,8 @@ module ChefMetalFog
|
|
503
501
|
result[:key_data] = [ get_private_key(server.key_name) ]
|
504
502
|
elsif machine_spec.location['key_name']
|
505
503
|
result[:key_data] = [ get_private_key(machine_spec.location['key_name']) ]
|
504
|
+
elsif machine_options[:bootstrap_options][:key_path]
|
505
|
+
result[:key_data] = [ IO.read(machine_options[:bootstrap_options][:key_path]) ]
|
506
506
|
elsif machine_options[:bootstrap_options][:key_name]
|
507
507
|
result[:key_data] = [ get_private_key(machine_options[:bootstrap_options][:key_name]) ]
|
508
508
|
else
|
@@ -512,14 +512,13 @@ module ChefMetalFog
|
|
512
512
|
result
|
513
513
|
end
|
514
514
|
|
515
|
+
def default_ssh_username
|
516
|
+
'root'
|
517
|
+
end
|
518
|
+
|
515
519
|
def create_ssh_transport(machine_spec, machine_options, server)
|
516
520
|
ssh_options = ssh_options_for(machine_spec, machine_options, server)
|
517
|
-
|
518
|
-
if provider == 'AWS'
|
519
|
-
username = machine_spec.location['ssh_username'] || 'ubuntu'
|
520
|
-
else
|
521
|
-
username = machine_spec.location['ssh_username'] || 'root'
|
522
|
-
end
|
521
|
+
username = machine_spec.location['ssh_username'] || default_ssh_username
|
523
522
|
if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.location['ssh_username']
|
524
523
|
Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.location['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.location['ssh_username']}. Please edit the node and change the metal.location.ssh_username attribute if you want to change it.")
|
525
524
|
end
|
@@ -548,136 +547,7 @@ module ChefMetalFog
|
|
548
547
|
end
|
549
548
|
|
550
549
|
def self.compute_options_for(provider, id, config)
|
551
|
-
|
552
|
-
compute_options = driver_options[:compute_options] || {}
|
553
|
-
new_compute_options = {}
|
554
|
-
new_compute_options[:provider] = provider
|
555
|
-
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
556
|
-
new_defaults = {
|
557
|
-
:driver_options => { :compute_options => {} },
|
558
|
-
:machine_options => { :bootstrap_options => {} }
|
559
|
-
}
|
560
|
-
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
561
|
-
|
562
|
-
# Get data from the identifier in the URL
|
563
|
-
if id && id != ''
|
564
|
-
case provider
|
565
|
-
when 'AWS'
|
566
|
-
# AWS canonical URLs are of the form fog:AWS:
|
567
|
-
if id =~ /^(\d{12})(:(.+))?$/
|
568
|
-
if $2
|
569
|
-
id = $1
|
570
|
-
new_compute_options[:region] = $3
|
571
|
-
else
|
572
|
-
Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-metal (before 0.11-final) found. If you have servers in multiple regions on this account, you may see odd behavior like servers being recreated. To fix, edit any nodes with attribute metal.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
|
573
|
-
end
|
574
|
-
else
|
575
|
-
# Assume it is a profile name, and set that.
|
576
|
-
aws_profile, region = id.split(':', 2)
|
577
|
-
new_config[:driver_options][:aws_profile] = aws_profile
|
578
|
-
new_compute_options[:region] = region
|
579
|
-
id = nil
|
580
|
-
end
|
581
|
-
when 'DigitalOcean'
|
582
|
-
new_compute_options[:digitalocean_client_id] = id
|
583
|
-
when 'OpenStack'
|
584
|
-
new_compute_options[:openstack_auth_url] = id
|
585
|
-
when 'Rackspace'
|
586
|
-
new_compute_options[:rackspace_auth_url] = id
|
587
|
-
when 'CloudStack'
|
588
|
-
cloudstack_uri = URI.parse(id)
|
589
|
-
new_compute_options[:cloudstack_scheme] = cloudstack_uri.scheme
|
590
|
-
new_compute_options[:cloudstack_host] = cloudstack_uri.host
|
591
|
-
new_compute_options[:cloudstack_port] = cloudstack_uri.port
|
592
|
-
new_compute_options[:cloudstack_path] = cloudstack_uri.path
|
593
|
-
else
|
594
|
-
raise "unsupported fog provider #{provider}"
|
595
|
-
end
|
596
|
-
end
|
597
|
-
|
598
|
-
# Set auth info from environment
|
599
|
-
case provider
|
600
|
-
when 'AWS'
|
601
|
-
# Grab the profile
|
602
|
-
aws_profile = FogDriverAWS.get_aws_profile(result[:driver_options], id)
|
603
|
-
new_compute_options[:aws_access_key_id] = aws_profile[:aws_access_key_id]
|
604
|
-
new_compute_options[:aws_secret_access_key] = aws_profile[:aws_secret_access_key]
|
605
|
-
new_compute_options[:aws_session_token] = aws_profile[:aws_security_token]
|
606
|
-
new_defaults[:driver_options][:compute_options][:region] = aws_profile[:region]
|
607
|
-
when 'OpenStack'
|
608
|
-
# TODO it is supposed to be unnecessary to load credentials from fog this way;
|
609
|
-
# why are we doing it?
|
610
|
-
# TODO support http://docs.openstack.org/cli-reference/content/cli_openrc.html
|
611
|
-
credential = Fog.credentials
|
612
|
-
|
613
|
-
new_compute_options[:openstack_username] ||= credential[:openstack_username]
|
614
|
-
new_compute_options[:openstack_api_key] ||= credential[:openstack_api_key]
|
615
|
-
new_compute_options[:openstack_auth_url] ||= credential[:openstack_auth_url]
|
616
|
-
new_compute_options[:openstack_tenant] ||= credential[:openstack_tenant]
|
617
|
-
when 'Rackspace'
|
618
|
-
credential = Fog.credentials
|
619
|
-
|
620
|
-
new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
|
621
|
-
new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
|
622
|
-
new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
|
623
|
-
new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
|
624
|
-
new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
|
625
|
-
new_compute_options[:rackspace_compute_url] ||= credential[:rackspace_compute_url]
|
626
|
-
when 'DigitalOcean'
|
627
|
-
# This uses ~/.tugboat, generated by "tugboat authorize" - see https://github.com/pearkes/tugboat
|
628
|
-
tugboat_file = File.expand_path('~/.tugboat')
|
629
|
-
if File.exist?(tugboat_file)
|
630
|
-
tugboat_data = YAML.load(IO.read(tugboat_file))
|
631
|
-
new_compute_options.merge!(
|
632
|
-
:digitalocean_client_id => tugboat_data['authentication']['client_key'],
|
633
|
-
:digitalocean_api_key => tugboat_data['authentication']['api_key']
|
634
|
-
)
|
635
|
-
new_defaults[:machine_options].merge!(
|
636
|
-
#:ssh_username => tugboat_data['ssh']['ssh_user'],
|
637
|
-
:ssh_options => {
|
638
|
-
:port => tugboat_data['ssh']['ssh_port'],
|
639
|
-
# TODO we ignore ssh_key_path in favor of ssh_key / key_name stuff
|
640
|
-
#:key_data => [ IO.read(tugboat_data['ssh']['ssh_key_path']) ] # TODO use paths, not data?
|
641
|
-
}
|
642
|
-
)
|
643
|
-
|
644
|
-
# TODO verify that the key_name exists and matches the ssh key path
|
645
|
-
|
646
|
-
new_defaults[:machine_options][:bootstrap_options].merge!(
|
647
|
-
:region_id => tugboat_data['defaults']['region'].to_i,
|
648
|
-
:image_id => tugboat_data['defaults']['image'].to_i,
|
649
|
-
:size_id => tugboat_data['defaults']['region'].to_i,
|
650
|
-
:private_networking => tugboat_data['defaults']['private_networking'] == 'true',
|
651
|
-
:backups_enabled => tugboat_data['defaults']['backups_enabled'] == 'true',
|
652
|
-
)
|
653
|
-
ssh_key = tugboat_data['defaults']['ssh_key']
|
654
|
-
if ssh_key && ssh_key.size > 0
|
655
|
-
new_defaults[:machine_options][:bootstrap_options][:key_name] = ssh_key
|
656
|
-
end
|
657
|
-
end
|
658
|
-
end
|
659
|
-
|
660
|
-
id = case provider
|
661
|
-
when 'AWS'
|
662
|
-
account_info = FogDriverAWS.aws_account_info_for(result[:driver_options][:compute_options])
|
663
|
-
new_config[:driver_options][:aws_account_info] = account_info
|
664
|
-
"#{account_info[:aws_account_id]}:#{result[:driver_options][:compute_options][:region]}"
|
665
|
-
when 'DigitalOcean'
|
666
|
-
result[:driver_options][:compute_options][:digitalocean_client_id]
|
667
|
-
when 'OpenStack'
|
668
|
-
result[:driver_options][:compute_options][:openstack_auth_url]
|
669
|
-
when 'Rackspace'
|
670
|
-
result[:driver_options][:compute_options][:rackspace_auth_url]
|
671
|
-
when 'CloudStack'
|
672
|
-
host = result[:driver_options][:compute_options][:cloudstack_host]
|
673
|
-
path = result[:driver_options][:compute_options][:cloudstack_path] || '/client/api'
|
674
|
-
port = result[:driver_options][:compute_options][:cloudstack_port] || 443
|
675
|
-
scheme = result[:driver_options][:compute_options][:cloudstack_scheme] || 'https'
|
676
|
-
|
677
|
-
URI.scheme_list[scheme.upcase].build(:host => host, :port => port, :path => path).to_s
|
678
|
-
end
|
679
|
-
|
680
|
-
[ result, id ]
|
550
|
+
raise "unsupported fog provider #{provider}"
|
681
551
|
end
|
682
552
|
end
|
683
553
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module ChefMetalFog
|
5
|
+
module Providers
|
6
|
+
class AWS
|
7
|
+
# Reads in a credentials file in Amazon's download format and presents the credentials to you
|
8
|
+
class Credentials
|
9
|
+
def initialize
|
10
|
+
@credentials = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
def default
|
16
|
+
@credentials[ENV['AWS_DEFAULT_PROFILE'] || 'default'] || @credentials.first[1]
|
17
|
+
end
|
18
|
+
|
19
|
+
def keys
|
20
|
+
@credentials.keys
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](name)
|
24
|
+
@credentials[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def each(&block)
|
28
|
+
@credentials.each(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_ini(credentials_ini_file)
|
32
|
+
inifile = IniFile.load(File.expand_path(credentials_ini_file))
|
33
|
+
inifile.each_section do |section|
|
34
|
+
if section =~ /^\s*profile\s+(.+)$/ || section =~ /^\s*(default)\s*/
|
35
|
+
profile_name = $1.strip
|
36
|
+
profile = inifile[section].inject({}) do |result, pair|
|
37
|
+
result[pair[0].to_sym] = pair[1]
|
38
|
+
result
|
39
|
+
end
|
40
|
+
profile[:name] = profile_name
|
41
|
+
@credentials[profile_name] = profile
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_csv(credentials_csv_file)
|
47
|
+
CSV.new(File.open(credentials_csv_file), :headers => :first_row).each do |row|
|
48
|
+
@credentials[row['User Name']] = {
|
49
|
+
:name => row['User Name'],
|
50
|
+
:user_name => row['User Name'],
|
51
|
+
:aws_access_key_id => row['Access Key Id'],
|
52
|
+
:aws_secret_access_key => row['Secret Access Key']
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_default
|
58
|
+
load_ini(ENV['AWS_CONFIG_FILE'] || File.expand_path('~/.aws/config'))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.method_missing(name, *args, &block)
|
62
|
+
singleton.send(name, *args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.singleton
|
66
|
+
@aws_credentials ||= Credentials.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'chef/log'
|
2
|
+
require 'fog/aws'
|
3
|
+
|
4
|
+
# fog:AWS:<account_id>:<region>
|
5
|
+
# fog:AWS:<profile_name>
|
6
|
+
# fog:AWS:<profile_name>:<region>
|
7
|
+
module ChefMetalFog
|
8
|
+
module Providers
|
9
|
+
class AWS < ChefMetalFog::FogDriver
|
10
|
+
|
11
|
+
require_relative 'aws/credentials'
|
12
|
+
|
13
|
+
ChefMetalFog::FogDriver.register_provider_class('AWS', ChefMetalFog::Providers::AWS)
|
14
|
+
|
15
|
+
def creator
|
16
|
+
driver_options[:aws_account_info][:aws_username]
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_ssh_username
|
20
|
+
'ubuntu'
|
21
|
+
end
|
22
|
+
|
23
|
+
def bootstrap_options_for(action_handler, machine_spec, machine_options)
|
24
|
+
bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
|
25
|
+
|
26
|
+
unless !bootstrap_options[:key_name]
|
27
|
+
bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler)
|
28
|
+
end
|
29
|
+
|
30
|
+
bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
|
31
|
+
|
32
|
+
bootstrap_options[:name] ||= machine_spec.name
|
33
|
+
|
34
|
+
bootstrap_options
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.get_aws_profile(driver_options, aws_account_id)
|
38
|
+
aws_credentials = get_aws_credentials(driver_options)
|
39
|
+
compute_options = driver_options[:compute_options] || {}
|
40
|
+
|
41
|
+
# Order of operations:
|
42
|
+
# compute_options[:aws_access_key_id] / compute_options[:aws_secret_access_key] / compute_options[:aws_security_token] / compute_options[:region]
|
43
|
+
# compute_options[:aws_profile]
|
44
|
+
# ENV['AWS_ACCESS_KEY_ID'] / ENV['AWS_SECRET_ACCESS_KEY'] / ENV['AWS_SECURITY_TOKEN'] / ENV['AWS_REGION']
|
45
|
+
# ENV['AWS_PROFILE']
|
46
|
+
# ENV['DEFAULT_PROFILE']
|
47
|
+
# 'default'
|
48
|
+
aws_profile = if compute_options[:aws_access_key_id]
|
49
|
+
Chef::Log.debug("Using AWS driver access key options")
|
50
|
+
{
|
51
|
+
:aws_access_key_id => compute_options[:aws_access_key_id],
|
52
|
+
:aws_secret_access_key => compute_options[:aws_secret_access_key],
|
53
|
+
:aws_security_token => compute_options[:aws_session_token],
|
54
|
+
:region => compute_options[:region]
|
55
|
+
}
|
56
|
+
elsif driver_options[:aws_profile]
|
57
|
+
Chef::Log.debug("Using AWS profile #{driver_options[:aws_profile]}")
|
58
|
+
aws_credentials[driver_options[:aws_profile]]
|
59
|
+
elsif ENV['AWS_ACCESS_KEY_ID']
|
60
|
+
Chef::Log.debug("Using AWS environment variable access keys")
|
61
|
+
{
|
62
|
+
:aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
|
63
|
+
:aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'],
|
64
|
+
:aws_security_token => ENV['AWS_SECURITY_TOKEN'],
|
65
|
+
:region => ENV['AWS_REGION']
|
66
|
+
}
|
67
|
+
elsif ENV['AWS_PROFILE']
|
68
|
+
Chef::Log.debug("Using AWS profile #{ENV['AWS_PROFILE']} from AWS_PROFILE environment variable")
|
69
|
+
aws_credentials[ENV['AWS_PROFILE']]
|
70
|
+
else
|
71
|
+
Chef::Log.debug("Using AWS default profile")
|
72
|
+
aws_credentials.default
|
73
|
+
end
|
74
|
+
|
75
|
+
# Merge in account info for profile
|
76
|
+
if aws_profile
|
77
|
+
aws_profile = aws_profile.merge(aws_account_info_for(aws_profile))
|
78
|
+
end
|
79
|
+
|
80
|
+
# If no profile is found (or the profile is not the right account), search
|
81
|
+
# for a profile that matches the given account ID
|
82
|
+
if aws_account_id && (!aws_profile || aws_profile[:aws_account_id] != aws_account_id)
|
83
|
+
aws_profile = find_aws_profile_for_account_id(aws_credentials, aws_account_id)
|
84
|
+
end
|
85
|
+
|
86
|
+
if !aws_profile
|
87
|
+
raise "No AWS profile specified! Are you missing something in the Chef config or ~/.aws/config?"
|
88
|
+
end
|
89
|
+
|
90
|
+
aws_profile.delete_if { |key, value| value.nil? }
|
91
|
+
aws_profile
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.find_aws_profile_for_account_id(aws_credentials, aws_account_id)
|
95
|
+
aws_profile = nil
|
96
|
+
aws_credentials.each do |profile_name, profile|
|
97
|
+
begin
|
98
|
+
aws_account_info = aws_account_info_for(profile)
|
99
|
+
rescue
|
100
|
+
Chef::Log.warn("Could not connect to AWS profile #{aws_credentials[:name]}: #{$!}")
|
101
|
+
Chef::Log.debug($!.backtrace.join("\n"))
|
102
|
+
next
|
103
|
+
end
|
104
|
+
if aws_account_info[:aws_account_id] == aws_account_id
|
105
|
+
aws_profile = profile
|
106
|
+
aws_profile[:name] = profile_name
|
107
|
+
aws_profile = aws_profile.merge(aws_account_info)
|
108
|
+
break
|
109
|
+
end
|
110
|
+
end
|
111
|
+
if aws_profile
|
112
|
+
Chef::Log.info("Discovered AWS profile #{aws_profile[:name]} pointing at account #{aws_account_id}. Using ...")
|
113
|
+
else
|
114
|
+
raise "No AWS profile leads to account ##{aws_account_id}. Do you need to add profiles to ~/.aws/config?"
|
115
|
+
end
|
116
|
+
aws_profile
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.aws_account_info_for(aws_profile)
|
120
|
+
@@aws_account_info ||= {}
|
121
|
+
@@aws_account_info[aws_profile[:aws_access_key_id]] ||= begin
|
122
|
+
options = {
|
123
|
+
:aws_access_key_id => aws_profile[:aws_access_key_id],
|
124
|
+
:aws_secret_access_key => aws_profile[:aws_secret_access_key],
|
125
|
+
:aws_session_token => aws_profile[:aws_security_token]
|
126
|
+
}
|
127
|
+
options.delete_if { |key, value| value.nil? }
|
128
|
+
|
129
|
+
iam = Fog::AWS::IAM.new(options)
|
130
|
+
arn = begin
|
131
|
+
# TODO it would be nice if Fog let you do this normally ...
|
132
|
+
iam.send(:request, {
|
133
|
+
'Action' => 'GetUser',
|
134
|
+
:parser => Fog::Parsers::AWS::IAM::GetUser.new
|
135
|
+
}).body['User']['Arn']
|
136
|
+
rescue Fog::AWS::IAM::Error
|
137
|
+
# TODO Someone tell me there is a better way to find out your current
|
138
|
+
# user ID than this! This is what happens when you use an IAM user
|
139
|
+
# with default privileges.
|
140
|
+
if $!.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
|
141
|
+
arn = $1
|
142
|
+
else
|
143
|
+
raise
|
144
|
+
end
|
145
|
+
end
|
146
|
+
arn_split = arn.split(':', 6)
|
147
|
+
{
|
148
|
+
:aws_account_id => arn_split[4],
|
149
|
+
:aws_username => arn_split[5],
|
150
|
+
:aws_user_arn => arn
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.get_aws_credentials(driver_options)
|
156
|
+
# Grab the list of possible credentials
|
157
|
+
if driver_options[:aws_credentials]
|
158
|
+
aws_credentials = driver_options[:aws_credentials]
|
159
|
+
else
|
160
|
+
aws_credentials = Credentials.new
|
161
|
+
if driver_options[:aws_config_file]
|
162
|
+
aws_credentials.load_ini(driver_options.delete(:aws_config_file))
|
163
|
+
elsif driver_options[:aws_csv_file]
|
164
|
+
aws_credentials.load_csv(driver_options.delete(:aws_csv_file))
|
165
|
+
else
|
166
|
+
aws_credentials.load_default
|
167
|
+
end
|
168
|
+
end
|
169
|
+
aws_credentials
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.compute_options_for(provider, id, config)
|
173
|
+
new_compute_options = {}
|
174
|
+
new_compute_options[:provider] = provider
|
175
|
+
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
176
|
+
new_defaults = {
|
177
|
+
:driver_options => { :compute_options => {} },
|
178
|
+
:machine_options => { :bootstrap_options => {} }
|
179
|
+
}
|
180
|
+
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
181
|
+
|
182
|
+
if id && id != ''
|
183
|
+
# AWS canonical URLs are of the form fog:AWS:
|
184
|
+
if id =~ /^(\d{12})(:(.+))?$/
|
185
|
+
if $2
|
186
|
+
id = $1
|
187
|
+
new_compute_options[:region] = $3
|
188
|
+
else
|
189
|
+
Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-metal (before 0.11-final) found. If you have servers in multiple regions on this account, you may see odd behavior like servers being recreated. To fix, edit any nodes with attribute metal.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
|
190
|
+
end
|
191
|
+
else
|
192
|
+
# Assume it is a profile name, and set that.
|
193
|
+
aws_profile, region = id.split(':', 2)
|
194
|
+
new_config[:driver_options][:aws_profile] = aws_profile
|
195
|
+
new_compute_options[:region] = region
|
196
|
+
id = nil
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
aws_profile = get_aws_profile(result[:driver_options], id)
|
201
|
+
new_compute_options[:aws_access_key_id] = aws_profile[:aws_access_key_id]
|
202
|
+
new_compute_options[:aws_secret_access_key] = aws_profile[:aws_secret_access_key]
|
203
|
+
new_compute_options[:aws_session_token] = aws_profile[:aws_security_token]
|
204
|
+
new_defaults[:driver_options][:compute_options][:region] = aws_profile[:region]
|
205
|
+
|
206
|
+
account_info = aws_account_info_for(result[:driver_options][:compute_options])
|
207
|
+
new_config[:driver_options][:aws_account_info] = account_info
|
208
|
+
id = "#{account_info[:aws_account_id]}:#{result[:driver_options][:compute_options][:region]}"
|
209
|
+
|
210
|
+
[result, id]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ChefMetalFog
|
2
|
+
module Providers
|
3
|
+
class CloudStack < ChefMetalFog::FogDriver
|
4
|
+
|
5
|
+
ChefMetalFog::FogDriver.register_provider_class('CloudStack', ChefMetalFog::Providers::CloudStack)
|
6
|
+
|
7
|
+
def self.compute_options_for(provider, id, config)
|
8
|
+
new_compute_options = {}
|
9
|
+
new_compute_options[:provider] = provider
|
10
|
+
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
11
|
+
new_defaults = {
|
12
|
+
:driver_options => { :compute_options => {} },
|
13
|
+
:machine_options => { :bootstrap_options => {} }
|
14
|
+
}
|
15
|
+
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
16
|
+
|
17
|
+
if id && id != ''
|
18
|
+
cloudstack_uri = URI.parse(id)
|
19
|
+
new_compute_options[:cloudstack_scheme] = cloudstack_uri.scheme
|
20
|
+
new_compute_options[:cloudstack_host] = cloudstack_uri.host
|
21
|
+
new_compute_options[:cloudstack_port] = cloudstack_uri.port
|
22
|
+
new_compute_options[:cloudstack_path] = cloudstack_uri.path
|
23
|
+
end
|
24
|
+
|
25
|
+
host = result[:driver_options][:compute_options][:cloudstack_host]
|
26
|
+
path = result[:driver_options][:compute_options][:cloudstack_path] || '/client/api'
|
27
|
+
port = result[:driver_options][:compute_options][:cloudstack_port] || 443
|
28
|
+
scheme = result[:driver_options][:compute_options][:cloudstack_scheme] || 'https'
|
29
|
+
id = URI.scheme_list[scheme.upcase].build(:host => host, :port => port, :path => path).to_s
|
30
|
+
|
31
|
+
[result, id]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# fog:DigitalOcean:<client id>
|
2
|
+
module ChefMetalFog
|
3
|
+
module Providers
|
4
|
+
class DigitalOcean < ChefMetalFog::FogDriver
|
5
|
+
ChefMetalFog::FogDriver.register_provider_class('DigitalOcean', ChefMetalFog::Providers::DigitalOcean)
|
6
|
+
|
7
|
+
def creator
|
8
|
+
''
|
9
|
+
end
|
10
|
+
|
11
|
+
def bootstrap_options_for(action_handler, machine_spec, machine_options)
|
12
|
+
bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
|
13
|
+
if bootstrap_options[:key_path]
|
14
|
+
bootstrap_options[:key_name] ||= File.basename(bootstrap_options[:key_path])
|
15
|
+
# Verify that the provided key name and path are in line (or create the key pair if not!)
|
16
|
+
driver = self
|
17
|
+
ChefMetal.inline_resource(action_handler) do
|
18
|
+
fog_key_pair bootstrap_options[:key_name] do
|
19
|
+
private_key_path bootstrap_options[:key_path]
|
20
|
+
driver driver
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler)
|
25
|
+
end
|
26
|
+
|
27
|
+
bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
|
28
|
+
|
29
|
+
if !bootstrap_options[:image_id]
|
30
|
+
bootstrap_options[:image_name] ||= 'CentOS 6.4 x32'
|
31
|
+
bootstrap_options[:image_id] = compute.images.select { |image| image.name == bootstrap_options[:image_name] }.first.id
|
32
|
+
end
|
33
|
+
if !bootstrap_options[:flavor_id]
|
34
|
+
bootstrap_options[:flavor_name] ||= '512MB'
|
35
|
+
bootstrap_options[:flavor_id] = compute.flavors.select { |flavor| flavor.name == bootstrap_options[:flavor_name] }.first.id
|
36
|
+
end
|
37
|
+
if !bootstrap_options[:region_id]
|
38
|
+
bootstrap_options[:region_name] ||= 'San Francisco 1'
|
39
|
+
bootstrap_options[:region_id] = compute.regions.select { |region| region.name == bootstrap_options[:region_name] }.first.id
|
40
|
+
end
|
41
|
+
found_key = compute.ssh_keys.select { |k| k.name == bootstrap_options[:key_name] }.first
|
42
|
+
if !found_key
|
43
|
+
raise "Could not find key named '#{bootstrap_options[:key_name]}' on #{driver_url}"
|
44
|
+
end
|
45
|
+
bootstrap_options[:ssh_key_ids] ||= [ found_key.id ]
|
46
|
+
|
47
|
+
# You don't get to specify name yourself
|
48
|
+
bootstrap_options[:name] = machine_spec.name
|
49
|
+
|
50
|
+
bootstrap_options
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.compute_options_for(provider, id, config)
|
54
|
+
new_compute_options = {}
|
55
|
+
new_compute_options[:provider] = provider
|
56
|
+
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
57
|
+
new_defaults = {
|
58
|
+
:driver_options => { :compute_options => {} },
|
59
|
+
:machine_options => { :bootstrap_options => {} }
|
60
|
+
}
|
61
|
+
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
62
|
+
|
63
|
+
new_compute_options[:digitalocean_client_id] = id if (id && id != '')
|
64
|
+
|
65
|
+
# This uses ~/.tugboat, generated by "tugboat authorize" - see https://github.com/pearkes/tugboat
|
66
|
+
tugboat_file = File.expand_path('~/.tugboat')
|
67
|
+
if File.exist?(tugboat_file)
|
68
|
+
tugboat_data = YAML.load(IO.read(tugboat_file))
|
69
|
+
new_compute_options.merge!(
|
70
|
+
:digitalocean_client_id => tugboat_data['authentication']['client_key'],
|
71
|
+
:digitalocean_api_key => tugboat_data['authentication']['api_key']
|
72
|
+
)
|
73
|
+
new_defaults[:machine_options].merge!(
|
74
|
+
#:ssh_username => tugboat_data['ssh']['ssh_user'],
|
75
|
+
:ssh_options => {
|
76
|
+
:port => tugboat_data['ssh']['ssh_port'],
|
77
|
+
# TODO we ignore ssh_key_path in favor of ssh_key / key_name stuff
|
78
|
+
#:key_data => [ IO.read(tugboat_data['ssh']['ssh_key_path']) ] # TODO use paths, not data?
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
# TODO verify that the key_name exists and matches the ssh key path
|
83
|
+
|
84
|
+
new_defaults[:machine_options][:bootstrap_options].merge!(
|
85
|
+
:region_id => tugboat_data['defaults']['region'].to_i,
|
86
|
+
:image_id => tugboat_data['defaults']['image'].to_i,
|
87
|
+
:size_id => tugboat_data['defaults']['region'].to_i,
|
88
|
+
:private_networking => tugboat_data['defaults']['private_networking'] == 'true',
|
89
|
+
:backups_enabled => tugboat_data['defaults']['backups_enabled'] == 'true',
|
90
|
+
)
|
91
|
+
if tugboat_data['ssh']['ssh_key_path']
|
92
|
+
new_defaults[:machine_options][:bootstrap_options][:key_path] = tugboat_data['ssh']['ssh_key_path']
|
93
|
+
end
|
94
|
+
ssh_key = tugboat_data['defaults']['ssh_key']
|
95
|
+
if ssh_key && ssh_key.size > 0
|
96
|
+
new_defaults[:machine_options][:bootstrap_options][:key_name] = ssh_key
|
97
|
+
end
|
98
|
+
end
|
99
|
+
id = result[:driver_options][:compute_options][:digitalocean_client_id]
|
100
|
+
|
101
|
+
[result, id]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# fog:OpenStack:https://identifyhost:portNumber/v2.0
|
2
|
+
module ChefMetalFog
|
3
|
+
module Providers
|
4
|
+
class OpenStack < ChefMetalFog::FogDriver
|
5
|
+
|
6
|
+
ChefMetalFog::FogDriver.register_provider_class('OpenStack', ChefMetalFog::Providers::OpenStack)
|
7
|
+
|
8
|
+
def creator
|
9
|
+
compute_options[:openstack_username]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.compute_options_for(provider, id, config)
|
13
|
+
new_compute_options = {}
|
14
|
+
new_compute_options[:provider] = provider
|
15
|
+
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
16
|
+
new_defaults = {
|
17
|
+
:driver_options => { :compute_options => {} },
|
18
|
+
:machine_options => { :bootstrap_options => {} }
|
19
|
+
}
|
20
|
+
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
21
|
+
|
22
|
+
new_compute_options[:openstack_auth_url] = id if (id && id != '')
|
23
|
+
credential = Fog.credentials
|
24
|
+
|
25
|
+
new_compute_options[:openstack_username] ||= credential[:openstack_username]
|
26
|
+
new_compute_options[:openstack_api_key] ||= credential[:openstack_api_key]
|
27
|
+
new_compute_options[:openstack_auth_url] ||= credential[:openstack_auth_url]
|
28
|
+
new_compute_options[:openstack_tenant] ||= credential[:openstack_tenant]
|
29
|
+
|
30
|
+
id = result[:driver_options][:compute_options][:openstack_auth_url]
|
31
|
+
|
32
|
+
[result, id]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
|
2
|
+
module ChefMetalFog
|
3
|
+
module Providers
|
4
|
+
class Rackspace < ChefMetalFog::FogDriver
|
5
|
+
|
6
|
+
ChefMetalFog::FogDriver.register_provider_class('Rackspace', ChefMetalFog::Providers::Rackspace)
|
7
|
+
|
8
|
+
def creator
|
9
|
+
compute_options[:rackspace_username]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.compute_options_for(provider, id, config)
|
13
|
+
new_compute_options = {}
|
14
|
+
new_compute_options[:provider] = provider
|
15
|
+
new_config = { :driver_options => { :compute_options => new_compute_options }}
|
16
|
+
new_defaults = {
|
17
|
+
:driver_options => { :compute_options => {} },
|
18
|
+
:machine_options => { :bootstrap_options => {} }
|
19
|
+
}
|
20
|
+
result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
|
21
|
+
|
22
|
+
new_compute_options[:rackspace_auth_url] = id if (id && id != '')
|
23
|
+
credential = Fog.credentials
|
24
|
+
|
25
|
+
new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
|
26
|
+
new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
|
27
|
+
new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
|
28
|
+
new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
|
29
|
+
new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
|
30
|
+
|
31
|
+
id = result[:driver_options][:compute_options][:rackspace_auth_url]
|
32
|
+
|
33
|
+
[result, id]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
$:.unshift File.expand_path('../support', __FILE__)
|
3
|
+
require 'fog'
|
4
|
+
require 'chef_metal'
|
5
|
+
require 'chef_metal_fog'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.filter_run :focus
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
17
|
+
|
18
|
+
Fog.mock!
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ChefMetalFog::Providers
|
2
|
+
class TestDriver < ChefMetalFog::FogDriver
|
3
|
+
ChefMetalFog::FogDriver.register_provider_class('TestDriver', ChefMetalFog::Providers::TestDriver)
|
4
|
+
|
5
|
+
attr_reader :config
|
6
|
+
def initialize(driver_url, config)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.compute_options_for(provider, id, config)
|
11
|
+
[config, 'test']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'chef_metal_fog/fog_driver'
|
3
|
+
|
4
|
+
describe ChefMetalFog::FogDriver do
|
5
|
+
|
6
|
+
describe ".from_url" do
|
7
|
+
subject { ChefMetalFog::FogDriver.from_provider('TestDriver', {}) }
|
8
|
+
|
9
|
+
it "should return the correct class" do
|
10
|
+
expect(subject).to be_an_instance_of ChefMetalFog::Providers::TestDriver
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should call the target compute_options_for" do
|
14
|
+
expect(ChefMetalFog::Providers::TestDriver).to receive(:compute_options_for)
|
15
|
+
.with('TestDriver', anything, {}).and_return([{}, 'test']).twice
|
16
|
+
subject
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "when creating a new class" do
|
22
|
+
it "should return the correct class" do
|
23
|
+
test = ChefMetalFog::FogDriver.new('fog:TestDriver:foo', {})
|
24
|
+
expect(test).to be_an_instance_of ChefMetalFog::Providers::TestDriver
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should populate config" do
|
28
|
+
test = ChefMetalFog::FogDriver.new('fog:TestDriver:foo', {test: "metal"})
|
29
|
+
expect(test.config[:test]).to eq "metal"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'chef_metal_fog/providers/aws/credentials'
|
2
|
+
|
3
|
+
describe ChefMetalFog::Providers::AWS::Credentials do
|
4
|
+
describe "#load_ini" do
|
5
|
+
let(:aws_credentials_ini_file) { File.join(File.expand_path('../../../../support', __FILE__), 'aws/ini-file.ini') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
described_class.load_ini(aws_credentials_ini_file)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should load a default profile" do
|
12
|
+
expect(described_class['default']).to include(:aws_access_key_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should load the correct values" do
|
16
|
+
expect(described_class['default'][:aws_access_key_id]).to eq "12345"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should load several profiles" do
|
20
|
+
expect(described_class.keys.length).to eq 2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#load_csv" do
|
25
|
+
let(:aws_credentials_csv_file) { File.join(File.expand_path('../../../../support', __FILE__), 'aws/config-file.csv') }
|
26
|
+
before do
|
27
|
+
described_class.load_csv(aws_credentials_csv_file)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should load a single profile" do
|
31
|
+
expect(described_class['default']).to include(:aws_access_key_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should load the correct values" do
|
35
|
+
expect(described_class['default'][:aws_access_key_id]).to eq "12345"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should load several profiles" do
|
39
|
+
expect(described_class.keys.length).to eq 2
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chef_metal_fog/fog_driver'
|
2
|
+
require 'chef_metal_fog/providers/rackspace'
|
3
|
+
|
4
|
+
describe ChefMetalFog::Providers::Rackspace do
|
5
|
+
subject { ChefMetalFog::FogDriver.from_provider('Rackspace',{}) }
|
6
|
+
|
7
|
+
it "returns the correct driver" do
|
8
|
+
expect(subject).to be_an_instance_of ChefMetalFog::Providers::Rackspace
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has a fog backend" do
|
12
|
+
pending unless Fog.mock?
|
13
|
+
expect(subject.compute).to be_an_instance_of Fog::Compute::RackspaceV2::Mock
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-metal-fog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Keiser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: chef-metal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.12'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.12'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: fog
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,11 +109,22 @@ files:
|
|
95
109
|
- lib/chef/resource/fog_key_pair.rb
|
96
110
|
- lib/chef_metal/driver_init/fog.rb
|
97
111
|
- lib/chef_metal_fog.rb
|
98
|
-
- lib/chef_metal_fog/aws_credentials.rb
|
99
112
|
- lib/chef_metal_fog/fog_driver.rb
|
100
|
-
- lib/chef_metal_fog/
|
113
|
+
- lib/chef_metal_fog/providers/aws.rb
|
114
|
+
- lib/chef_metal_fog/providers/aws/credentials.rb
|
115
|
+
- lib/chef_metal_fog/providers/cloudstack.rb
|
116
|
+
- lib/chef_metal_fog/providers/digitalocean.rb
|
117
|
+
- lib/chef_metal_fog/providers/openstack.rb
|
118
|
+
- lib/chef_metal_fog/providers/rackspace.rb
|
101
119
|
- lib/chef_metal_fog/recipe_dsl.rb
|
102
120
|
- lib/chef_metal_fog/version.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/support/aws/config-file.csv
|
123
|
+
- spec/support/aws/ini-file.ini
|
124
|
+
- spec/support/chef_metal_fog/providers/testdriver.rb
|
125
|
+
- spec/unit/fog_driver_spec.rb
|
126
|
+
- spec/unit/providers/aws/credentials_spec.rb
|
127
|
+
- spec/unit/providers/rackspace_spec.rb
|
103
128
|
homepage: https://github.com/opscode/chef-metal-fog
|
104
129
|
licenses: []
|
105
130
|
metadata: {}
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'inifile'
|
2
|
-
require 'csv'
|
3
|
-
|
4
|
-
module ChefMetalFog
|
5
|
-
# Reads in a credentials file in Amazon's download format and presents the credentials to you
|
6
|
-
class AWSCredentials
|
7
|
-
def initialize
|
8
|
-
@credentials = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
include Enumerable
|
12
|
-
|
13
|
-
def default
|
14
|
-
@credentials[ENV['AWS_DEFAULT_PROFILE'] || 'default'] || @credentials.first[1]
|
15
|
-
end
|
16
|
-
|
17
|
-
def keys
|
18
|
-
@credentials.keys
|
19
|
-
end
|
20
|
-
|
21
|
-
def [](name)
|
22
|
-
@credentials[name]
|
23
|
-
end
|
24
|
-
|
25
|
-
def each(&block)
|
26
|
-
@credentials.each(&block)
|
27
|
-
end
|
28
|
-
|
29
|
-
def load_ini(credentials_ini_file)
|
30
|
-
inifile = IniFile.load(File.expand_path(credentials_ini_file))
|
31
|
-
inifile.each_section do |section|
|
32
|
-
if section =~ /^\s*profile\s+(.+)$/ || section =~ /^\s*(default)\s*/
|
33
|
-
profile_name = $1.strip
|
34
|
-
profile = inifile[section].inject({}) do |result, pair|
|
35
|
-
result[pair[0].to_sym] = pair[1]
|
36
|
-
result
|
37
|
-
end
|
38
|
-
profile[:name] = profile_name
|
39
|
-
@credentials[profile_name] = profile
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def load_csv(credentials_csv_file)
|
45
|
-
CSV.new(File.open(credentials_csv_file), :headers => :first_row).each do |row|
|
46
|
-
@credentials[row['User Name']] = {
|
47
|
-
:name => row['User Name'],
|
48
|
-
:user_name => row['User Name'],
|
49
|
-
:aws_access_key_id => row['Access Key Id'],
|
50
|
-
:aws_secret_access_key => row['Secret Access Key']
|
51
|
-
}
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def load_default
|
56
|
-
load_ini(ENV['AWS_CONFIG_FILE'] || File.expand_path('~/.aws/config'))
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.method_missing(name, *args, &block)
|
60
|
-
singleton.send(name, *args, &block)
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.singleton
|
64
|
-
@aws_credentials ||= AWSCredentials.new
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,142 +0,0 @@
|
|
1
|
-
require 'chef_metal_fog/aws_credentials'
|
2
|
-
require 'chef/log'
|
3
|
-
require 'fog/aws'
|
4
|
-
|
5
|
-
module ChefMetalFog
|
6
|
-
module FogDriverAWS
|
7
|
-
def self.get_aws_profile(driver_options, aws_account_id)
|
8
|
-
aws_credentials = get_aws_credentials(driver_options)
|
9
|
-
compute_options = driver_options[:compute_options] || {}
|
10
|
-
|
11
|
-
# Order of operations:
|
12
|
-
# compute_options[:aws_access_key_id] / compute_options[:aws_secret_access_key] / compute_options[:aws_security_token] / compute_options[:region]
|
13
|
-
# compute_options[:aws_profile]
|
14
|
-
# ENV['AWS_ACCESS_KEY_ID'] / ENV['AWS_SECRET_ACCESS_KEY'] / ENV['AWS_SECURITY_TOKEN'] / ENV['AWS_REGION']
|
15
|
-
# ENV['AWS_PROFILE']
|
16
|
-
# ENV['DEFAULT_PROFILE']
|
17
|
-
# 'default'
|
18
|
-
aws_profile = if compute_options[:aws_access_key_id]
|
19
|
-
Chef::Log.debug("Using AWS driver access key options")
|
20
|
-
{
|
21
|
-
:aws_access_key_id => compute_options[:aws_access_key_id],
|
22
|
-
:aws_secret_access_key => compute_options[:aws_secret_access_key],
|
23
|
-
:aws_security_token => compute_options[:aws_session_token],
|
24
|
-
:region => compute_options[:region]
|
25
|
-
}
|
26
|
-
elsif driver_options[:aws_profile]
|
27
|
-
Chef::Log.debug("Using AWS profile #{driver_options[:aws_profile]}")
|
28
|
-
aws_credentials[driver_options[:aws_profile]]
|
29
|
-
elsif ENV['AWS_ACCESS_KEY_ID']
|
30
|
-
Chef::Log.debug("Using AWS environment variable access keys")
|
31
|
-
{
|
32
|
-
:aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
|
33
|
-
:aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'],
|
34
|
-
:aws_security_token => ENV['AWS_SECURITY_TOKEN'],
|
35
|
-
:region => ENV['AWS_REGION']
|
36
|
-
}
|
37
|
-
elsif ENV['AWS_PROFILE']
|
38
|
-
Chef::Log.debug("Using AWS profile #{ENV['AWS_PROFILE']} from AWS_PROFILE environment variable")
|
39
|
-
aws_credentials[ENV['AWS_PROFILE']]
|
40
|
-
else
|
41
|
-
Chef::Log.debug("Using AWS default profile")
|
42
|
-
aws_credentials.default
|
43
|
-
end
|
44
|
-
|
45
|
-
# Merge in account info for profile
|
46
|
-
if aws_profile
|
47
|
-
aws_profile = aws_profile.merge(aws_account_info_for(aws_profile))
|
48
|
-
end
|
49
|
-
|
50
|
-
# If no profile is found (or the profile is not the right account), search
|
51
|
-
# for a profile that matches the given account ID
|
52
|
-
if aws_account_id && (!aws_profile || aws_profile[:aws_account_id] != aws_account_id)
|
53
|
-
aws_profile = find_aws_profile_for_account_id(aws_credentials, aws_account_id)
|
54
|
-
end
|
55
|
-
|
56
|
-
if !aws_profile
|
57
|
-
raise "No AWS profile specified! Are you missing something in the Chef config or ~/.aws/config?"
|
58
|
-
end
|
59
|
-
|
60
|
-
aws_profile.delete_if { |key, value| value.nil? }
|
61
|
-
aws_profile
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.find_aws_profile_for_account_id(aws_credentials, aws_account_id)
|
65
|
-
aws_profile = nil
|
66
|
-
aws_credentials.each do |profile_name, profile|
|
67
|
-
begin
|
68
|
-
aws_account_info = aws_account_info_for(profile)
|
69
|
-
rescue
|
70
|
-
Chef::Log.warn("Could not connect to AWS profile #{aws_credentials[:name]}: #{$!}")
|
71
|
-
Chef::Log.debug($!.backtrace.join("\n"))
|
72
|
-
next
|
73
|
-
end
|
74
|
-
if aws_account_info[:aws_account_id] == aws_account_id
|
75
|
-
aws_profile = profile
|
76
|
-
aws_profile[:name] = profile_name
|
77
|
-
aws_profile = aws_profile.merge(aws_account_info)
|
78
|
-
break
|
79
|
-
end
|
80
|
-
end
|
81
|
-
if aws_profile
|
82
|
-
Chef::Log.info("Discovered AWS profile #{aws_profile[:name]} pointing at account #{aws_account_id}. Using ...")
|
83
|
-
else
|
84
|
-
raise "No AWS profile leads to account ##{aws_account_id}. Do you need to add profiles to ~/.aws/config?"
|
85
|
-
end
|
86
|
-
aws_profile
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.aws_account_info_for(aws_profile)
|
90
|
-
@@aws_account_info ||= {}
|
91
|
-
@@aws_account_info[aws_profile[:aws_access_key_id]] ||= begin
|
92
|
-
options = {
|
93
|
-
:aws_access_key_id => aws_profile[:aws_access_key_id],
|
94
|
-
:aws_secret_access_key => aws_profile[:aws_secret_access_key],
|
95
|
-
:aws_session_token => aws_profile[:aws_security_token]
|
96
|
-
}
|
97
|
-
options.delete_if { |key, value| value.nil? }
|
98
|
-
|
99
|
-
iam = Fog::AWS::IAM.new(options)
|
100
|
-
arn = begin
|
101
|
-
# TODO it would be nice if Fog let you do this normally ...
|
102
|
-
iam.send(:request, {
|
103
|
-
'Action' => 'GetUser',
|
104
|
-
:parser => Fog::Parsers::AWS::IAM::GetUser.new
|
105
|
-
}).body['User']['Arn']
|
106
|
-
rescue Fog::AWS::IAM::Error
|
107
|
-
# TODO Someone tell me there is a better way to find out your current
|
108
|
-
# user ID than this! This is what happens when you use an IAM user
|
109
|
-
# with default privileges.
|
110
|
-
if $!.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
|
111
|
-
arn = $1
|
112
|
-
else
|
113
|
-
raise
|
114
|
-
end
|
115
|
-
end
|
116
|
-
arn_split = arn.split(':', 6)
|
117
|
-
{
|
118
|
-
:aws_account_id => arn_split[4],
|
119
|
-
:aws_username => arn_split[5],
|
120
|
-
:aws_user_arn => arn
|
121
|
-
}
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def self.get_aws_credentials(driver_options)
|
126
|
-
# Grab the list of possible credentials
|
127
|
-
if driver_options[:aws_credentials]
|
128
|
-
aws_credentials = driver_options[:aws_credentials]
|
129
|
-
else
|
130
|
-
aws_credentials = AWSCredentials.new
|
131
|
-
if driver_options[:aws_config_file]
|
132
|
-
aws_credentials.load_ini(driver_options.delete(:aws_config_file))
|
133
|
-
elsif driver_options[:aws_csv_file]
|
134
|
-
aws_credentials.load_csv(driver_options.delete(:aws_csv_file))
|
135
|
-
else
|
136
|
-
aws_credentials.load_default
|
137
|
-
end
|
138
|
-
end
|
139
|
-
aws_credentials
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|