chef-metal-fog 0.5.4 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
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