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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d352005e584088b29def2a19cb7bb2ae8e582b52
4
- data.tar.gz: 82a39220c895a4a62a92664e6377615090465f45
3
+ metadata.gz: b1fdb71aedeacd1389f9b204d2518a566cd129f8
4
+ data.tar.gz: 06a458753dad712619205ad5cccee51c8cbd00c3
5
5
  SHA512:
6
- metadata.gz: eef339d4b7ee99b1538cfb0788448ea0dd514d06a8db6cb8b10cccd738fee1dbf2ff9251eb976ee448dfc282ef137243c46d8840bc4b32ffd96555ac86b7e884
7
- data.tar.gz: 6b8019284fbb58cdd1d5dffda056d23a67ff8a34001d871b2a28f1bd5198e77faba71abfff8102c5be4eaf74adcbf69cb535d837eba815878206cd9e73be2848
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
- new_fingerprints << Cheffish::KeyFormatter.encode(desired_private_key,
74
- :format => :pkcs8sha1fingerprint)
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.utc.to_s
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.utc.to_s
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.utc.to_s
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 - Time.parse(machine_spec.location['started_at']))
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 - Time.parse(machine_spec.location['allocated_at']))
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
- if (provider == 'DigitalOcean' || provider == 'AWS') && !bootstrap_options[:key_name]
422
- bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler)
423
- end
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!(bootstrap_options[:tags]) if bootstrap_options[:tags]
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
- # If we're on AWS, the default is to use ubuntu, not root
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
- driver_options = config[:driver_options] || {}
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
@@ -1,3 +1,3 @@
1
1
  module ChefMetalFog
2
- VERSION = '0.5.4'
2
+ VERSION = '0.6'
3
3
  end
@@ -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,2 @@
1
+ User Name,Access Key Id,Secret Access Key
2
+ test,12345,abcde
@@ -0,0 +1,7 @@
1
+ [default]
2
+ aws_access_key_id = 12345
3
+ aws_secret_access_key = abcde
4
+
5
+ [profile test]
6
+ aws_access_key_id = foobar
7
+ aws_secret_access_key = canteloupe
@@ -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.5.4
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-10 00:00:00.000000000 Z
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/fog_driver_aws.rb
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