bosh_cli_plugin_aws 1.5.0.pre.1113
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.
- 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
|