chef-provisioning-aws 0.1

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.
@@ -0,0 +1,43 @@
1
+ require 'chef/provider/aws_provider'
2
+
3
+ class Chef::Provider::AwsLaunchConfig < Chef::Provider::AwsProvider
4
+ action :create do
5
+ if existing_launch_config.nil?
6
+ converge_by "Creating new Launch Config #{id} in #{new_resource.region_name}" do
7
+ @existing_launch_config = auto_scaling.launch_configurations.create(
8
+ new_resource.name,
9
+ new_resource.image,
10
+ new_resource.instance_type
11
+ )
12
+
13
+ new_resource.save
14
+ end
15
+ end
16
+ end
17
+
18
+ action :delete do
19
+ if existing_launch_config
20
+ converge_by "Deleting Launch Config #{id} in #{new_resource.region_name}" do
21
+ begin
22
+ existing_launch_config.delete
23
+ rescue AWS::AutoScaling::Errors::ResourceInUse
24
+ sleep 5
25
+ retry
26
+ end
27
+ end
28
+ end
29
+
30
+ new_resource.delete
31
+ end
32
+
33
+ def existing_launch_config
34
+ @existing_launch_config ||= begin
35
+ elc = auto_scaling.launch_configurations[new_resource.name]
36
+ elc.exists? ? elc : nil
37
+ end
38
+ end
39
+
40
+ def id
41
+ new_resource.name
42
+ end
43
+ end
@@ -0,0 +1,89 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef/provisioning/aws_driver/credentials'
3
+
4
+ class Chef::Provider::AwsProvider < Chef::Provider::LWRPBase
5
+ use_inline_resources
6
+
7
+ attr_reader :credentials
8
+
9
+ # All these need to implement whyrun
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ def from_hash hash
15
+ hash.each do |k,v|
16
+ begin
17
+ self.instance_variable_set("@#{k}", v)
18
+ rescue NameError => ne
19
+ # nothing...
20
+ end
21
+ end
22
+ self
23
+ end
24
+
25
+ def resource_from_databag(fqn, chef_server = Cheffish.default_chef_server)
26
+ chef_api = Cheffish.chef_server_api(chef_server)
27
+ begin
28
+ data = chef_api.get("/data/#{databag_name}/#{fqn}")
29
+ rescue Net::HTTPServerException => e
30
+ if e.response.code == '404'
31
+ nil
32
+ else
33
+ raise
34
+ end
35
+ end
36
+ end
37
+
38
+ def initialize(*args)
39
+ super
40
+ # TODO - temporary, needs to be better
41
+ @credentials = Chef::Provisioning::AWSDriver::Credentials.new
42
+ @credentials.load_default
43
+ credentials = @credentials.default
44
+ AWS.config(:access_key_id => credentials[:aws_access_key_id],
45
+ :secret_access_key => credentials[:aws_secret_access_key])
46
+ end
47
+
48
+ def self.databag_name
49
+ raise 'Class does not implement databag name'
50
+ end
51
+
52
+ def fqn
53
+ if id
54
+ id
55
+ else
56
+ "#{new_resource.name}_#{new_resource.region_name}"
57
+ end
58
+ end
59
+
60
+ # AWS objects we might need
61
+ def ec2
62
+ @ec2 ||= aws_init { AWS::EC2.new }
63
+ end
64
+
65
+ def sns
66
+ @sns ||= aws_init { AWS::SNS.new }
67
+ end
68
+
69
+ def sqs
70
+ @sqs ||= aws_init { AWS::SQS.new }
71
+ end
72
+
73
+ def s3
74
+ @s3 ||= aws_init { AWS::S3.new }
75
+ end
76
+
77
+ def auto_scaling
78
+ @auto_scaling ||= aws_init { AWS::AutoScaling.new }
79
+ end
80
+
81
+ private
82
+ def aws_init
83
+ credentials = @credentials.default
84
+ region = new_resource.region_name || credentials[:region]
85
+ # Pivot region
86
+ AWS.config(:region => region)
87
+ yield
88
+ end
89
+ end
@@ -0,0 +1,41 @@
1
+ require 'chef/provider/aws_provider'
2
+ require 'date'
3
+
4
+ class Chef::Provider::AwsS3Bucket < Chef::Provider::AwsProvider
5
+ action :create do
6
+ if existing_bucket == nil
7
+ converge_by "Creating new S3 bucket #{fqn}" do
8
+ bucket = s3.buckets.create(fqn)
9
+ bucket.tags['Name'] = new_resource.name
10
+ end
11
+ end
12
+
13
+ new_resource.save
14
+ end
15
+
16
+ action :delete do
17
+ if existing_bucket
18
+ converge_by "Deleting S3 bucket #{fqn}" do
19
+ existing_bucket.delete
20
+ end
21
+ end
22
+
23
+ new_resource.delete
24
+ end
25
+
26
+
27
+ def existing_bucket
28
+ Chef::Log.debug("Checking for S3 bucket #{fqn}")
29
+ @existing_bucket ||= s3.buckets[fqn] if s3.buckets[fqn].exists?
30
+ end
31
+
32
+ # Fully qualified bucket name (i.e resource_region unless otherwise specified)
33
+ def id
34
+ new_resource.bucket_name
35
+ end
36
+
37
+ def fqn
38
+ super.gsub('_', '-')
39
+ end
40
+
41
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/provider/aws_provider'
2
+ require 'date'
3
+
4
+ class Chef::Provider::AwsSnsTopic < Chef::Provider::AwsProvider
5
+
6
+ action :create do
7
+ if existing_topic == nil
8
+ converge_by "Creating new SNS topic #{fqn} in #{new_resource.region_name}" do
9
+ sns.topics.create(fqn)
10
+
11
+ new_resource.created_at DataTime.now.to_s
12
+ new_resource.save
13
+ end
14
+ end
15
+ end
16
+
17
+ action :delete do
18
+ if existing_topic
19
+ converge_by "Deleting SNS topic #{fqn} in #{new_resource.region_name}" do
20
+ existing_topic.delete
21
+ end
22
+ end
23
+
24
+ new_resource.delete
25
+ end
26
+
27
+ def existing_topic
28
+ @existing_topic ||= begin
29
+ sns.topics.named(fqn)
30
+ rescue
31
+ nil
32
+ end
33
+ end
34
+
35
+ def id
36
+ new_resource.topic_name
37
+ end
38
+
39
+ end
@@ -0,0 +1,47 @@
1
+ require 'chef/provider/aws_provider'
2
+ require 'date'
3
+
4
+ class Chef::Provider::AwsSqsQueue < Chef::Provider::AwsProvider
5
+
6
+ action :create do
7
+ if existing_queue == nil
8
+ converge_by "Creating new SQS queue #{fqn} in #{new_resource.region_name}" do
9
+ loop do
10
+ begin
11
+ sqs.queues.create(fqn)
12
+ break
13
+ rescue AWS::SQS::Errors::QueueDeletedRecently
14
+ sleep 5
15
+ end
16
+ end
17
+
18
+ new_resource.created_at DateTime.now.to_s
19
+ new_resource.save
20
+ end
21
+ end
22
+ end
23
+
24
+ action :delete do
25
+ if existing_queue
26
+ converge_by "Deleting SQS queue #{fqn} in #{new_resource.region_name}" do
27
+ existing_queue.delete
28
+ end
29
+ end
30
+
31
+ new_resource.delete
32
+ end
33
+
34
+ def existing_queue
35
+ @existing_queue ||= begin
36
+ sqs.queues.named(fqn)
37
+ rescue
38
+ nil
39
+ end
40
+ end
41
+
42
+ # Fully qualified queue name (i.e luigi:us-east-1)
43
+ def id
44
+ new_resource.queue_name
45
+ end
46
+
47
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef/provisioning'
2
+ require 'chef/provisioning/aws_driver/driver'
3
+ require 'chef/provisioning/aws_driver/resources'
@@ -0,0 +1,73 @@
1
+ class AwsProfile
2
+
3
+ # Order of operations:
4
+ # compute_options[:aws_access_key_id] / compute_options[:aws_secret_access_key] / compute_options[:aws_security_token] / compute_options[:region]
5
+ # compute_options[:aws_profile]
6
+ # ENV['AWS_ACCESS_KEY_ID'] / ENV['AWS_SECRET_ACCESS_KEY'] / ENV['AWS_SECURITY_TOKEN'] / ENV['AWS_REGION']
7
+ # ENV['AWS_PROFILE']
8
+ # ENV['DEFAULT_PROFILE']
9
+ # 'default'
10
+ def initialize(driver_options, aws_account_id)
11
+ aws_credentials = get_aws_credentials(driver_options)
12
+ compute_options = driver_options[:compute_options] || {}
13
+
14
+ aws_profile = if compute_options[:aws_access_key_id]
15
+ Chef::Log.debug('Using AWS driver access key options')
16
+ {
17
+ :aws_access_key_id => compute_options[:aws_access_key_id],
18
+ :aws_secret_access_key => compute_options[:aws_secret_access_key],
19
+ :aws_security_token => compute_options[:aws_session_token],
20
+ :region => compute_options[:region]
21
+ }
22
+ elsif driver_options[:aws_profile]
23
+ Chef::Log.debug("Using AWS profile #{driver_options[:aws_profile]}")
24
+ aws_credentials[driver_options[:aws_profile]]
25
+ elsif ENV['AWS_ACCESS_KEY_ID'] || ENV['AWS_ACCESS_KEY']
26
+ Chef::Log.debug('Using AWS environment variable access keys')
27
+ {
28
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'] || ENV['AWS_ACCESS_KEY'],
29
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'] || ENV['AWS_SECRET_KEY'],
30
+ :aws_security_token => ENV['AWS_SECURITY_TOKEN'],
31
+ :region => ENV['AWS_REGION']
32
+ }
33
+ elsif ENV['AWS_PROFILE']
34
+ Chef::Log.debug("Using AWS profile #{ENV['AWS_PROFILE']} from AWS_PROFILE environment variable")
35
+ aws_credentials[ENV['AWS_PROFILE']]
36
+ else
37
+ Chef::Log.debug('Using AWS default profile')
38
+ aws_credentials.default
39
+ end
40
+ # Endpoint configuration
41
+ if compute_options[:ec2_endpoint]
42
+ aws_profile[:ec2_endpoint] = compute_options[:ec2_endpoint]
43
+ elsif ENV['EC2_URL']
44
+ aws_profile[:ec2_endpoint] = ENV['EC2_URL']
45
+ end
46
+ if compute_options[:iam_endpoint]
47
+ aws_profile[:iam_endpoint] = compute_options[:iam_endpoint]
48
+ elsif ENV['AWS_IAM_URL']
49
+ aws_profile[:iam_endpoint] = ENV['AWS_IAM_URL']
50
+ else
51
+ aws_profile[:iam_endpoint] = 'https://iam.amazonaws.com/'
52
+ end
53
+
54
+ # Merge in account info for profile
55
+ if aws_profile
56
+ aws_profile = aws_profile.merge(aws_account_info_for(aws_profile))
57
+ end
58
+
59
+ # If no profile is found (or the profile is not the right account), search
60
+ # for a profile that matches the given account ID
61
+ if aws_account_id && (!aws_profile || aws_profile[:aws_account_id] != aws_account_id)
62
+ aws_profile = find_aws_profile_for_account_id(aws_credentials, aws_account_id)
63
+ end
64
+
65
+ unless aws_profile
66
+ raise 'No AWS profile specified! Are you missing something in the Chef config or ~/.aws/config?'
67
+ end
68
+
69
+ aws_profile.delete_if { |_, value| value.nil? }
70
+ aws_profile
71
+ end
72
+
73
+ end
@@ -0,0 +1,84 @@
1
+ require 'inifile'
2
+ require 'csv'
3
+
4
+ class Chef
5
+ module Provisioning
6
+ module AWSDriver
7
+ # Reads in a credentials file in Amazon's download format and presents the credentials to you
8
+ class Credentials
9
+ def initialize
10
+ @credentials = {}
11
+ load_default
12
+ end
13
+
14
+ include Enumerable
15
+
16
+ def default
17
+ if @credentials.size == 0
18
+ raise 'No credentials loaded! Do you have a ~/.aws/config?'
19
+ end
20
+ @credentials[ENV['AWS_DEFAULT_PROFILE'] || 'default'] || @credentials.first[1]
21
+ end
22
+
23
+ def keys
24
+ @credentials.keys
25
+ end
26
+
27
+ def [](name)
28
+ @credentials[name]
29
+ end
30
+
31
+ def each(&block)
32
+ @credentials.each(&block)
33
+ end
34
+
35
+ def load_ini(credentials_ini_file)
36
+ inifile = IniFile.load(File.expand_path(credentials_ini_file))
37
+ if inifile
38
+ inifile.each_section do |section|
39
+ if section =~ /^\s*profile\s+(.+)$/ || section =~ /^\s*(default)\s*/
40
+ profile_name = $1.strip
41
+ profile = inifile[section].inject({}) do |result, pair|
42
+ result[pair[0].to_sym] = pair[1]
43
+ result
44
+ end
45
+ profile[:name] = profile_name
46
+ @credentials[profile_name] = profile
47
+ end
48
+ end
49
+ else
50
+ # Get it to throw an error
51
+ File.open(File.expand_path(credentials_ini_file)) do
52
+ end
53
+ end
54
+ end
55
+
56
+ def load_csv(credentials_csv_file)
57
+ CSV.new(File.open(credentials_csv_file), :headers => :first_row).each do |row|
58
+ @credentials[row['User Name']] = {
59
+ :name => row['User Name'],
60
+ :user_name => row['User Name'],
61
+ :aws_access_key_id => row['Access Key Id'],
62
+ :aws_secret_access_key => row['Secret Access Key']
63
+ }
64
+ end
65
+ end
66
+
67
+ def load_default
68
+ config_file = ENV['AWS_CONFIG_FILE'] || File.expand_path('~/.aws/config')
69
+ if File.file?(config_file)
70
+ load_ini(config_file)
71
+ end
72
+ end
73
+
74
+ def self.method_missing(name, *args, &block)
75
+ singleton.send(name, *args, &block)
76
+ end
77
+
78
+ def self.singleton
79
+ @aws_credentials ||= Credentials.new
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,396 @@
1
+ require 'chef/mixin/shell_out'
2
+ require 'chef/provisioning/driver'
3
+ require 'chef/provisioning/convergence_strategy/install_cached'
4
+ require 'chef/provisioning/convergence_strategy/install_sh'
5
+ require 'chef/provisioning/convergence_strategy/no_converge'
6
+ require 'chef/provisioning/transport/ssh'
7
+ require 'chef/provisioning/machine/windows_machine'
8
+ require 'chef/provisioning/machine/unix_machine'
9
+ require 'chef/provisioning/machine_spec'
10
+
11
+ require 'chef/provisioning/aws_driver/version'
12
+ require 'chef/provisioning/aws_driver/credentials'
13
+
14
+ require 'yaml'
15
+ require 'aws-sdk-v1'
16
+
17
+
18
+ class Chef
19
+ module Provisioning
20
+ module AWSDriver
21
+ # Provisions machines using the AWS SDK
22
+ class Driver < Chef::Provisioning::Driver
23
+
24
+ include Chef::Mixin::ShellOut
25
+
26
+ attr_reader :region
27
+
28
+ # URL scheme:
29
+ # aws:account_id:region
30
+ # TODO: migration path from fog:AWS - parse that URL
31
+ # canonical URL calls realpath on <path>
32
+ def self.from_url(driver_url, config)
33
+ Driver.new(driver_url, config)
34
+ end
35
+
36
+ def initialize(driver_url, config)
37
+ super
38
+ credentials = aws_credentials.default
39
+ @region = credentials[:region]
40
+ # TODO: fix credentials here
41
+ AWS.config(:access_key_id => credentials[:aws_access_key_id],
42
+ :secret_access_key => credentials[:aws_secret_access_key],
43
+ :region => credentials[:region])
44
+ end
45
+
46
+ def self.canonicalize_url(driver_url, config)
47
+ url = driver_url.split(":")[0]
48
+ [ "aws:#{url}", config ]
49
+ end
50
+
51
+
52
+ # Load balancer methods
53
+ def allocate_load_balancer(action_handler, lb_spec, lb_options)
54
+ existing_elb = load_balancer_for(lb_spec)
55
+ if !existing_elb.exists?
56
+ lb_spec.location = {
57
+ 'driver_url' => driver_url,
58
+ 'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
59
+ 'allocated_at' => Time.now.utc.to_s,
60
+ 'host_node' => action_handler.host_node,
61
+ }
62
+
63
+ availability_zones = lb_options[:availability_zones]
64
+ listeners = lb_options[:listeners]
65
+ elb.load_balancers.create(lb_spec.name,
66
+ :availability_zones => availability_zones,
67
+ :listeners => listeners)
68
+ end
69
+ end
70
+
71
+ def ready_load_balancer(action_handler, lb_spec, lb_options)
72
+ end
73
+
74
+ def destroy_load_balancer(action_handler, lb_spec, lb_options)
75
+ end
76
+
77
+ def update_load_balancer(action_handler, lb_spec, lb_options, opts = {})
78
+ existing_elb = load_balancer_for(lb_spec)
79
+ # TODO update listeners and zones?
80
+ if existing_elb.exists?
81
+ machines = opts[:machines]
82
+ existing_instance_ids = existing_elb.instances.collect { |i| i.instance_id }
83
+ new_instance_ids = machines.keys.collect do |machine_name|
84
+ machine_spec = machines[machine_name]
85
+ machine_spec.location['instance_id']
86
+ end
87
+
88
+ instance_ids_to_add = new_instance_ids - existing_instance_ids
89
+ instance_ids_to_remove = existing_instance_ids - new_instance_ids
90
+
91
+ if instance_ids_to_add && instance_ids_to_add.size > 0
92
+ existing_elb.instances.add(instance_ids_to_add)
93
+ end
94
+
95
+ if instance_ids_to_remove && instance_ids_to_remove.size > 0
96
+ existing_elb.instances.remove(instance_ids_to_remove)
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+
103
+ # Image methods
104
+ def allocate_image(action_handler, image_spec, image_options, machine_spec)
105
+ end
106
+
107
+ def ready_image(action_handler, image_spec, image_options)
108
+ end
109
+
110
+ def destroy_image(action_handler, image_spec, image_options)
111
+ end
112
+
113
+ # Machine methods
114
+ def allocate_machine(action_handler, machine_spec, machine_options)
115
+ existing_instance = instance_for(machine_spec)
116
+ if existing_instance == nil || !existing_instance.exists?
117
+
118
+ machine_spec.location = {
119
+ 'driver_url' => driver_url,
120
+ 'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
121
+ 'allocated_at' => Time.now.utc.to_s,
122
+ 'host_node' => action_handler.host_node,
123
+ 'image_id' => machine_options[:image_id]
124
+ }
125
+
126
+ image_id = machine_options[:image_id] || default_ami_for_region(@region)
127
+ action_handler.report_progress "Creating #{machine_spec.name} with AMI #{image_id} in #{@region}..."
128
+ bootstrap_options = machine_options[:bootstrap_options] || {}
129
+ bootstrap_options[:image_id] = image_id
130
+ Chef::Log.debug "AWS Bootstrap options: #{bootstrap_options.inspect}"
131
+ instance = ec2.instances.create(bootstrap_options)
132
+ instance.tags['Name'] = machine_spec.name
133
+ machine_spec.location['instance_id'] = instance.id
134
+ action_handler.report_progress "Created #{instance.id} in #{@region}..."
135
+ end
136
+ end
137
+
138
+ def ready_machine(action_handler, machine_spec, machine_options)
139
+ instance = instance_for(machine_spec)
140
+
141
+ if instance.nil?
142
+ raise "Machine #{machine_spec.name} does not have an instance associated with it, or instance does not exist."
143
+ end
144
+
145
+ if instance.status != :running
146
+ action_handler.report_progress "Starting #{machine_spec.name} (#{machine_spec.location['instance_id']}) in #{@region}..."
147
+ wait_until_ready(action_handler, machine_spec)
148
+ wait_for_transport(action_handler, machine_spec, machine_options)
149
+ else
150
+ action_handler.report_progress "#{machine_spec.name} (#{machine_spec.location['instance_id']}) already running in #{@region}..."
151
+ end
152
+
153
+ machine_for(machine_spec, machine_options, instance)
154
+
155
+ end
156
+
157
+ def destroy_machine(action_handler, machine_spec, machine_options)
158
+ instance = instance_for(machine_spec)
159
+ if instance
160
+ instance.terminate
161
+ end
162
+ end
163
+
164
+
165
+
166
+
167
+ private
168
+ def machine_for(machine_spec, machine_options, instance = nil)
169
+ instance ||= instance_for(machine_spec)
170
+
171
+ if !instance
172
+ raise "Instance for node #{machine_spec.name} has not been created!"
173
+ end
174
+
175
+ if machine_spec.location['is_windows']
176
+ Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
177
+ else
178
+ Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
179
+ end
180
+ end
181
+
182
+ def start_machine(action_handler, machine_spec, machine_options, base_image_name)
183
+ end
184
+
185
+ def ec2
186
+ @ec2 ||= AWS.ec2
187
+ end
188
+
189
+ def elb
190
+ @elb ||= AWS::ELB.new
191
+ end
192
+
193
+ def default_ssh_username
194
+ 'ubuntu'
195
+ end
196
+
197
+ def keypair_for(bootstrap_options)
198
+ if bootstrap_options[:key_name]
199
+ keypair_name = bootstrap_options[:key_name]
200
+ existing_key_pair = ec2.key_pairs[keypair_name]
201
+ if !existing_key_pair.exists?
202
+ ec2.key_pairs.create(keypair_name)
203
+ end
204
+ existing_key_pair
205
+ end
206
+ end
207
+
208
+ def load_balancer_for(lb_spec)
209
+ if lb_spec.name
210
+ elb.load_balancers[lb_spec.name]
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ def instance_for(machine_spec)
217
+ if machine_spec.location && machine_spec.location['instance_id']
218
+ ec2.instances[machine_spec.location['instance_id']]
219
+ else
220
+ nil
221
+ end
222
+ end
223
+
224
+ def transport_for(machine_spec, machine_options, instance)
225
+ # TODO winrm
226
+ create_ssh_transport(machine_spec, machine_options, instance)
227
+ end
228
+
229
+ def compute_options
230
+
231
+ end
232
+
233
+ def aws_credentials
234
+ # Grab the list of possible credentials
235
+ @aws_credentials ||= if driver_options[:aws_credentials]
236
+ driver_options[:aws_credentials]
237
+ else
238
+ credentials = Credentials.new
239
+ if driver_options[:aws_config_file]
240
+ credentials.load_ini(driver_options.delete(:aws_config_file))
241
+ elsif driver_options[:aws_csv_file]
242
+ credentials.load_csv(driver_options.delete(:aws_csv_file))
243
+ else
244
+ credentials.load_default
245
+ end
246
+ credentials
247
+ end
248
+ end
249
+
250
+ def default_ami_for_region(region)
251
+ Chef::Log.debug("Choosing default AMI for region '#{region}'")
252
+
253
+ case region
254
+ when 'ap-northeast-1'
255
+ 'ami-c786dcc6'
256
+ when 'ap-southeast-1'
257
+ 'ami-eefca7bc'
258
+ when 'ap-southeast-2'
259
+ 'ami-996706a3'
260
+ when 'eu-west-1'
261
+ 'ami-4ab46b3d'
262
+ when 'sa-east-1'
263
+ 'ami-6770d87a'
264
+ when 'us-east-1'
265
+ 'ami-d2ff23ba'
266
+ when 'us-west-1'
267
+ 'ami-73717d36'
268
+ when 'us-west-2'
269
+ 'ami-f1ce8bc1'
270
+ else
271
+ raise 'Unsupported region!'
272
+ end
273
+ end
274
+
275
+ def create_ssh_transport(machine_spec, machine_options, instance)
276
+ ssh_options = ssh_options_for(machine_spec, machine_options, instance)
277
+ username = machine_spec.location['ssh_username'] || default_ssh_username
278
+ if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.location['ssh_username']
279
+ Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.location['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.location['ssh_username']}. Please edit the node and change the chef_provisioning.location.ssh_username attribute if you want to change it.")
280
+ end
281
+ options = {}
282
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
283
+ options[:prefix] = 'sudo '
284
+ end
285
+
286
+ remote_host = nil
287
+
288
+ if machine_spec.location['use_private_ip_for_ssh']
289
+ remote_host = instance.private_ip_address
290
+ elsif !instance.public_ip_address
291
+ Chef::Log.warn("Server #{machine_spec.name} has no public ip address. Using private ip '#{instance.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
292
+ remote_host = instance.private_ip_address
293
+ elsif instance.public_ip_address
294
+ remote_host = instance.public_ip_address
295
+ else
296
+ raise "Server #{instance.id} has no private or public IP address!"
297
+ end
298
+
299
+ #Enable pty by default
300
+ options[:ssh_pty_enable] = true
301
+ options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
302
+
303
+ Chef::Provisioning::Transport::SSH.new(remote_host, username, ssh_options, options, config)
304
+ end
305
+
306
+ def ssh_options_for(machine_spec, machine_options, instance)
307
+ result = {
308
+ # TODO create a user known hosts file
309
+ # :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'],
310
+ # :paranoid => true,
311
+ :auth_methods => [ 'publickey' ],
312
+ :keys_only => true,
313
+ :host_key_alias => "#{instance.id}.AWS"
314
+ }.merge(machine_options[:ssh_options] || {})
315
+ if instance.respond_to?(:private_key) && instance.private_key
316
+ result[:key_data] = [ instance.private_key ]
317
+ elsif instance.respond_to?(:key_name) && instance.key_name
318
+ key = get_private_key(instance.key_name)
319
+ unless key
320
+ raise "Server has key name '#{instance.key_name}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
321
+ end
322
+ result[:key_data] = [ key ]
323
+ elsif machine_spec.location['key_name']
324
+ key = get_private_key(machine_spec.location['key_name'])
325
+ unless key
326
+ raise "Server was created with key name '#{machine_spec.location['key_name']}', but the corresponding private key was not found locally. Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
327
+ end
328
+ result[:key_data] = [ key ]
329
+ elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_path]
330
+ result[:key_data] = [ IO.read(machine_options[:bootstrap_options][:key_path]) ]
331
+ elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_name]
332
+ result[:key_data] = [ get_private_key(machine_options[:bootstrap_options][:key_name]) ]
333
+ else
334
+ # TODO make a way to suggest other keys to try ...
335
+ raise "No key found to connect to #{machine_spec.name} (#{machine_spec.location.inspect})!"
336
+ end
337
+ result
338
+ end
339
+
340
+ def convergence_strategy_for(machine_spec, machine_options)
341
+ # Defaults
342
+ if !machine_spec.location
343
+ return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
344
+ end
345
+
346
+ if machine_spec.location['is_windows']
347
+ Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
348
+ elsif machine_options[:cached_installer] == true
349
+ Chef::Provisioning::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
350
+ else
351
+ Chef::Provisioning::ConvergenceStrategy::InstallSh.new(machine_options[:convergence_options], config)
352
+ end
353
+ end
354
+
355
+ def wait_until_ready(action_handler, machine_spec)
356
+ instance = instance_for(machine_spec)
357
+ time_elapsed = 0
358
+ sleep_time = 10
359
+ max_wait_time = 120
360
+ unless instance.status == :running
361
+ if action_handler.should_perform_actions
362
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be ready ..."
363
+ while time_elapsed < 120 && instance.status != :running
364
+ action_handler.report_progress "been waiting #{time_elapsed}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be ready ..."
365
+ sleep(sleep_time)
366
+ time_elapsed += sleep_time
367
+ end
368
+ action_handler.report_progress "#{machine_spec.name} is now ready"
369
+ end
370
+ end
371
+ end
372
+
373
+ def wait_for_transport(action_handler, machine_spec, machine_options)
374
+ instance = instance_for(machine_spec)
375
+ time_elapsed = 0
376
+ sleep_time = 10
377
+ max_wait_time = 120
378
+ transport = transport_for(machine_spec, machine_options, instance)
379
+ unless transport.available?
380
+ if action_handler.should_perform_actions
381
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be connectable (transport up and running) ..."
382
+ while time_elapsed < 120 && !transport.available?
383
+ action_handler.report_progress "been waiting #{time_elapsed}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be connectable ..."
384
+ sleep(sleep_time)
385
+ time_elapsed += sleep_time
386
+ end
387
+
388
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
389
+ end
390
+ end
391
+ end
392
+
393
+ end
394
+ end
395
+ end
396
+ end