chef-provisioning-fog 0.26.1 → 0.26.3

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.
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