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.
- checksums.yaml +4 -4
- data/Gemfile +29 -8
- data/Rakefile +23 -12
- data/chef-provisioning-fog.gemspec +24 -27
- data/lib/chef/provider/fog_key_pair.rb +49 -53
- data/lib/chef/provider/scaleway_volume.rb +46 -48
- data/lib/chef/provisioning/driver_init/fog.rb +1 -1
- data/lib/chef/provisioning/fog_driver/driver.rb +646 -653
- data/lib/chef/provisioning/fog_driver/providers/aws.rb +411 -422
- data/lib/chef/provisioning/fog_driver/providers/aws/credentials.rb +88 -90
- data/lib/chef/provisioning/fog_driver/providers/cloudstack.rb +32 -34
- data/lib/chef/provisioning/fog_driver/providers/digitalocean.rb +98 -100
- data/lib/chef/provisioning/fog_driver/providers/google.rb +27 -34
- data/lib/chef/provisioning/fog_driver/providers/joyent.rb +53 -55
- data/lib/chef/provisioning/fog_driver/providers/openstack.rb +139 -146
- data/lib/chef/provisioning/fog_driver/providers/rackspace.rb +40 -44
- data/lib/chef/provisioning/fog_driver/providers/scaleway.rb +183 -189
- data/lib/chef/provisioning/fog_driver/providers/softlayer.rb +61 -64
- data/lib/chef/provisioning/fog_driver/providers/vcair.rb +72 -78
- data/lib/chef/provisioning/fog_driver/providers/xenserver.rb +56 -69
- data/lib/chef/provisioning/fog_driver/recipe_dsl.rb +11 -12
- data/lib/chef/provisioning/fog_driver/version.rb +1 -1
- data/lib/chef/resource/fog_key_pair.rb +8 -8
- data/lib/chef/resource/scaleway_volume.rb +8 -8
- data/spec/spec_helper.rb +7 -7
- data/spec/support/chef/provisioning/fog_driver/providers/testdriver.rb +3 -3
- data/spec/unit/chef/provisioning/fog_driver/driver_spec.rb +39 -38
- data/spec/unit/fog_driver_spec.rb +6 -8
- data/spec/unit/providers/aws/credentials_spec.rb +10 -10
- data/spec/unit/providers/rackspace_spec.rb +5 -6
- data/spec/unit/providers/scaleway_spec.rb +9 -9
- data/spec/unit/providers/softlayer.rb +7 -7
- metadata +6 -36
- data/README.md +0 -357
@@ -1,422 +1,414 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
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
|
-
|
16
|
-
|
13
|
+
module Provisioning
|
14
|
+
module FogDriver
|
15
|
+
module Providers
|
16
|
+
class AWS < FogDriver::Driver
|
17
|
+
require_relative "aws/credentials"
|
17
18
|
|
18
|
-
|
19
|
+
Driver.register_provider_class("AWS", FogDriver::Providers::AWS)
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
99
|
-
|
65
|
+
Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
|
66
|
+
end
|
100
67
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
226
|
-
|
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
|
-
|
229
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
245
|
-
|
246
|
-
end
|
234
|
+
aws_profile[:ec2_endpoint] ||= default_ec2_endpoint
|
235
|
+
aws_profile[:iam_endpoint] ||= default_iam_endpoint
|
247
236
|
|
248
|
-
|
249
|
-
|
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
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
380
|
-
|
381
|
-
|
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
|
-
|
384
|
-
|
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
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
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
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
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
|
-
|
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
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
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
|
-
|
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
|
-
|
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
|