chef-provisioning-fog 0.26.1 → 0.26.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +29 -8
  3. data/Rakefile +23 -12
  4. data/chef-provisioning-fog.gemspec +24 -27
  5. data/lib/chef/provider/fog_key_pair.rb +49 -53
  6. data/lib/chef/provider/scaleway_volume.rb +46 -48
  7. data/lib/chef/provisioning/driver_init/fog.rb +1 -1
  8. data/lib/chef/provisioning/fog_driver/driver.rb +646 -653
  9. data/lib/chef/provisioning/fog_driver/providers/aws.rb +411 -422
  10. data/lib/chef/provisioning/fog_driver/providers/aws/credentials.rb +88 -90
  11. data/lib/chef/provisioning/fog_driver/providers/cloudstack.rb +32 -34
  12. data/lib/chef/provisioning/fog_driver/providers/digitalocean.rb +98 -100
  13. data/lib/chef/provisioning/fog_driver/providers/google.rb +27 -34
  14. data/lib/chef/provisioning/fog_driver/providers/joyent.rb +53 -55
  15. data/lib/chef/provisioning/fog_driver/providers/openstack.rb +139 -146
  16. data/lib/chef/provisioning/fog_driver/providers/rackspace.rb +40 -44
  17. data/lib/chef/provisioning/fog_driver/providers/scaleway.rb +183 -189
  18. data/lib/chef/provisioning/fog_driver/providers/softlayer.rb +61 -64
  19. data/lib/chef/provisioning/fog_driver/providers/vcair.rb +72 -78
  20. data/lib/chef/provisioning/fog_driver/providers/xenserver.rb +56 -69
  21. data/lib/chef/provisioning/fog_driver/recipe_dsl.rb +11 -12
  22. data/lib/chef/provisioning/fog_driver/version.rb +1 -1
  23. data/lib/chef/resource/fog_key_pair.rb +8 -8
  24. data/lib/chef/resource/scaleway_volume.rb +8 -8
  25. data/spec/spec_helper.rb +7 -7
  26. data/spec/support/chef/provisioning/fog_driver/providers/testdriver.rb +3 -3
  27. data/spec/unit/chef/provisioning/fog_driver/driver_spec.rb +39 -38
  28. data/spec/unit/fog_driver_spec.rb +6 -8
  29. data/spec/unit/providers/aws/credentials_spec.rb +10 -10
  30. data/spec/unit/providers/rackspace_spec.rb +5 -6
  31. data/spec/unit/providers/scaleway_spec.rb +9 -9
  32. data/spec/unit/providers/softlayer.rb +7 -7
  33. metadata +6 -36
  34. data/README.md +0 -357
@@ -1,422 +1,414 @@
1
- require 'chef/log'
2
- require 'fog/aws'
3
- require 'uri'
4
- require 'base64'
5
- require 'openssl'
6
- require 'pathname'
7
- require 'chef/provisioning/transport/winrm'
1
+ require "chef/log"
2
+ require "fog/aws"
3
+ require "uri"
4
+ require "base64"
5
+ require "openssl"
6
+ require "pathname"
7
+ require "chef/provisioning/transport/winrm"
8
8
 
9
9
  # fog:AWS:<account_id>:<region>
10
10
  # fog:AWS:<profile_name>
11
11
  # fog:AWS:<profile_name>:<region>
12
12
  class Chef
13
- module Provisioning
14
- module FogDriver
15
- module Providers
16
- class AWS < FogDriver::Driver
13
+ module Provisioning
14
+ module FogDriver
15
+ module Providers
16
+ class AWS < FogDriver::Driver
17
+ require_relative "aws/credentials"
17
18
 
18
- require_relative 'aws/credentials'
19
+ Driver.register_provider_class("AWS", FogDriver::Providers::AWS)
19
20
 
20
- Driver.register_provider_class('AWS', FogDriver::Providers::AWS)
21
-
22
- def creator
23
- driver_options[:aws_account_info][:aws_username]
24
- end
25
-
26
- def default_ssh_username
27
- 'ubuntu'
28
- end
29
-
30
- # Create a WinRM transport for an AWS instance
31
- # @param [Hash] machine_spec Machine-spec hash
32
- # @param [Hash] machine_options Machine options (from the recipe)
33
- # @param [Fog::Compute::Server] server A Fog mapping to the AWS instance
34
- # @return [ChefMetal::Transport::WinRM] A WinRM Transport object to talk to the server
35
- def create_winrm_transport(machine_spec, machine_options, server)
36
- remote_host = if machine_spec.location['use_private_ip_for_ssh']
37
- server.private_ip_address
38
- elsif !server.public_ip_address
39
- Chef::Log.warn("Server #{machine_spec.name} has no public IP address. Using private IP '#{server.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
40
- server.private_ip_address
41
- elsif server.public_ip_address
42
- server.public_ip_address
43
- else
44
- fail "Server #{server.id} has no private or public IP address!"
45
- end
46
-
47
- port = machine_spec.location['winrm_port'] || 5985
48
- endpoint = "http://#{remote_host}:#{port}/wsman"
49
- type = :plaintext
50
- pem_bytes = private_key_for(machine_spec, machine_options, server)
51
- encrypted_admin_password = wait_for_admin_password(machine_spec)
52
- decoded = Base64.decode64(encrypted_admin_password)
53
- private_key = OpenSSL::PKey::RSA.new(pem_bytes)
54
- decrypted_password = private_key.private_decrypt decoded
55
-
56
- # Use basic HTTP auth - this is required for the WinRM setup we
57
- # are using
58
- # TODO: Improve that.
59
- options = {
60
- :user => machine_spec.location['winrm.username'] || 'Administrator',
61
- :pass => decrypted_password,
62
- :disable_sspi => true,
63
- :basic_auth_only => true
64
- }
65
-
66
- Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
67
- end
21
+ def creator
22
+ driver_options[:aws_account_info][:aws_username]
23
+ end
68
24
 
69
- def allocate_image(action_handler, image_spec, image_options, machine_spec)
70
- if image_spec.location
71
- image = compute.images.get(image_spec.location['image_id'])
72
- if image
73
- raise "The image already exists, why are you asking me to create it? I can't do that, Dave."
25
+ def default_ssh_username
26
+ "ubuntu"
74
27
  end
75
- end
76
- action_handler.perform_action "Create image #{image_spec.name} from machine #{machine_spec.name} with options #{image_options.inspect}" do
77
- opt = image_options.dup
78
- response = compute.create_image(machine_spec.location['server_id'],
79
- image_spec.name,
80
- opt.delete(:description) || "The image formerly and currently named '#{image_spec.name}'",
81
- opt.delete(:no_reboot) || false,
82
- opt)
83
- image_spec.location = {
84
- 'driver_url' => driver_url,
85
- 'driver_version' => FogDriver::VERSION,
86
- 'image_id' => response.body['imageId'],
87
- 'creator' => creator,
88
- 'allocated_at' => Time.now.to_i
89
- }
90
-
91
- image_spec.machine_options ||= {}
92
- image_spec.machine_options.merge!({
93
- :bootstrap_options => {
94
- :image_id => image_spec.location['image_id']
28
+
29
+ # Create a WinRM transport for an AWS instance
30
+ # @param [Hash] machine_spec Machine-spec hash
31
+ # @param [Hash] machine_options Machine options (from the recipe)
32
+ # @param [Fog::Compute::Server] server A Fog mapping to the AWS instance
33
+ # @return [ChefMetal::Transport::WinRM] A WinRM Transport object to talk to the server
34
+ def create_winrm_transport(machine_spec, machine_options, server)
35
+ remote_host = if machine_spec.location["use_private_ip_for_ssh"]
36
+ server.private_ip_address
37
+ elsif !server.public_ip_address
38
+ Chef::Log.warn("Server #{machine_spec.name} has no public IP address. Using private IP '#{server.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
39
+ server.private_ip_address
40
+ elsif server.public_ip_address
41
+ server.public_ip_address
42
+ else
43
+ raise "Server #{server.id} has no private or public IP address!"
44
+ end
45
+
46
+ port = machine_spec.location["winrm_port"] || 5985
47
+ endpoint = "http://#{remote_host}:#{port}/wsman"
48
+ type = :plaintext
49
+ pem_bytes = private_key_for(machine_spec, machine_options, server)
50
+ encrypted_admin_password = wait_for_admin_password(machine_spec)
51
+ decoded = Base64.decode64(encrypted_admin_password)
52
+ private_key = OpenSSL::PKey::RSA.new(pem_bytes)
53
+ decrypted_password = private_key.private_decrypt decoded
54
+
55
+ # Use basic HTTP auth - this is required for the WinRM setup we
56
+ # are using
57
+ # TODO: Improve that.
58
+ options = {
59
+ user: machine_spec.location["winrm.username"] || "Administrator",
60
+ pass: decrypted_password,
61
+ disable_sspi: true,
62
+ basic_auth_only: true
95
63
  }
96
- })
97
64
 
98
- end
99
- end
65
+ Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
66
+ end
100
67
 
101
- def ready_image(action_handler, image_spec, image_options)
102
- if !image_spec.location
103
- raise "Cannot ready an image that does not exist"
104
- end
105
- image = compute.images.get(image_spec.location['image_id'])
106
- if !image.ready?
107
- action_handler.report_progress "Waiting for image to be ready ..."
108
- # TODO timeout
109
- image.wait_for { ready? }
110
- action_handler.report_progress "Image is ready!"
111
- end
112
- end
68
+ def allocate_image(action_handler, image_spec, image_options, machine_spec)
69
+ if image_spec.location
70
+ image = compute.images.get(image_spec.location["image_id"])
71
+ if image
72
+ raise "The image already exists, why are you asking me to create it? I can't do that, Dave."
73
+ end
74
+ end
75
+ action_handler.perform_action "Create image #{image_spec.name} from machine #{machine_spec.name} with options #{image_options.inspect}" do
76
+ opt = image_options.dup
77
+ response = compute.create_image(machine_spec.location["server_id"],
78
+ image_spec.name,
79
+ opt.delete(:description) || "The image formerly and currently named '#{image_spec.name}'",
80
+ opt.delete(:no_reboot) || false,
81
+ opt)
82
+ image_spec.location = {
83
+ "driver_url" => driver_url,
84
+ "driver_version" => FogDriver::VERSION,
85
+ "image_id" => response.body["imageId"],
86
+ "creator" => creator,
87
+ "allocated_at" => Time.now.to_i
88
+ }
89
+
90
+ image_spec.machine_options ||= {}
91
+ image_spec.machine_options.merge!(
92
+ bootstrap_options: {
93
+ image_id: image_spec.location["image_id"]
94
+ }
95
+ )
96
+ end
97
+ end
113
98
 
114
- def destroy_image(action_handler, image_spec, image_options)
115
- if !image_spec.location
116
- return
117
- end
118
- image = compute.images.get(image_spec.location['image_id'])
119
- if !image
120
- return
121
- end
122
- delete_snapshots = image_options[:delete_snapshots]
123
- delete_snapshots = true if delete_snapshots.nil?
124
- image.deregister(delete_snapshots)
125
- end
99
+ def ready_image(action_handler, image_spec, _image_options)
100
+ unless image_spec.location
101
+ raise "Cannot ready an image that does not exist"
102
+ end
103
+ image = compute.images.get(image_spec.location["image_id"])
104
+ unless image.ready?
105
+ action_handler.report_progress "Waiting for image to be ready ..."
106
+ # TODO: timeout
107
+ image.wait_for { ready? }
108
+ action_handler.report_progress "Image is ready!"
109
+ end
110
+ end
126
111
 
127
- def bootstrap_options_for(action_handler, machine_spec, machine_options)
128
- bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
112
+ def destroy_image(_action_handler, image_spec, image_options)
113
+ return unless image_spec.location
114
+ image = compute.images.get(image_spec.location["image_id"])
115
+ return unless image
116
+ delete_snapshots = image_options[:delete_snapshots]
117
+ delete_snapshots = true if delete_snapshots.nil?
118
+ image.deregister(delete_snapshots)
119
+ end
129
120
 
130
- if !bootstrap_options[:key_name]
131
- bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler, machine_spec)
132
- end
121
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
122
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
133
123
 
134
- if machine_options[:is_windows]
135
- Chef::Log.debug('Attaching WinRM data for user data.')
136
- # Enable WinRM basic auth, HTTP and open the firewall
137
- bootstrap_options[:user_data] = user_data if bootstrap_options[:user_data].nil?
138
- end
139
- bootstrap_options.delete(:tags) # we handle these separately for performance reasons
140
- bootstrap_options
141
- end
124
+ unless bootstrap_options[:key_name]
125
+ bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler, machine_spec)
126
+ end
142
127
 
143
- def create_servers(action_handler, specs_and_options, parallelizer)
144
- super(action_handler, specs_and_options, parallelizer) do |machine_spec, server|
145
- yield machine_spec, server if block_given?
146
-
147
- machine_options = specs_and_options[machine_spec]
148
- bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
149
- tags = default_tags(machine_spec, bootstrap_options[:tags] || {})
150
-
151
- # Right now, not doing that in case manual tagging is going on
152
- server_tags = server.tags || {}
153
- extra_tags = tags.keys.select { |tag_name| !server_tags.has_key?(tag_name) }.to_a
154
- different_tags = server_tags.select { |tag_name, tag_value| tags.has_key?(tag_name) && tags[tag_name] != tag_value }.to_a
155
- if extra_tags.size > 0 || different_tags.size > 0
156
- tags_description = [ "Update tags for #{machine_spec.name} on #{driver_url}" ]
157
- tags_description += extra_tags.map { |tag| " Add #{tag} = #{tags[tag].inspect}" }
158
- tags_description += different_tags.map { |tag_name, tag_value| " Update #{tag_name} from #{tag_value.inspect} to #{tags[tag_name].inspect}"}
159
- action_handler.perform_action tags_description do
160
- # TODO should we narrow this down to just extra/different tags or
161
- # is it OK to just pass 'em all? Certainly easier to do the
162
- # latter, and I can't think of a consequence for doing so offhand.
163
- compute.create_tags(server.identity, tags)
128
+ if machine_options[:is_windows]
129
+ Chef::Log.debug("Attaching WinRM data for user data.")
130
+ # Enable WinRM basic auth, HTTP and open the firewall
131
+ bootstrap_options[:user_data] = user_data if bootstrap_options[:user_data].nil?
164
132
  end
133
+ bootstrap_options.delete(:tags) # we handle these separately for performance reasons
134
+ bootstrap_options
165
135
  end
166
- end
167
- end
168
136
 
169
- def convergence_strategy_for(machine_spec, machine_options)
170
- machine_options = Cheffish::MergedConfig.new(machine_options, {
171
- :convergence_options => {:ohai_hints => {'ec2' => ''}}
172
- })
173
- super(machine_spec, machine_options)
174
- end
137
+ def create_servers(action_handler, specs_and_options, parallelizer)
138
+ super(action_handler, specs_and_options, parallelizer) do |machine_spec, server|
139
+ yield machine_spec, server if block_given?
140
+
141
+ machine_options = specs_and_options[machine_spec]
142
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
143
+ tags = default_tags(machine_spec, bootstrap_options[:tags] || {})
144
+
145
+ # Right now, not doing that in case manual tagging is going on
146
+ server_tags = server.tags || {}
147
+ extra_tags = tags.keys.reject { |tag_name| server_tags.key?(tag_name) }.to_a
148
+ different_tags = server_tags.select { |tag_name, tag_value| tags.key?(tag_name) && tags[tag_name] != tag_value }.to_a
149
+ if !extra_tags.empty? || !different_tags.empty?
150
+ tags_description = ["Update tags for #{machine_spec.name} on #{driver_url}"]
151
+ tags_description += extra_tags.map { |tag| " Add #{tag} = #{tags[tag].inspect}" }
152
+ tags_description += different_tags.map { |tag_name, tag_value| " Update #{tag_name} from #{tag_value.inspect} to #{tags[tag_name].inspect}" }
153
+ action_handler.perform_action tags_description do
154
+ # TODO: should we narrow this down to just extra/different tags or
155
+ # is it OK to just pass 'em all? Certainly easier to do the
156
+ # latter, and I can't think of a consequence for doing so offhand.
157
+ compute.create_tags(server.identity, tags)
158
+ end
159
+ end
160
+ end
161
+ end
175
162
 
176
- # Attach given IP to machine
177
- def attach_ip(server, ip)
178
- Chef::Log.info "Attaching floating IP <#{ip}>"
179
- compute.associate_address(:instance_id => server.id,
180
- :allocation_id => option_for(machine_options, :allocation_id),
181
- :public_ip => ip)
182
- end
163
+ def convergence_strategy_for(machine_spec, machine_options)
164
+ machine_options = Cheffish::MergedConfig.new(machine_options,
165
+ convergence_options: { ohai_hints: { "ec2" => "" } })
166
+ super(machine_spec, machine_options)
167
+ end
183
168
 
184
- def self.get_aws_profile(driver_options, aws_account_id)
185
- aws_credentials = get_aws_credentials(driver_options)
186
- compute_options = driver_options[:compute_options] || {}
187
-
188
- # Order of operations:
189
- # compute_options[:aws_access_key_id] / compute_options[:aws_secret_access_key] / compute_options[:aws_security_token] / compute_options[:region]
190
- # compute_options[:aws_profile]
191
- # ENV['AWS_ACCESS_KEY_ID'] / ENV['AWS_SECRET_ACCESS_KEY'] / ENV['AWS_SECURITY_TOKEN'] / ENV['AWS_DEFAULT_REGION']
192
- # ENV['AWS_PROFILE']
193
- # ENV['DEFAULT_PROFILE']
194
- # 'default'
195
- if compute_options[:aws_access_key_id]
196
- Chef::Log.debug("Using AWS driver access key options")
197
- aws_profile = {
198
- :aws_access_key_id => compute_options[:aws_access_key_id],
199
- :aws_secret_access_key => compute_options[:aws_secret_access_key],
200
- :aws_security_token => compute_options[:aws_session_token],
201
- :region => compute_options[:region]
202
- }
203
- elsif driver_options[:aws_profile]
204
- Chef::Log.debug("Using AWS profile #{driver_options[:aws_profile]}")
205
- aws_profile = aws_credentials[driver_options[:aws_profile]]
206
- elsif ENV['AWS_ACCESS_KEY_ID'] || ENV['AWS_ACCESS_KEY']
207
- Chef::Log.debug("Using AWS environment variable access keys")
208
- aws_profile = {
209
- :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'] || ENV['AWS_ACCESS_KEY'],
210
- :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'] || ENV['AWS_SECRET_KEY'],
211
- :aws_security_token => ENV['AWS_SECURITY_TOKEN'],
212
- :region => ENV['AWS_DEFAULT_REGION'] || ENV['AWS_REGION'] || ENV['EC2_REGION']
213
- }
214
- elsif ENV['AWS_PROFILE']
215
- Chef::Log.debug("Using AWS profile #{ENV['AWS_PROFILE']} from AWS_PROFILE environment variable")
216
- aws_profile = aws_credentials[ENV['AWS_PROFILE']]
217
- if !aws_profile
218
- raise "Environment variable AWS_PROFILE is set to #{ENV['AWS_PROFILE'].inspect} but your AWS config file does not contain that profile!"
169
+ # Attach given IP to machine
170
+ def attach_ip(server, ip)
171
+ Chef::Log.info "Attaching floating IP <#{ip}>"
172
+ compute.associate_address(instance_id: server.id,
173
+ allocation_id: option_for(machine_options, :allocation_id),
174
+ public_ip: ip)
219
175
  end
220
- else
221
- Chef::Log.debug("Using AWS default profile")
222
- aws_profile = aws_credentials.default
223
- end
224
176
 
225
- default_ec2_endpoint = compute_options[:ec2_endpoint] || ENV['EC2_URL']
226
- default_iam_endpoint = compute_options[:iam_endpoint] || ENV['AWS_IAM_URL']
177
+ def self.get_aws_profile(driver_options, aws_account_id)
178
+ aws_credentials = get_aws_credentials(driver_options)
179
+ compute_options = driver_options[:compute_options] || {}
180
+
181
+ # Order of operations:
182
+ # compute_options[:aws_access_key_id] / compute_options[:aws_secret_access_key] / compute_options[:aws_security_token] / compute_options[:region]
183
+ # compute_options[:aws_profile]
184
+ # ENV['AWS_ACCESS_KEY_ID'] / ENV['AWS_SECRET_ACCESS_KEY'] / ENV['AWS_SECURITY_TOKEN'] / ENV['AWS_DEFAULT_REGION']
185
+ # ENV['AWS_PROFILE']
186
+ # ENV['DEFAULT_PROFILE']
187
+ # 'default'
188
+ if compute_options[:aws_access_key_id]
189
+ Chef::Log.debug("Using AWS driver access key options")
190
+ aws_profile = {
191
+ aws_access_key_id: compute_options[:aws_access_key_id],
192
+ aws_secret_access_key: compute_options[:aws_secret_access_key],
193
+ aws_security_token: compute_options[:aws_session_token],
194
+ region: compute_options[:region]
195
+ }
196
+ elsif driver_options[:aws_profile]
197
+ Chef::Log.debug("Using AWS profile #{driver_options[:aws_profile]}")
198
+ aws_profile = aws_credentials[driver_options[:aws_profile]]
199
+ elsif ENV["AWS_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY"]
200
+ Chef::Log.debug("Using AWS environment variable access keys")
201
+ aws_profile = {
202
+ aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY"],
203
+ aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_KEY"],
204
+ aws_security_token: ENV["AWS_SECURITY_TOKEN"],
205
+ region: ENV["AWS_DEFAULT_REGION"] || ENV["AWS_REGION"] || ENV["EC2_REGION"]
206
+ }
207
+ elsif ENV["AWS_PROFILE"]
208
+ Chef::Log.debug("Using AWS profile #{ENV['AWS_PROFILE']} from AWS_PROFILE environment variable")
209
+ aws_profile = aws_credentials[ENV["AWS_PROFILE"]]
210
+ unless aws_profile
211
+ raise "Environment variable AWS_PROFILE is set to #{ENV['AWS_PROFILE'].inspect} but your AWS config file does not contain that profile!"
212
+ end
213
+ else
214
+ Chef::Log.debug("Using AWS default profile")
215
+ aws_profile = aws_credentials.default
216
+ end
227
217
 
228
- # Merge in account info for profile
229
- if aws_profile
230
- aws_profile = aws_profile.merge(aws_account_info_for(aws_profile, default_iam_endpoint))
231
- end
218
+ default_ec2_endpoint = compute_options[:ec2_endpoint] || ENV["EC2_URL"]
219
+ default_iam_endpoint = compute_options[:iam_endpoint] || ENV["AWS_IAM_URL"]
232
220
 
233
- # If no profile is found (or the profile is not the right account), search
234
- # for a profile that matches the given account ID
235
- if aws_account_id && (!aws_profile || aws_profile[:aws_account_id] != aws_account_id)
236
- aws_profile = find_aws_profile_for_account_id(aws_credentials, aws_account_id, default_iam_endpoint)
237
- end
221
+ # Merge in account info for profile
222
+ if aws_profile
223
+ aws_profile = aws_profile.merge(aws_account_info_for(aws_profile, default_iam_endpoint))
224
+ end
238
225
 
239
- fail 'No AWS profile specified! Are you missing something in the Chef or AWS config?' unless aws_profile
226
+ # If no profile is found (or the profile is not the right account), search
227
+ # for a profile that matches the given account ID
228
+ if aws_account_id && (!aws_profile || aws_profile[:aws_account_id] != aws_account_id)
229
+ aws_profile = find_aws_profile_for_account_id(aws_credentials, aws_account_id, default_iam_endpoint)
230
+ end
240
231
 
241
- aws_profile[:ec2_endpoint] ||= default_ec2_endpoint
242
- aws_profile[:iam_endpoint] ||= default_iam_endpoint
232
+ raise "No AWS profile specified! Are you missing something in the Chef or AWS config?" unless aws_profile
243
233
 
244
- aws_profile.delete_if { |key, value| value.nil? }
245
- aws_profile
246
- end
234
+ aws_profile[:ec2_endpoint] ||= default_ec2_endpoint
235
+ aws_profile[:iam_endpoint] ||= default_iam_endpoint
247
236
 
248
- def self.find_aws_profile_for_account_id(aws_credentials, aws_account_id, default_iam_endpoint=nil)
249
- aws_profile = nil
250
- aws_credentials.each do |profile_name, profile|
251
- begin
252
- aws_account_info = aws_account_info_for(profile, default_iam_endpoint)
253
- rescue
254
- Chef::Log.warn("Could not connect to AWS profile #{aws_credentials[:name]}: #{$!}")
255
- Chef::Log.debug($!.backtrace.join("\n"))
256
- next
237
+ aws_profile.delete_if { |_key, value| value.nil? }
238
+ aws_profile
257
239
  end
258
- if aws_account_info[:aws_account_id] == aws_account_id
259
- aws_profile = profile
260
- aws_profile[:name] = profile_name
261
- aws_profile = aws_profile.merge(aws_account_info)
262
- break
263
- end
264
- end
265
- if aws_profile
266
- Chef::Log.info("Discovered AWS profile #{aws_profile[:name]} pointing at account #{aws_account_id}. Using ...")
267
- else
268
- raise "No AWS profile leads to account #{aws_account_id}. Do you need to add profiles to the AWS config?"
269
- end
270
- aws_profile
271
- end
272
240
 
273
- def self.aws_account_info_for(aws_profile, default_iam_endpoint = nil)
274
- iam_endpoint = aws_profile[:iam_endpoint] || default_iam_endpoint
275
-
276
- @@aws_account_info ||= {}
277
- @@aws_account_info[aws_profile[:aws_access_key_id]] ||= begin
278
- options = {
279
- # Endpoint configuration
280
- :aws_access_key_id => aws_profile[:aws_access_key_id],
281
- :aws_secret_access_key => aws_profile[:aws_secret_access_key],
282
- :aws_session_token => aws_profile[:aws_security_token]
283
- }
284
- if iam_endpoint
285
- options[:host] = URI(iam_endpoint).host
286
- options[:scheme] = URI(iam_endpoint).scheme
287
- options[:port] = URI(iam_endpoint).port
288
- options[:path] = URI(iam_endpoint).path
241
+ def self.find_aws_profile_for_account_id(aws_credentials, aws_account_id, default_iam_endpoint = nil)
242
+ aws_profile = nil
243
+ aws_credentials.each do |profile_name, profile|
244
+ begin
245
+ aws_account_info = aws_account_info_for(profile, default_iam_endpoint)
246
+ rescue StandardError
247
+ Chef::Log.warn("Could not connect to AWS profile #{aws_credentials[:name]}: #{$ERROR_INFO}")
248
+ Chef::Log.debug($ERROR_INFO.backtrace.join("\n"))
249
+ next
250
+ end
251
+ next unless aws_account_info[:aws_account_id] == aws_account_id
252
+ aws_profile = profile
253
+ aws_profile[:name] = profile_name
254
+ aws_profile = aws_profile.merge(aws_account_info)
255
+ break
256
+ end
257
+ if aws_profile
258
+ Chef::Log.info("Discovered AWS profile #{aws_profile[:name]} pointing at account #{aws_account_id}. Using ...")
259
+ else
260
+ raise "No AWS profile leads to account #{aws_account_id}. Do you need to add profiles to the AWS config?"
261
+ end
262
+ aws_profile
289
263
  end
290
- options.delete_if { |key, value| value.nil? }
291
-
292
- iam = Fog::AWS::IAM.new(options)
293
- arn = begin
294
- # TODO it would be nice if Fog let you do this normally ...
295
- iam.send(:request, {
296
- 'Action' => 'GetUser',
297
- :parser => Fog::Parsers::AWS::IAM::GetUser.new
298
- }).body['User']['Arn']
299
- rescue Fog::AWS::IAM::Error
300
- # TODO Someone tell me there is a better way to find out your current
301
- # user ID than this! This is what happens when you use an IAM user
302
- # with default privileges.
303
- if $!.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
304
- arn = $1
305
- else
306
- raise
307
- end
308
- end
309
- arn_split = arn.split(':', 6)
310
- {
311
- :aws_account_id => arn_split[4],
312
- :aws_username => arn_split[5],
313
- :aws_user_arn => arn
314
- }
315
- end
316
- end
317
264
 
318
- def self.get_aws_credentials(driver_options)
319
- # Grab the list of possible credentials
320
- if driver_options[:aws_credentials]
321
- aws_credentials = driver_options[:aws_credentials]
322
- else
323
- aws_credentials = Credentials.new
324
- if driver_options[:aws_config_file]
325
- aws_credentials.load_ini(driver_options[:aws_config_file])
326
- elsif driver_options[:aws_csv_file]
327
- aws_credentials.load_csv(driver_options[:aws_csv_file])
328
- else
329
- aws_credentials.load_default
265
+ def self.aws_account_info_for(aws_profile, default_iam_endpoint = nil)
266
+ iam_endpoint = aws_profile[:iam_endpoint] || default_iam_endpoint
267
+
268
+ @@aws_account_info ||= {}
269
+ @@aws_account_info[aws_profile[:aws_access_key_id]] ||= begin
270
+ options = {
271
+ # Endpoint configuration
272
+ aws_access_key_id: aws_profile[:aws_access_key_id],
273
+ aws_secret_access_key: aws_profile[:aws_secret_access_key],
274
+ aws_session_token: aws_profile[:aws_security_token]
275
+ }
276
+ if iam_endpoint
277
+ options[:host] = URI(iam_endpoint).host
278
+ options[:scheme] = URI(iam_endpoint).scheme
279
+ options[:port] = URI(iam_endpoint).port
280
+ options[:path] = URI(iam_endpoint).path
281
+ end
282
+ options.delete_if { |_key, value| value.nil? }
283
+
284
+ iam = Fog::AWS::IAM.new(options)
285
+ arn = begin
286
+ # TODO: it would be nice if Fog let you do this normally ...
287
+ iam.send(:request,
288
+ "Action" => "GetUser",
289
+ :parser => Fog::Parsers::AWS::IAM::GetUser.new).body["User"]["Arn"]
290
+ rescue Fog::AWS::IAM::Error
291
+ # TODO: Someone tell me there is a better way to find out your current
292
+ # user ID than this! This is what happens when you use an IAM user
293
+ # with default privileges.
294
+ if $ERROR_INFO.message =~ /AccessDenied.+(arn:aws:iam::\d+:\S+)/
295
+ arn = Regexp.last_match(1)
296
+ else
297
+ raise
298
+ end
299
+ end
300
+ arn_split = arn.split(":", 6)
301
+ {
302
+ aws_account_id: arn_split[4],
303
+ aws_username: arn_split[5],
304
+ aws_user_arn: arn
305
+ }
306
+ end
330
307
  end
331
- end
332
- aws_credentials
333
- end
334
308
 
335
- def self.compute_options_for(provider, id, config)
336
- new_compute_options = {}
337
- new_compute_options[:provider] = provider
338
- new_config = { :driver_options => { :compute_options => new_compute_options }}
339
- new_defaults = {
340
- :driver_options => { :compute_options => {} },
341
- :machine_options => { :bootstrap_options => {} }
342
- }
343
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
344
-
345
- if id && id != ''
346
- # AWS canonical URLs are of the form fog:AWS:
347
- if id =~ /^(\d{12}|IAM)(:(.+))?$/
348
- if $2
349
- id = $1
350
- new_compute_options[:region] = $3
309
+ def self.get_aws_credentials(driver_options)
310
+ # Grab the list of possible credentials
311
+ if driver_options[:aws_credentials]
312
+ aws_credentials = driver_options[:aws_credentials]
351
313
  else
352
- Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-provisioning (before chef-metal 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 chef_provisioning.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
314
+ aws_credentials = Credentials.new
315
+ if driver_options[:aws_config_file]
316
+ aws_credentials.load_ini(driver_options[:aws_config_file])
317
+ elsif driver_options[:aws_csv_file]
318
+ aws_credentials.load_csv(driver_options[:aws_csv_file])
319
+ else
320
+ aws_credentials.load_default
321
+ end
353
322
  end
354
- else
355
- # Assume it is a profile name, and set that.
356
- aws_profile, region = id.split(':', 2)
357
- new_config[:driver_options][:aws_profile] = aws_profile
358
- new_compute_options[:region] = region
359
- id = nil
323
+ aws_credentials
360
324
  end
361
- end
362
- if id == 'IAM'
363
- id = "IAM:#{result[:driver_options][:compute_options][:region]}"
364
- new_config[:driver_options][:aws_account_info] = { aws_username: 'IAM' }
365
- new_compute_options[:use_iam_profile] = true
366
- else
367
- aws_profile = get_aws_profile(result[:driver_options], id)
368
- new_compute_options[:aws_access_key_id] = aws_profile[:aws_access_key_id]
369
- new_compute_options[:aws_secret_access_key] = aws_profile[:aws_secret_access_key]
370
- new_compute_options[:aws_session_token] = aws_profile[:aws_security_token]
371
- new_defaults[:driver_options][:compute_options][:region] = aws_profile[:region]
372
- new_defaults[:driver_options][:compute_options][:endpoint] = aws_profile[:ec2_endpoint]
373
-
374
- account_info = aws_account_info_for(result[:driver_options][:compute_options])
375
- new_config[:driver_options][:aws_account_info] = account_info
376
- id = "#{account_info[:aws_account_id]}:#{result[:driver_options][:compute_options][:region]}"
377
- end
378
325
 
379
- # Make sure we're using a reasonable default AMI, for now this is Ubuntu 14.04 LTS
380
- result[:machine_options][:bootstrap_options][:image_id] ||=
381
- default_ami_for_region(result[:driver_options][:compute_options][:region])
326
+ def self.compute_options_for(provider, id, config)
327
+ new_compute_options = {}
328
+ new_compute_options[:provider] = provider
329
+ new_config = { driver_options: { compute_options: new_compute_options } }
330
+ new_defaults = {
331
+ driver_options: { compute_options: {} },
332
+ machine_options: { bootstrap_options: {} }
333
+ }
334
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
335
+
336
+ if id && id != ""
337
+ # AWS canonical URLs are of the form fog:AWS:
338
+ if id =~ /^(\d{12}|IAM)(:(.+))?$/
339
+ if Regexp.last_match(2)
340
+ id = Regexp.last_match(1)
341
+ new_compute_options[:region] = Regexp.last_match(3)
342
+ else
343
+ Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-provisioning (before chef-metal 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 chef_provisioning.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
344
+ end
345
+ else
346
+ # Assume it is a profile name, and set that.
347
+ aws_profile, region = id.split(":", 2)
348
+ new_config[:driver_options][:aws_profile] = aws_profile
349
+ new_compute_options[:region] = region
350
+ id = nil
351
+ end
352
+ end
353
+ if id == "IAM"
354
+ id = "IAM:#{result[:driver_options][:compute_options][:region]}"
355
+ new_config[:driver_options][:aws_account_info] = { aws_username: "IAM" }
356
+ new_compute_options[:use_iam_profile] = true
357
+ else
358
+ aws_profile = get_aws_profile(result[:driver_options], id)
359
+ new_compute_options[:aws_access_key_id] = aws_profile[:aws_access_key_id]
360
+ new_compute_options[:aws_secret_access_key] = aws_profile[:aws_secret_access_key]
361
+ new_compute_options[:aws_session_token] = aws_profile[:aws_security_token]
362
+ new_defaults[:driver_options][:compute_options][:region] = aws_profile[:region]
363
+ new_defaults[:driver_options][:compute_options][:endpoint] = aws_profile[:ec2_endpoint]
364
+
365
+ account_info = aws_account_info_for(result[:driver_options][:compute_options])
366
+ new_config[:driver_options][:aws_account_info] = account_info
367
+ id = "#{account_info[:aws_account_id]}:#{result[:driver_options][:compute_options][:region]}"
368
+ end
382
369
 
383
- [result, id]
384
- end
370
+ # Make sure we're using a reasonable default AMI, for now this is Ubuntu 14.04 LTS
371
+ result[:machine_options][:bootstrap_options][:image_id] ||=
372
+ default_ami_for_region(result[:driver_options][:compute_options][:region])
385
373
 
386
- def create_many_servers(num_servers, bootstrap_options, parallelizer)
387
- # Create all the servers in one request if we have a version of Fog that can do that
388
- if compute.servers.respond_to?(:create_many)
389
- servers = compute.servers.create_many(num_servers, num_servers, bootstrap_options)
390
- if block_given?
391
- parallelizer.parallelize(servers) do |server|
392
- yield server
393
- end.to_a
374
+ [result, id]
394
375
  end
395
- servers
396
- else
397
- super
398
- end
399
- end
400
376
 
401
- def servers_for(machine_specs)
402
- # Grab all the servers in one request
403
- instance_ids = machine_specs.map { |machine_spec| (machine_spec.location || {})['server_id'] }.select { |id| !id.nil? }
404
- servers = compute.servers.all('instance-id' => instance_ids)
405
- result = {}
406
- machine_specs.each do |machine_spec|
407
- if machine_spec.location
408
- result[machine_spec] = servers.select { |s| s.id == machine_spec.location['server_id'] }.first
409
- else
410
- result[machine_spec] = nil
377
+ def create_many_servers(num_servers, bootstrap_options, parallelizer)
378
+ # Create all the servers in one request if we have a version of Fog that can do that
379
+ if compute.servers.respond_to?(:create_many)
380
+ servers = compute.servers.create_many(num_servers, num_servers, bootstrap_options)
381
+ if block_given?
382
+ parallelizer.parallelize(servers) do |server|
383
+ yield server
384
+ end.to_a
385
+ end
386
+ servers
387
+ else
388
+ super
389
+ end
411
390
  end
412
- end
413
- result
414
- end
415
391
 
416
- private
417
- def user_data
418
- # TODO: Make this use HTTPS at some point.
419
- <<EOD
392
+ def servers_for(machine_specs)
393
+ # Grab all the servers in one request
394
+ instance_ids = machine_specs.map { |machine_spec| (machine_spec.location || {})["server_id"] }.reject(&:nil?)
395
+ servers = compute.servers.all("instance-id" => instance_ids)
396
+ result = {}
397
+ machine_specs.each do |machine_spec|
398
+ if machine_spec.location
399
+ result[machine_spec] = servers.select { |s| s.id == machine_spec.location["server_id"] }.first
400
+ else
401
+ result[machine_spec] = nil
402
+ end
403
+ end
404
+ result
405
+ end
406
+
407
+ private
408
+
409
+ def user_data
410
+ # TODO: Make this use HTTPS at some point.
411
+ <<EOD
420
412
  <powershell>
421
413
  winrm quickconfig -q
422
414
  winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'
@@ -433,60 +425,57 @@ net start winrm
433
425
  </powershell>
434
426
 
435
427
  EOD
436
- end
437
-
438
- def self.default_ami_for_region(region)
439
- Chef::Log.debug("Choosing default AMI for region '#{region}'")
440
-
441
- case region
442
- when 'ap-northeast-1'
443
- 'ami-c786dcc6'
444
- when 'ap-southeast-1'
445
- 'ami-eefca7bc'
446
- when 'ap-southeast-2'
447
- 'ami-996706a3'
448
- when 'eu-west-1'
449
- 'ami-4ab46b3d'
450
- when 'sa-east-1'
451
- 'ami-6770d87a'
452
- when 'us-east-1'
453
- 'ami-d2ff23ba'
454
- when 'us-west-1'
455
- 'ami-73717d36'
456
- when 'us-west-2'
457
- 'ami-f1ce8bc1'
458
- end
459
- end
428
+ end
460
429
 
461
- # Wait for the Windows Admin password to become available
462
- # @param [Hash] machine_spec Machine spec data
463
- # @return [String] encrypted admin password
464
- def wait_for_admin_password(machine_spec)
465
- time_elapsed = 0
466
- sleep_time = 10
467
- max_wait_time = 900 # 15 minutes
468
- encrypted_admin_password = nil
469
- instance_id = machine_spec.location['server_id']
470
-
471
-
472
- Chef::Log.info "waiting for #{machine_spec.name}'s admin password to be available..."
473
- while time_elapsed < max_wait_time && encrypted_admin_password.nil?
474
- response = compute.get_password_data(instance_id)
475
- encrypted_admin_password = response.body['passwordData']
476
- if encrypted_admin_password.nil?
477
- Chef::Log.info "#{time_elapsed}/#{max_wait_time}s elapsed -- sleeping #{sleep_time} seconds for #{machine_spec.name}'s admin password."
478
- sleep(sleep_time)
479
- time_elapsed += sleep_time
430
+ def self.default_ami_for_region(region)
431
+ Chef::Log.debug("Choosing default AMI for region '#{region}'")
432
+
433
+ case region
434
+ when "ap-northeast-1"
435
+ "ami-c786dcc6"
436
+ when "ap-southeast-1"
437
+ "ami-eefca7bc"
438
+ when "ap-southeast-2"
439
+ "ami-996706a3"
440
+ when "eu-west-1"
441
+ "ami-4ab46b3d"
442
+ when "sa-east-1"
443
+ "ami-6770d87a"
444
+ when "us-east-1"
445
+ "ami-d2ff23ba"
446
+ when "us-west-1"
447
+ "ami-73717d36"
448
+ when "us-west-2"
449
+ "ami-f1ce8bc1"
450
+ end
480
451
  end
481
- end
482
452
 
483
- Chef::Log.info "#{machine_spec.name}'s admin password is available!'"
453
+ # Wait for the Windows Admin password to become available
454
+ # @param [Hash] machine_spec Machine spec data
455
+ # @return [String] encrypted admin password
456
+ def wait_for_admin_password(machine_spec)
457
+ time_elapsed = 0
458
+ sleep_time = 10
459
+ max_wait_time = 900 # 15 minutes
460
+ encrypted_admin_password = nil
461
+ instance_id = machine_spec.location["server_id"]
462
+
463
+ Chef::Log.info "waiting for #{machine_spec.name}'s admin password to be available..."
464
+ while time_elapsed < max_wait_time && encrypted_admin_password.nil?
465
+ response = compute.get_password_data(instance_id)
466
+ encrypted_admin_password = response.body["passwordData"]
467
+ next unless encrypted_admin_password.nil?
468
+ Chef::Log.info "#{time_elapsed}/#{max_wait_time}s elapsed -- sleeping #{sleep_time} seconds for #{machine_spec.name}'s admin password."
469
+ sleep(sleep_time)
470
+ time_elapsed += sleep_time
471
+ end
484
472
 
485
- encrypted_admin_password
486
- end
473
+ Chef::Log.info "#{machine_spec.name}'s admin password is available!'"
487
474
 
475
+ encrypted_admin_password
476
+ end
477
+ end
478
+ end
488
479
  end
489
480
  end
490
481
  end
491
- end
492
- end