bosh_cli_plugin_aws 1.5.0.pre.1113
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/bosh/cli/commands/aws.rb +464 -0
- data/lib/bosh_cli_plugin_aws/aws_config.rb +141 -0
- data/lib/bosh_cli_plugin_aws/aws_provider.rb +53 -0
- data/lib/bosh_cli_plugin_aws/bat_manifest.rb +40 -0
- data/lib/bosh_cli_plugin_aws/bootstrap.rb +31 -0
- data/lib/bosh_cli_plugin_aws/bosh_bootstrap.rb +158 -0
- data/lib/bosh_cli_plugin_aws/bosh_manifest.rb +71 -0
- data/lib/bosh_cli_plugin_aws/ec2.rb +265 -0
- data/lib/bosh_cli_plugin_aws/elb.rb +132 -0
- data/lib/bosh_cli_plugin_aws/micro_bosh_bootstrap.rb +64 -0
- data/lib/bosh_cli_plugin_aws/microbosh_manifest.rb +117 -0
- data/lib/bosh_cli_plugin_aws/migration.rb +40 -0
- data/lib/bosh_cli_plugin_aws/migration_helper.rb +150 -0
- data/lib/bosh_cli_plugin_aws/migrator.rb +137 -0
- data/lib/bosh_cli_plugin_aws/rds.rb +182 -0
- data/lib/bosh_cli_plugin_aws/route53.rb +103 -0
- data/lib/bosh_cli_plugin_aws/s3.rb +93 -0
- data/lib/bosh_cli_plugin_aws/version.rb +5 -0
- data/lib/bosh_cli_plugin_aws/vpc.rb +181 -0
- data/lib/bosh_cli_plugin_aws.rb +31 -0
- data/migrations/20130412000811_create_key_pairs.rb +8 -0
- data/migrations/20130412004642_create_vpc.rb +65 -0
- data/migrations/20130412181302_create_route53_records.rb +37 -0
- data/migrations/20130412183544_create_rds_dbs.rb +35 -0
- data/migrations/20130412192351_create_s3.rb +4 -0
- data/migrations/20130529212130_create_more_unique_s3_buckets.rb +33 -0
- data/migrations/20130531180445_create_bosh_rds_db.rb +30 -0
- data/migrations/20130826150635_update_elb_for_websockets.rb +97 -0
- data/migrations/20130827000001_add_secondary_az_to_vpc.rb +34 -0
- data/templates/aws_configuration_template.yml.erb +187 -0
- data/templates/aws_migration.erb +5 -0
- data/templates/aws_migration_spec.erb +12 -0
- data/templates/bat.yml.erb +84 -0
- data/templates/bosh.yml.erb +198 -0
- data/templates/micro_bosh.yml.erb +82 -0
- metadata +163 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
class Bootstrap
|
4
|
+
AWS_JENKINS_BUCKET = "bosh-jenkins-artifacts"
|
5
|
+
|
6
|
+
attr_accessor :options, :runner
|
7
|
+
|
8
|
+
def initialize(runner, options)
|
9
|
+
self.options = options
|
10
|
+
self.runner = runner
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_user(username, password)
|
14
|
+
user = Bosh::Cli::Command::User.new(runner)
|
15
|
+
user.options = self.options
|
16
|
+
user.create(username, password)
|
17
|
+
login(username, password)
|
18
|
+
end
|
19
|
+
|
20
|
+
def login(username, password)
|
21
|
+
misc = Bosh::Cli::Command::Misc.new(runner)
|
22
|
+
misc.options = self.options
|
23
|
+
misc.login(username, password)
|
24
|
+
end
|
25
|
+
|
26
|
+
def manifest
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require_relative 'bootstrap'
|
2
|
+
require 'net/https'
|
3
|
+
require 'bosh/stemcell/archive'
|
4
|
+
require 'bosh/stemcell/archive_filename'
|
5
|
+
require 'bosh/stemcell/infrastructure'
|
6
|
+
require 'bosh/stemcell/operating_system'
|
7
|
+
|
8
|
+
module Bosh
|
9
|
+
module Aws
|
10
|
+
BootstrapError = Class.new(StandardError)
|
11
|
+
|
12
|
+
class BoshBootstrap < Bootstrap
|
13
|
+
attr_accessor :director, :s3
|
14
|
+
|
15
|
+
def initialize(director, s3, options)
|
16
|
+
self.options = options
|
17
|
+
self.options[:non_interactive] = true
|
18
|
+
self.director = director
|
19
|
+
self.s3 = s3
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_requirements
|
23
|
+
|
24
|
+
release_exist = director.list_releases.detect { |r| r['name'] == 'bosh' }
|
25
|
+
first_stemcell = director.list_stemcells.first
|
26
|
+
|
27
|
+
existing_deployments = director.list_deployments.map { |deployment| deployment['name'] }
|
28
|
+
|
29
|
+
if existing_deployments.include? manifest.bosh_deployment_name
|
30
|
+
raise BootstrapError, <<-MSG
|
31
|
+
Deployment `#{manifest.bosh_deployment_name}' already exists.
|
32
|
+
This command should be used for bootstrapping bosh from scratch.
|
33
|
+
MSG
|
34
|
+
end
|
35
|
+
|
36
|
+
return release_exist, first_stemcell
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
release_exist, first_stemcell = validate_requirements
|
41
|
+
|
42
|
+
fetch_and_upload_release unless release_exist
|
43
|
+
if first_stemcell
|
44
|
+
manifest.stemcell_name = first_stemcell['name']
|
45
|
+
else
|
46
|
+
manifest.stemcell_name = Bosh::Stemcell::Archive.new(fetch_and_upload_stemcell).name
|
47
|
+
end
|
48
|
+
generate_manifest
|
49
|
+
|
50
|
+
deploy
|
51
|
+
|
52
|
+
target_bosh_and_log_in
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def manifest
|
58
|
+
unless @manifest
|
59
|
+
vpc_receipt_filename = File.expand_path('aws_vpc_receipt.yml')
|
60
|
+
route53_receipt_filename = File.expand_path('aws_route53_receipt.yml')
|
61
|
+
bosh_rds_receipt_filename = File.expand_path('aws_rds_bosh_receipt.yml')
|
62
|
+
|
63
|
+
vpc_config = load_yaml_file(vpc_receipt_filename)
|
64
|
+
route53_config = load_yaml_file(route53_receipt_filename)
|
65
|
+
bosh_rds_config = load_yaml_file(bosh_rds_receipt_filename)
|
66
|
+
@manifest = Bosh::Aws::BoshManifest.new(vpc_config, route53_config, director.uuid, bosh_rds_config, options)
|
67
|
+
end
|
68
|
+
|
69
|
+
@manifest
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate_manifest
|
73
|
+
deployment_folder = File.join('deployments', manifest.deployment_name)
|
74
|
+
|
75
|
+
FileUtils.mkdir_p deployment_folder
|
76
|
+
Dir.chdir(deployment_folder) do
|
77
|
+
write_yaml(manifest, manifest.file_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
deployment_command = Bosh::Cli::Command::Deployment.new
|
81
|
+
deployment_command.options = self.options
|
82
|
+
deployment_command.set_current(File.join(deployment_folder, manifest.file_name))
|
83
|
+
end
|
84
|
+
|
85
|
+
def fetch_and_upload_release
|
86
|
+
release_command = Bosh::Cli::Command::Release.new
|
87
|
+
release_command.options = self.options
|
88
|
+
release_command.upload(bosh_release)
|
89
|
+
end
|
90
|
+
|
91
|
+
def target_bosh_and_log_in
|
92
|
+
misc_command = Bosh::Cli::Command::Misc.new
|
93
|
+
misc_command.options = self.options
|
94
|
+
misc_command.set_target(manifest.vip)
|
95
|
+
misc_command.options[:target] = manifest.vip
|
96
|
+
misc_command.login('admin', 'admin')
|
97
|
+
|
98
|
+
self.options[:target] = misc_command.config.target
|
99
|
+
end
|
100
|
+
|
101
|
+
def deploy
|
102
|
+
deployment_command = Bosh::Cli::Command::Deployment.new
|
103
|
+
deployment_command.options = self.options
|
104
|
+
deployment_command.perform
|
105
|
+
|
106
|
+
new_director = Bosh::Cli::Client::Director.new("https://#{manifest.vip}:25555", nil, nil,
|
107
|
+
num_retries: 12, retry_wait_interval: 5)
|
108
|
+
new_director.wait_until_ready
|
109
|
+
end
|
110
|
+
|
111
|
+
def fetch_and_upload_stemcell
|
112
|
+
stemcell_command = Bosh::Cli::Command::Stemcell.new
|
113
|
+
stemcell_command.options = self.options
|
114
|
+
stemcell_path = bosh_stemcell
|
115
|
+
stemcell_command.upload(stemcell_path)
|
116
|
+
stemcell_path
|
117
|
+
end
|
118
|
+
|
119
|
+
def bosh_stemcell
|
120
|
+
if bosh_stemcell_override
|
121
|
+
say("Using stemcell #{bosh_stemcell_override}")
|
122
|
+
return bosh_stemcell_override
|
123
|
+
end
|
124
|
+
|
125
|
+
s3.copy_remote_file(AWS_JENKINS_BUCKET,
|
126
|
+
"bosh-stemcell/aws/#{latest_aws_ubuntu_bosh_stemcell_filename}",
|
127
|
+
'bosh_stemcell.tgz')
|
128
|
+
end
|
129
|
+
|
130
|
+
def latest_aws_ubuntu_bosh_stemcell_filename
|
131
|
+
infrastructure = Bosh::Stemcell::Infrastructure.for('aws')
|
132
|
+
operating_system = Bosh::Stemcell::OperatingSystem.for('ubuntu')
|
133
|
+
Bosh::Stemcell::ArchiveFilename.new('latest', infrastructure, operating_system, 'bosh-stemcell', true)
|
134
|
+
end
|
135
|
+
|
136
|
+
def bosh_release
|
137
|
+
if bosh_release_override
|
138
|
+
say("Using release #{bosh_release_override}")
|
139
|
+
return bosh_release_override
|
140
|
+
end
|
141
|
+
s3.copy_remote_file(AWS_JENKINS_BUCKET, "release/bosh-#{bosh_version}.tgz", 'bosh_release.tgz')
|
142
|
+
end
|
143
|
+
|
144
|
+
def bosh_stemcell_override
|
145
|
+
ENV['BOSH_OVERRIDE_LIGHT_STEMCELL_URL']
|
146
|
+
end
|
147
|
+
|
148
|
+
def bosh_release_override
|
149
|
+
ENV['BOSH_OVERRIDE_RELEASE_TGZ']
|
150
|
+
end
|
151
|
+
|
152
|
+
def bosh_version
|
153
|
+
ENV['BOSH_VERSION_OVERRIDE'] ||
|
154
|
+
Bosh::Aws::VERSION.split('.').last
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
class BoshManifest < MicroboshManifest
|
4
|
+
|
5
|
+
attr_reader :director_uuid, :rds_receipt
|
6
|
+
|
7
|
+
attr_accessor :stemcell_name
|
8
|
+
|
9
|
+
def initialize(vpc_receipt, route53_receipt, director_uuid, rds_receipt, options={})
|
10
|
+
super(vpc_receipt, route53_receipt, options)
|
11
|
+
@director_uuid = director_uuid
|
12
|
+
@rds_receipt = rds_receipt
|
13
|
+
@stemcell_name = 'bosh-aws-xen-ubuntu'
|
14
|
+
end
|
15
|
+
|
16
|
+
def file_name
|
17
|
+
"bosh.yml"
|
18
|
+
end
|
19
|
+
|
20
|
+
def deployment_name
|
21
|
+
"bosh"
|
22
|
+
end
|
23
|
+
|
24
|
+
def vip
|
25
|
+
route53_receipt['elastic_ips']['bosh']['ips'][0] || warning('Missing vip field')
|
26
|
+
end
|
27
|
+
|
28
|
+
def bosh_deployment_name
|
29
|
+
"vpc-bosh-#{name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def director_ssl_key
|
33
|
+
certificate.key.gsub("\n", "\n ")
|
34
|
+
end
|
35
|
+
|
36
|
+
def director_ssl_cert
|
37
|
+
certificate.certificate.gsub("\n", "\n ")
|
38
|
+
end
|
39
|
+
|
40
|
+
def bosh_rds_properties
|
41
|
+
rds_receipt['deployment_manifest']['properties']['bosh']
|
42
|
+
end
|
43
|
+
|
44
|
+
def bosh_rds_host
|
45
|
+
bosh_rds_properties['address']
|
46
|
+
end
|
47
|
+
|
48
|
+
def bosh_rds_port
|
49
|
+
bosh_rds_properties['port']
|
50
|
+
end
|
51
|
+
|
52
|
+
def bosh_rds_password
|
53
|
+
bosh_rds_properties['roles'].first['password']
|
54
|
+
end
|
55
|
+
|
56
|
+
def bosh_rds_user
|
57
|
+
bosh_rds_properties['roles'].first['name']
|
58
|
+
end
|
59
|
+
|
60
|
+
# RSpec overloads to_yaml when you set up expectations on an object;
|
61
|
+
# so to_y is just a way to get directly at the to_yaml implementation without fighting RSpec.
|
62
|
+
def to_y
|
63
|
+
ERB.new(File.read(get_template("bosh.yml.erb"))).result(binding)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_yaml
|
67
|
+
to_y
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
class EC2
|
4
|
+
|
5
|
+
NAT_AMI_ID = {
|
6
|
+
'us-east-1' => 'ami-f619c29f', # ami-vpc-nat-1.1.0-beta
|
7
|
+
'us-west-1' => 'ami-3bcc9e7e', # ami-vpc-nat-1.0.0-beta
|
8
|
+
'us-west-2' => 'ami-52ff7262', # ami-vpc-nat-1.0.0-beta
|
9
|
+
'eu-west-1' => 'ami-e5e2d991', # ami-vpc-nat-1.1.0-beta
|
10
|
+
'ap-southeast-1' => 'ami-02eb9350', # ami-vpc-nat-1.0.0-beta
|
11
|
+
'ap-northeast-1' => 'ami-14d86d15', # ami-vpc-nat-1.0.0-beta
|
12
|
+
'ap-southeast-2' => 'ami-ab990e91', # ami-vpc-nat-1.0.0-beta
|
13
|
+
'sa-east-1' => 'ami-0039e61d', # ami-vpc-nat-1.0.0-beta
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_reader :elastic_ips
|
17
|
+
|
18
|
+
def initialize(credentials)
|
19
|
+
@aws_provider = AwsProvider.new(credentials)
|
20
|
+
@elastic_ips = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def instances_count
|
24
|
+
terminatable_instances.size
|
25
|
+
end
|
26
|
+
|
27
|
+
def vpcs
|
28
|
+
aws_ec2.vpcs
|
29
|
+
end
|
30
|
+
|
31
|
+
def dhcp_options
|
32
|
+
aws_ec2.dhcp_options
|
33
|
+
end
|
34
|
+
|
35
|
+
def allocate_elastic_ips(count)
|
36
|
+
count.times do
|
37
|
+
@elastic_ips << allocate_elastic_ip.public_ip
|
38
|
+
end
|
39
|
+
#say "\tallocated #{eip.public_ip}".make_green
|
40
|
+
end
|
41
|
+
|
42
|
+
def allocate_elastic_ip
|
43
|
+
aws_ec2.elastic_ips.allocate(vpc: true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def release_elastic_ips(ips)
|
47
|
+
aws_ec2.elastic_ips.each { |ip| ip.release if ips.include? ip.public_ip }
|
48
|
+
end
|
49
|
+
|
50
|
+
def release_all_elastic_ips
|
51
|
+
releasable_elastic_ips.map(&:release)
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_internet_gateway
|
55
|
+
aws_ec2.internet_gateways.create
|
56
|
+
end
|
57
|
+
|
58
|
+
def internet_gateway_ids
|
59
|
+
aws_ec2.internet_gateways.map(&:id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete_internet_gateways(ids)
|
63
|
+
Array(ids).each do |id|
|
64
|
+
gw = aws_ec2.internet_gateways[id]
|
65
|
+
gw.attachments.map(&:delete)
|
66
|
+
gw.delete
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_instance(options)
|
71
|
+
aws_ec2.instances.create(options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_nat_instance(options)
|
75
|
+
name = options["name"]
|
76
|
+
key_pair = select_key_pair_for_instance(name, options["key_name"])
|
77
|
+
|
78
|
+
|
79
|
+
instance_options = {
|
80
|
+
image_id: NAT_AMI_ID[aws_provider.region],
|
81
|
+
instance_type: options.fetch("instance_type", "m1.small"),
|
82
|
+
subnet: options["subnet_id"],
|
83
|
+
private_ip_address: options["ip"],
|
84
|
+
security_groups: [options["security_group"]],
|
85
|
+
key_name: key_pair
|
86
|
+
}
|
87
|
+
|
88
|
+
create_instance(instance_options).tap do |instance|
|
89
|
+
Bosh::AwsCloud::ResourceWait.for_instance(instance: instance, state: :running)
|
90
|
+
instance.add_tag("Name", {value: name})
|
91
|
+
elastic_ip = allocate_elastic_ip
|
92
|
+
Bosh::Common.retryable(tries: 30, on: AWS::EC2::Errors::InvalidAddress::NotFound) do
|
93
|
+
instance.associate_elastic_ip(elastic_ip)
|
94
|
+
true
|
95
|
+
end
|
96
|
+
disable_src_dest_checking(instance.id)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def disable_src_dest_checking(instance_id)
|
101
|
+
aws_ec2.client.modify_instance_attribute(
|
102
|
+
:instance_id => instance_id,
|
103
|
+
:source_dest_check => {:value => false}
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def terminate_instances
|
108
|
+
terminatable_instances.each(&:terminate)
|
109
|
+
1.upto(100) do
|
110
|
+
break if terminatable_instances.empty?
|
111
|
+
sleep 4
|
112
|
+
end
|
113
|
+
terminatable_instances.empty?
|
114
|
+
end
|
115
|
+
|
116
|
+
def instance_names
|
117
|
+
terminatable_instances.inject({}) do |memo, instance|
|
118
|
+
memo[instance.instance_id] = instance.tags["Name"] || '<unnamed instance>'
|
119
|
+
memo
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_running_instance_by_name(name)
|
124
|
+
instances = aws_ec2.instances.select { |instance| instance.tags["Name"] == name && instance.status == :running }
|
125
|
+
raise "More than one running instance with name '#{name}'." if instances.count > 1
|
126
|
+
instances.first
|
127
|
+
end
|
128
|
+
|
129
|
+
def terminatable_instance_names
|
130
|
+
terminatable_instances.inject({}) do |memo, instance|
|
131
|
+
memo[instance.instance_id] = instance.tags["Name"]
|
132
|
+
memo
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_volumes
|
137
|
+
unattached_volumes.each do |vol|
|
138
|
+
begin
|
139
|
+
vol.delete
|
140
|
+
rescue AWS::EC2::Errors::InvalidVolume::NotFound
|
141
|
+
# ignored
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def volume_count
|
147
|
+
unattached_volumes.count
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_key_pair(name, path_to_public_private_key)
|
151
|
+
private_key_path = path_to_public_private_key.gsub(/\.pub$/, '')
|
152
|
+
public_key_path = "#{private_key_path}.pub"
|
153
|
+
|
154
|
+
if !File.exist?(private_key_path)
|
155
|
+
system "ssh-keygen", "-q", '-N', "", "-t", "rsa", "-f", private_key_path
|
156
|
+
end
|
157
|
+
|
158
|
+
unless key_pair_by_name(name).nil?
|
159
|
+
err "Key pair #{name} already exists on AWS"
|
160
|
+
end
|
161
|
+
|
162
|
+
aws_ec2.key_pairs.import(name, File.read(public_key_path))
|
163
|
+
end
|
164
|
+
|
165
|
+
def key_pair_by_name(name)
|
166
|
+
key_pairs.detect { |kp| kp.name == name }
|
167
|
+
end
|
168
|
+
|
169
|
+
def key_pairs
|
170
|
+
aws_ec2.key_pairs.to_a
|
171
|
+
end
|
172
|
+
|
173
|
+
def force_add_key_pair(name, path_to_public_private_key)
|
174
|
+
remove_key_pair(name)
|
175
|
+
add_key_pair(name, path_to_public_private_key)
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove_key_pair(name)
|
179
|
+
key_pair = key_pair_by_name(name)
|
180
|
+
key_pair.delete unless key_pair.nil?
|
181
|
+
Bosh::Common.retryable(tries: 15) do
|
182
|
+
key_pair_by_name(name).nil?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def remove_all_key_pairs
|
187
|
+
aws_ec2.key_pairs.each(&:delete)
|
188
|
+
|
189
|
+
Bosh::Common.retryable(tries: 10) do
|
190
|
+
aws_ec2.key_pairs.to_a.empty?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def delete_all_security_groups
|
195
|
+
dsg = deletable_security_groups
|
196
|
+
|
197
|
+
# Revoke all permissions before deleting because a permission can reference
|
198
|
+
# another security group, causing a delete to fail
|
199
|
+
dsg.each do |sg|
|
200
|
+
sg.ingress_ip_permissions.map(&:revoke)
|
201
|
+
sg.egress_ip_permissions.map(&:revoke)
|
202
|
+
end
|
203
|
+
|
204
|
+
dsg.each do |sg|
|
205
|
+
sg.delete unless (sg.name == "default" && !sg.vpc_id)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
attr_reader :aws_provider
|
212
|
+
|
213
|
+
def aws_ec2
|
214
|
+
aws_provider.ec2
|
215
|
+
end
|
216
|
+
|
217
|
+
def terminatable_instances
|
218
|
+
aws_ec2.instances.reject do |i|
|
219
|
+
begin
|
220
|
+
i.api_termination_disabled? || i.status.to_s == "terminated"
|
221
|
+
rescue AWS::Core::Resource::NotFound
|
222
|
+
# ignoring instances which disappear while we are going through them
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def releasable_elastic_ips
|
228
|
+
ti = terminatable_instances.map(&:id)
|
229
|
+
aws_ec2.elastic_ips.select { |eip| eip.instance_id.nil? || ti.include?(eip.instance_id) }
|
230
|
+
end
|
231
|
+
|
232
|
+
def deletable_security_groups
|
233
|
+
aws_ec2.security_groups.reject { |sg| security_group_in_use?(sg) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def security_group_in_use?(sg)
|
237
|
+
sg.instances.any? { |s| s.api_termination_disabled? }
|
238
|
+
end
|
239
|
+
|
240
|
+
def unattached_volumes
|
241
|
+
# only check volumes that don't have status 'deleting' and 'deleted'
|
242
|
+
aws_ec2.volumes.filter('status', %w[available creating in_use error]).
|
243
|
+
reject { |v| v.attachments.any? }
|
244
|
+
end
|
245
|
+
|
246
|
+
def select_key_pair_for_instance(name, key_pair)
|
247
|
+
if key_pair.nil?
|
248
|
+
if key_pairs.count > 1
|
249
|
+
raise "AWS key pair name unspecified for instance '#{name}', unable to select a default."
|
250
|
+
elsif key_pairs.count == 0
|
251
|
+
raise "AWS key pair name unspecified for instance '#{name}', no key pairs available to select a default."
|
252
|
+
else
|
253
|
+
key_pair = key_pairs.first.name
|
254
|
+
end
|
255
|
+
else
|
256
|
+
if key_pairs.none? { |kp| kp.name == key_pair }
|
257
|
+
raise "No such key pair '#{key_pair}' on AWS."
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
key_pair
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Bosh::Aws
|
2
|
+
class ELB
|
3
|
+
class BadCertificateError < RuntimeError;
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(credentials)
|
7
|
+
@aws_provider = AwsProvider.new(credentials)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(name, vpc, settings, certs)
|
11
|
+
subnet_names = settings['subnets']
|
12
|
+
subnet_ids = vpc.subnets.select { |k, v| subnet_names.include?(k) }.values
|
13
|
+
security_group_name = settings['security_group']
|
14
|
+
security_group_id = vpc.security_group_by_name(security_group_name).id
|
15
|
+
options = {
|
16
|
+
:listeners => [{
|
17
|
+
port: 80,
|
18
|
+
protocol: :http,
|
19
|
+
instance_port: 80,
|
20
|
+
instance_protocol: :http,
|
21
|
+
}],
|
22
|
+
:subnets => subnet_ids,
|
23
|
+
:security_groups => [security_group_id]
|
24
|
+
}
|
25
|
+
|
26
|
+
if settings['https']
|
27
|
+
domain = settings['domain']
|
28
|
+
cert_name = settings['ssl_cert']
|
29
|
+
cert = certs[cert_name]
|
30
|
+
dns_record = settings['dns_record']
|
31
|
+
|
32
|
+
certificate = Bosh::Ssl::Certificate.new(cert['private_key_path'],
|
33
|
+
cert['certificate_path'],
|
34
|
+
"#{dns_record}.#{domain}",
|
35
|
+
cert['certificate_chain_path']
|
36
|
+
).load_or_create
|
37
|
+
|
38
|
+
uploaded_cert = upload_certificate(cert_name, certificate)
|
39
|
+
|
40
|
+
options[:listeners] << {
|
41
|
+
:port => 443,
|
42
|
+
:protocol => :https,
|
43
|
+
:instance_port => 80,
|
44
|
+
:instance_protocol => :http,
|
45
|
+
# passing through 'ssl_certificate_id' is undocumented, but we're
|
46
|
+
# working around a bug filed here: https://github.com/aws/aws-sdk-ruby/issues/216
|
47
|
+
:ssl_certificate_id => uploaded_cert.arn
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
Bosh::Common.retryable(tries: 15, on: AWS::ELB::Errors::CertificateNotFound) do
|
52
|
+
aws_elb.load_balancers.create(name, options).tap do |new_elb|
|
53
|
+
new_elb.configure_health_check({
|
54
|
+
:healthy_threshold => 5,
|
55
|
+
:unhealthy_threshold => 2,
|
56
|
+
:interval => 5,
|
57
|
+
:timeout => 2,
|
58
|
+
:target => 'TCP:80'
|
59
|
+
})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def names
|
65
|
+
aws_elb.load_balancers.map(&:name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def server_certificate_names
|
69
|
+
aws_iam.server_certificates.map(&:name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_elbs
|
73
|
+
aws_elb.load_balancers.each(&:delete)
|
74
|
+
delete_server_certificates
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_server_certificates
|
78
|
+
Bosh::Common.retryable(tries: 5, sleep: 2) do
|
79
|
+
aws_iam.server_certificates.each(&:delete)
|
80
|
+
aws_iam.server_certificates.to_a.empty?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_by_name(name)
|
85
|
+
aws_elb.load_balancers.find { |lb| lb.name == name }
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
attr_reader :aws_provider
|
91
|
+
|
92
|
+
def aws_iam
|
93
|
+
aws_provider.iam
|
94
|
+
end
|
95
|
+
|
96
|
+
def aws_elb
|
97
|
+
aws_provider.elb
|
98
|
+
end
|
99
|
+
|
100
|
+
def upload_certificate(name, cert)
|
101
|
+
certificates = aws_iam.server_certificates
|
102
|
+
options = {
|
103
|
+
name: name,
|
104
|
+
certificate_body: cert.certificate,
|
105
|
+
private_key: cert.key
|
106
|
+
}
|
107
|
+
|
108
|
+
options[:certificate_chain] = cert.chain if cert.chain
|
109
|
+
|
110
|
+
begin
|
111
|
+
certificate = nil
|
112
|
+
|
113
|
+
Bosh::Common.retryable(on: AWS::IAM::Errors::MalformedCertificate, tries: 10, sleep: 2) do
|
114
|
+
begin
|
115
|
+
certificate = certificates.upload(options)
|
116
|
+
server_certificate_names.include? name
|
117
|
+
rescue AWS::IAM::Errors::EntityAlreadyExists
|
118
|
+
certificate = aws_iam.server_certificates[name]
|
119
|
+
true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
certificate
|
124
|
+
rescue AWS::IAM::Errors::MalformedCertificate => e
|
125
|
+
certificate = cert.certificate
|
126
|
+
private_key = cert.key
|
127
|
+
message = "Certificate:\n#{certificate}\n\nPrivate Key:\n#{private_key}"
|
128
|
+
raise BadCertificateError.new("Unable to upload ELB SSL Certificate: #{e.message}\n#{message}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|