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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +12 -0
- data/Rakefile +6 -0
- data/lib/chef/provider/aws_auto_scaling_group.rb +44 -0
- data/lib/chef/provider/aws_ebs_volume.rb +59 -0
- data/lib/chef/provider/aws_key_pair.rb +187 -0
- data/lib/chef/provider/aws_launch_config.rb +43 -0
- data/lib/chef/provider/aws_provider.rb +89 -0
- data/lib/chef/provider/aws_s3_bucket.rb +41 -0
- data/lib/chef/provider/aws_sns_topic.rb +39 -0
- data/lib/chef/provider/aws_sqs_queue.rb +47 -0
- data/lib/chef/provisioning/aws_driver.rb +3 -0
- data/lib/chef/provisioning/aws_driver/aws_profile.rb +73 -0
- data/lib/chef/provisioning/aws_driver/credentials.rb +84 -0
- data/lib/chef/provisioning/aws_driver/driver.rb +396 -0
- data/lib/chef/provisioning/aws_driver/resources.rb +7 -0
- data/lib/chef/provisioning/aws_driver/version.rb +7 -0
- data/lib/chef/provisioning/driver_init/aws.rb +3 -0
- data/lib/chef/resource/aws_auto_scaling_group.rb +16 -0
- data/lib/chef/resource/aws_ebs_volume.rb +31 -0
- data/lib/chef/resource/aws_key_pair.rb +29 -0
- data/lib/chef/resource/aws_launch_config.rb +14 -0
- data/lib/chef/resource/aws_resource.rb +10 -0
- data/lib/chef/resource/aws_s3_bucket.rb +21 -0
- data/lib/chef/resource/aws_sns_topic.rb +20 -0
- data/lib/chef/resource/aws_sqs_queue.rb +24 -0
- metadata +141 -0
@@ -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,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
|