chef-provisioning-aws 0.1

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