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,64 @@
|
|
1
|
+
require_relative "bootstrap"
|
2
|
+
|
3
|
+
module Bosh
|
4
|
+
module Aws
|
5
|
+
class MicroBoshBootstrap < Bootstrap
|
6
|
+
def start
|
7
|
+
cleanup_previous_deployments
|
8
|
+
generate_deployment_manifest
|
9
|
+
deploy
|
10
|
+
|
11
|
+
login("admin", "admin")
|
12
|
+
end
|
13
|
+
|
14
|
+
def deploy
|
15
|
+
Dir.chdir("deployments") do
|
16
|
+
micro = Bosh::Cli::Command::Micro.new(runner)
|
17
|
+
micro.options = self.options
|
18
|
+
micro.micro_deployment("micro")
|
19
|
+
micro.perform(micro_ami)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def manifest
|
24
|
+
unless @manifest
|
25
|
+
vpc_receipt_filename = File.expand_path("aws_vpc_receipt.yml")
|
26
|
+
route53_receipt_filename = File.expand_path("aws_route53_receipt.yml")
|
27
|
+
|
28
|
+
vpc_config = load_yaml_file(vpc_receipt_filename)
|
29
|
+
route53_config = load_yaml_file(route53_receipt_filename)
|
30
|
+
|
31
|
+
@manifest = Bosh::Aws::MicroboshManifest.new(vpc_config, route53_config, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
@manifest
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_deployment_manifest
|
38
|
+
deployment_folder = File.join("deployments", manifest.deployment_name)
|
39
|
+
|
40
|
+
FileUtils.mkdir_p deployment_folder
|
41
|
+
if File.exists?(manifest.certificate.certificate_path)
|
42
|
+
FileUtils.cp manifest.certificate.certificate_path, File.join(deployment_folder, manifest.certificate.certificate_path)
|
43
|
+
end
|
44
|
+
if File.exists?(manifest.certificate.key_path)
|
45
|
+
FileUtils.cp manifest.certificate.key_path, File.join(deployment_folder, manifest.certificate.key_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
Dir.chdir(deployment_folder) do
|
49
|
+
write_yaml(manifest, manifest.file_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def cleanup_previous_deployments
|
54
|
+
rm_files = %w[bosh-deployments.yml micro bosh-registry.log]
|
55
|
+
rm_files.each { |file| FileUtils.rm_rf File.join("deployments", file) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def micro_ami
|
59
|
+
ENV["BOSH_OVERRIDE_MICRO_STEMCELL_AMI"] ||
|
60
|
+
Net::HTTP.get("#{AWS_JENKINS_BUCKET}.s3.amazonaws.com", "/last_successful-bosh-stemcell-aws_ami_us-east-1").strip
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'common/ssl'
|
2
|
+
|
3
|
+
module Bosh
|
4
|
+
module Aws
|
5
|
+
class MicroboshManifest
|
6
|
+
attr_reader :vpc_receipt, :route53_receipt, :hm_director_password, :hm_director_user
|
7
|
+
|
8
|
+
def initialize(vpc_receipt, route53_receipt, options={})
|
9
|
+
@vpc_receipt = vpc_receipt
|
10
|
+
@route53_receipt = route53_receipt
|
11
|
+
@hm_director_user = options[:hm_director_user]
|
12
|
+
@hm_director_password = options[:hm_director_password]
|
13
|
+
end
|
14
|
+
|
15
|
+
def file_name
|
16
|
+
"micro_bosh.yml"
|
17
|
+
end
|
18
|
+
|
19
|
+
def deployment_name
|
20
|
+
"micro"
|
21
|
+
end
|
22
|
+
|
23
|
+
def vpc_config
|
24
|
+
vpc_receipt['original_configuration']
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
vpc_config['name'] || warning('Missing name field')
|
29
|
+
end
|
30
|
+
|
31
|
+
def vip
|
32
|
+
route53_receipt['elastic_ips']['micro']['ips'][0] || warning('Missing vip field')
|
33
|
+
end
|
34
|
+
|
35
|
+
def subnet
|
36
|
+
vpc_receipt['vpc']['subnets']['bosh1'] || warning('Missing bosh subnet field')
|
37
|
+
end
|
38
|
+
|
39
|
+
def availability_zone
|
40
|
+
vpc_config['vpc']['subnets']['bosh1']['availability_zone'] || warning('Missing availability zone in vpc.subnets.bosh')
|
41
|
+
end
|
42
|
+
|
43
|
+
def access_key_id
|
44
|
+
vpc_config['aws']['access_key_id'] || warning('Missing aws access_key_id field')
|
45
|
+
end
|
46
|
+
|
47
|
+
def secret_access_key
|
48
|
+
vpc_config['aws']['secret_access_key'] || warning('Missing aws secret_access_key field')
|
49
|
+
end
|
50
|
+
|
51
|
+
def region
|
52
|
+
vpc_config['aws']['region'] || warning('Missing aws region field')
|
53
|
+
end
|
54
|
+
|
55
|
+
def key_pair_name
|
56
|
+
vpc_config['key_pairs'].any? ? vpc_config['key_pairs'].keys[0] : warning("Missing key_pairs field, must have at least 1 keypair")
|
57
|
+
end
|
58
|
+
|
59
|
+
def private_key_path
|
60
|
+
vpc_config['key_pairs'].any? ? vpc_config['key_pairs'].values[0].gsub(/\.pub$/, '') : warning("Missing key_pairs field, must have at least 1 keypair")
|
61
|
+
end
|
62
|
+
|
63
|
+
def compiled_package_cache?
|
64
|
+
!!vpc_config['compiled_package_cache']
|
65
|
+
end
|
66
|
+
|
67
|
+
def cache_access_key_id
|
68
|
+
vpc_config['compiled_package_cache']['access_key_id'] || warning('Missing compiled_package_cache access_key_id field')
|
69
|
+
end
|
70
|
+
|
71
|
+
def cache_secret_access_key
|
72
|
+
vpc_config['compiled_package_cache']['secret_access_key'] || warning('Missing compiled_package_cache secret_access_key field')
|
73
|
+
end
|
74
|
+
|
75
|
+
def cache_bucket_name
|
76
|
+
vpc_config['compiled_package_cache']['bucket_name'] || warning('Missing compiled_package_cache bucket_name field')
|
77
|
+
end
|
78
|
+
|
79
|
+
def director_ssl_key
|
80
|
+
certificate.key.gsub("\n", "\n ")
|
81
|
+
end
|
82
|
+
|
83
|
+
def director_ssl_cert
|
84
|
+
certificate.certificate.gsub("\n", "\n ")
|
85
|
+
end
|
86
|
+
|
87
|
+
def certificate
|
88
|
+
@cert if @cert
|
89
|
+
key_path = director_ssl['private_key_path'] || 'director.key'
|
90
|
+
cert_path = director_ssl['certificate_path'] || 'director.pem'
|
91
|
+
@cert = Bosh::Ssl::Certificate.new(key_path, cert_path, "*.#{vpc_config['vpc']['domain']}").load_or_create
|
92
|
+
end
|
93
|
+
|
94
|
+
def director_ssl
|
95
|
+
ssl_certs['director_cert'] || {}
|
96
|
+
end
|
97
|
+
|
98
|
+
def ssl_certs
|
99
|
+
vpc_config['ssl_certs'] || {}
|
100
|
+
end
|
101
|
+
|
102
|
+
# RSpec overloads to_yaml when you set up expectations on an object;
|
103
|
+
# so to_y is just a way to get directly at the to_yaml implementation without fighting RSpec.
|
104
|
+
def to_y
|
105
|
+
ERB.new(File.read(get_template("micro_bosh.yml.erb"))).result(binding)
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_yaml
|
109
|
+
to_y
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_template(template)
|
113
|
+
File.expand_path("../../../templates/#{template}", __FILE__)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
class Migration
|
4
|
+
|
5
|
+
attr_reader :s3, :elb, :ec2, :rds, :route53, :logger, :config
|
6
|
+
|
7
|
+
def initialize(config, receipt_bucket_name)
|
8
|
+
@config = config
|
9
|
+
@receipt_bucket_name = receipt_bucket_name
|
10
|
+
aws_config = config['aws']
|
11
|
+
@s3 = S3.new(aws_config)
|
12
|
+
@elb = ELB.new(aws_config)
|
13
|
+
@ec2 = EC2.new(aws_config)
|
14
|
+
@rds = RDS.new(aws_config)
|
15
|
+
@route53 = Route53.new(aws_config)
|
16
|
+
@logger = Bosh::Clouds::Config.logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
say "Executing migration #{self.class.name}"
|
21
|
+
execute
|
22
|
+
end
|
23
|
+
|
24
|
+
def save_receipt(receipt_name, receipt)
|
25
|
+
receipt_yaml = YAML.dump(receipt)
|
26
|
+
s3.upload_to_bucket(@receipt_bucket_name, "receipts/#{receipt_name}.yml", receipt_yaml)
|
27
|
+
|
28
|
+
File.open("#{receipt_name}.yml", "w+") do |f|
|
29
|
+
f.write(receipt_yaml)
|
30
|
+
end
|
31
|
+
|
32
|
+
say "details in S3 receipt: #{receipt_name} and file: #{receipt_name}.yml"
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_receipt(receipt_name)
|
36
|
+
YAML.load(s3.fetch_object_contents(@receipt_bucket_name, "receipts/#{receipt_name}.yml"))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
module MigrationHelper
|
4
|
+
class Template
|
5
|
+
attr_reader :timestamp_string, :name, :class_name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@timestamp_string = Time.new.getutc.strftime("%Y%m%d%H%M%S")
|
9
|
+
@name = name
|
10
|
+
@class_name = MigrationHelper.to_class_name(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def file_prefix
|
14
|
+
"#{timestamp_string}_#{name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def render(template_name = "aws_migration")
|
18
|
+
template_file_path = File.expand_path("../../templates/#{template_name}.erb", File.dirname(__FILE__))
|
19
|
+
template = ERB.new(File.new(template_file_path).read(), 0, '<>%-')
|
20
|
+
template.result(binding)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class RdsDb
|
25
|
+
|
26
|
+
attr_accessor :instance_id, :receipt, :tag, :subnet_ids
|
27
|
+
|
28
|
+
def initialize(args = {})
|
29
|
+
vpc_receipt = args.fetch(:vpc_receipt).fetch('vpc')
|
30
|
+
vpc_subnets = vpc_receipt.fetch('subnets')
|
31
|
+
@rds_db_conf = args.fetch(:rds_db_conf)
|
32
|
+
@instance_id = @rds_db_conf.fetch('instance')
|
33
|
+
@tag = @rds_db_conf.fetch('tag')
|
34
|
+
@subnet_ids = @rds_db_conf.fetch('subnets').map { |s| vpc_subnets[s] }
|
35
|
+
@vpc_id = vpc_receipt.fetch('id')
|
36
|
+
end
|
37
|
+
|
38
|
+
def create!
|
39
|
+
return if RdsDb.aws_rds.database_exists? @instance_id
|
40
|
+
creation_opts = [@instance_id, @subnet_ids, @vpc_id]
|
41
|
+
|
42
|
+
if @rds_db_conf["aws_creation_options"]
|
43
|
+
creation_opts << @rds_db_conf["aws_creation_options"]
|
44
|
+
end
|
45
|
+
|
46
|
+
@response = RdsDb.aws_rds.create_database(*creation_opts)
|
47
|
+
output_rds_properties
|
48
|
+
end
|
49
|
+
|
50
|
+
def output_rds_properties
|
51
|
+
RdsDb.receipt["deployment_manifest"] ||= {}
|
52
|
+
RdsDb.receipt["deployment_manifest"]["properties"] ||= {}
|
53
|
+
RdsDb.receipt["deployment_manifest"]["properties"][@instance_id] = {
|
54
|
+
"db_scheme" => @response[:engine],
|
55
|
+
"roles" => [
|
56
|
+
{
|
57
|
+
"tag" => "admin",
|
58
|
+
"name" => @response[:master_username],
|
59
|
+
"password" => @response[:master_user_password]
|
60
|
+
}
|
61
|
+
],
|
62
|
+
"databases" => [
|
63
|
+
{
|
64
|
+
"tag" => @tag,
|
65
|
+
"name" => @instance_id
|
66
|
+
}
|
67
|
+
]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_receipt
|
72
|
+
return unless RdsDb.deployment_properties[instance_id]
|
73
|
+
|
74
|
+
db_instance = RdsDb.aws_rds.database(instance_id)
|
75
|
+
RdsDb.receipt['deployment_manifest']['properties'][instance_id].merge!(
|
76
|
+
'address' => db_instance.endpoint_address,
|
77
|
+
'port' => db_instance.endpoint_port
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.deployment_properties
|
82
|
+
RdsDb.receipt.fetch('deployment_manifest', {}).fetch('properties', {})
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.aws_rds
|
86
|
+
@aws_rds
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.aws_rds=(arg)
|
90
|
+
@aws_rds = arg
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.receipt
|
94
|
+
@receipt ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.was_rds_eventually_available?
|
98
|
+
return true if all_rds_instances_available?(:silent => true)
|
99
|
+
(1..540).any? do |attempt| # wait up to 3 hours, checking every 20s
|
100
|
+
Kernel.sleep 20
|
101
|
+
all_rds_instances_available?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.all_rds_instances_available?(opts = {})
|
106
|
+
silent = opts[:silent]
|
107
|
+
say("checking rds status...") unless silent
|
108
|
+
aws_rds.databases.all? do |db_instance|
|
109
|
+
say(" #{db_instance.db_name} #{db_instance.db_instance_status} #{db_instance.endpoint_address}") unless silent
|
110
|
+
!db_instance.endpoint_address.nil?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.aws_migration_directory
|
117
|
+
File.expand_path("../../migrations", File.dirname(__FILE__))
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.aws_spec_migration_directory
|
121
|
+
File.expand_path("../../spec/migrations", File.dirname(__FILE__))
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.generate_migration_file(name)
|
125
|
+
template = Template.new(name)
|
126
|
+
|
127
|
+
filename = "#{aws_migration_directory}/#{template.file_prefix}.rb"
|
128
|
+
spec_filename = "#{aws_spec_migration_directory}/#{template.file_prefix}_spec.rb"
|
129
|
+
|
130
|
+
puts "Creating #{filename} and #{spec_filename}"
|
131
|
+
|
132
|
+
File.open(filename, 'w+') { |f| f.write(template.render) }
|
133
|
+
File.open(spec_filename, 'w+') { |f| f.write(template.render("aws_migration_spec")) }
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.to_class_name(name)
|
137
|
+
name.split('_').map(&:capitalize).join('')
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.all_rds_instances_available?(rds, opts = {})
|
141
|
+
silent = opts[:silent]
|
142
|
+
say("checking rds status...") unless silent
|
143
|
+
rds.databases.all? do |db_instance|
|
144
|
+
say(" #{db_instance.db_name} #{db_instance.db_instance_status} #{db_instance.endpoint_address}") unless silent
|
145
|
+
!db_instance.endpoint_address.nil?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Bosh
|
2
|
+
module Aws
|
3
|
+
class Migrator
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
@migration_path = MigrationHelper.aws_migration_directory
|
8
|
+
end
|
9
|
+
|
10
|
+
def migrate
|
11
|
+
return unless needs_migration?
|
12
|
+
|
13
|
+
run_migrations(pending_migrations)
|
14
|
+
end
|
15
|
+
|
16
|
+
def migrate_version(version)
|
17
|
+
return unless needs_migration?
|
18
|
+
|
19
|
+
migration_to_run = pending_migrations.detect do |migration|
|
20
|
+
migration.version == version.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
run_migrations([migration_to_run])
|
24
|
+
end
|
25
|
+
|
26
|
+
def pending_migrations
|
27
|
+
migrations - environment_migrations
|
28
|
+
end
|
29
|
+
|
30
|
+
def migrations
|
31
|
+
@migrations ||= load_migrations
|
32
|
+
end
|
33
|
+
|
34
|
+
def environment_migrations
|
35
|
+
@environment_migrations ||= load_migrations_for_env
|
36
|
+
end
|
37
|
+
|
38
|
+
def needs_migration?
|
39
|
+
ensure_bucket_exists
|
40
|
+
environment_migrations.nil? || environment_migrations != migrations
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :migration_path
|
46
|
+
|
47
|
+
def aws_s3
|
48
|
+
@aws_s3 ||= Bosh::Aws::S3.new(@config['aws'])
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_bucket_exists
|
52
|
+
unless aws_s3.bucket_exists?(bucket_name)
|
53
|
+
aws_s3.create_bucket(bucket_name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def s3_safe_vpc_domain
|
58
|
+
@config['vpc']['domain'].gsub('.', '-')
|
59
|
+
end
|
60
|
+
|
61
|
+
def bucket_name
|
62
|
+
if aws_s3.bucket_exists?("#{@config['name']}-bosh-artifacts")
|
63
|
+
"#{@config['name']}-bosh-artifacts"
|
64
|
+
else
|
65
|
+
"#{s3_safe_vpc_domain}-bosh-artifacts"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def migrations_name
|
70
|
+
"aws_migrations/migrations.yaml"
|
71
|
+
end
|
72
|
+
|
73
|
+
def run_migrations(migrations_to_run)
|
74
|
+
migrations_to_run.each do |migration|
|
75
|
+
migration.load_class.new(@config, bucket_name).run
|
76
|
+
record_migration(migration)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def load_migrations
|
81
|
+
Dir.glob(File.join(migration_path, "*.rb")).collect do |migration_file_path|
|
82
|
+
version, name = migration_file_path.scan(/([0-9]+)_([_a-z0-9]*).rb\z/).first
|
83
|
+
MigrationProxy.new(name,version.to_i)
|
84
|
+
end.sort
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_migrations_for_env
|
88
|
+
yaml_file = aws_s3.fetch_object_contents(bucket_name, migrations_name) || ""
|
89
|
+
migrations = YAML.load(yaml_file) || []
|
90
|
+
|
91
|
+
migrations.collect do |migration_yaml|
|
92
|
+
MigrationProxy.new(migration_yaml['name'],migration_yaml['version'].to_i)
|
93
|
+
end.sort
|
94
|
+
end
|
95
|
+
|
96
|
+
def record_migration(migration)
|
97
|
+
environment_migrations << migration
|
98
|
+
migration_yaml = YAML.dump(environment_migrations.collect do |m|
|
99
|
+
m.to_hash
|
100
|
+
end)
|
101
|
+
aws_s3.upload_to_bucket(bucket_name, migrations_name, migration_yaml)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class MigrationProxy
|
106
|
+
include Comparable
|
107
|
+
|
108
|
+
attr_reader :name, :version
|
109
|
+
|
110
|
+
def initialize(name, version)
|
111
|
+
@name = name
|
112
|
+
@version = version.to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
def load_class
|
116
|
+
require File.join(MigrationHelper.aws_migration_directory, "#{version}_#{name}")
|
117
|
+
Object.const_get(MigrationHelper.to_class_name(name))
|
118
|
+
end
|
119
|
+
|
120
|
+
def eql?(other)
|
121
|
+
version == other.version
|
122
|
+
end
|
123
|
+
|
124
|
+
def <=>(other)
|
125
|
+
version <=> other.version
|
126
|
+
end
|
127
|
+
|
128
|
+
def hash
|
129
|
+
(name.to_s + version.to_s).hash
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_hash
|
133
|
+
{"name" => name, "version" => version}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module Bosh
|
4
|
+
module Aws
|
5
|
+
class RDS
|
6
|
+
DEFAULT_RDS_OPTIONS = {
|
7
|
+
:allocated_storage => 5,
|
8
|
+
:db_instance_class => "db.t1.micro",
|
9
|
+
:engine => "mysql",
|
10
|
+
:multi_az => true,
|
11
|
+
:engine_version => "5.5.31"
|
12
|
+
}
|
13
|
+
DEFAULT_RDS_PROTOCOL = :tcp
|
14
|
+
DEFAULT_MYSQL_PORT = 3306
|
15
|
+
|
16
|
+
def initialize(credentials)
|
17
|
+
@credentials = credentials
|
18
|
+
@aws_provider = AwsProvider.new(@credentials)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_database(name, subnet_ids, vpc_id, options = {})
|
22
|
+
create_db_parameter_group('utf8')
|
23
|
+
vpc = Bosh::Aws::VPC.find(Bosh::Aws::EC2.new(@credentials), vpc_id)
|
24
|
+
create_vpc_db_security_group(vpc, name) if vpc.security_group_by_name(name).nil?
|
25
|
+
create_subnet_group(name, subnet_ids) unless subnet_group_exists?(name)
|
26
|
+
|
27
|
+
# symbolize options keys
|
28
|
+
options = options.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo }
|
29
|
+
|
30
|
+
creation_options = DEFAULT_RDS_OPTIONS.merge(options)
|
31
|
+
creation_options[:db_instance_identifier] = name
|
32
|
+
creation_options[:db_name] ||= name
|
33
|
+
creation_options[:vpc_security_group_ids] = [vpc.security_group_by_name(name).id]
|
34
|
+
creation_options[:db_subnet_group_name] = name
|
35
|
+
creation_options[:db_parameter_group_name] = 'utf8'
|
36
|
+
creation_options[:master_username] ||= generate_user
|
37
|
+
creation_options[:master_user_password] ||= generate_password
|
38
|
+
response = aws_rds_client.create_db_instance(creation_options)
|
39
|
+
response.data.merge(:master_user_password => creation_options[:master_user_password])
|
40
|
+
end
|
41
|
+
|
42
|
+
def databases
|
43
|
+
aws_rds.db_instances
|
44
|
+
end
|
45
|
+
|
46
|
+
def database(name)
|
47
|
+
databases.find { |v| v.id == name }
|
48
|
+
end
|
49
|
+
|
50
|
+
def database_exists?(name)
|
51
|
+
!database(name).nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_databases
|
55
|
+
databases.each { |db| db.delete(skip_final_snapshot: true) unless db.db_instance_status == "deleting" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def database_names
|
59
|
+
databases.inject({}) do |memo, db_instance|
|
60
|
+
memo[db_instance.id] = db_instance.name
|
61
|
+
memo
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def subnet_group_exists?(name)
|
66
|
+
aws_rds_client.describe_db_subnet_groups(:db_subnet_group_name => name)
|
67
|
+
return true
|
68
|
+
rescue AWS::RDS::Errors::DBSubnetGroupNotFoundFault
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
def db_parameter_group_names
|
73
|
+
charset = 'utf8'
|
74
|
+
param_names = %w(character_set_server
|
75
|
+
character_set_client
|
76
|
+
character_set_results
|
77
|
+
character_set_database
|
78
|
+
character_set_connection)
|
79
|
+
|
80
|
+
params = param_names.map{|param_name| {:parameter_name => param_name,
|
81
|
+
:parameter_value => charset,
|
82
|
+
:apply_method => 'immediate'}}
|
83
|
+
|
84
|
+
params << {:parameter_name => 'collation_connection',
|
85
|
+
:parameter_value => 'utf8_unicode_ci',
|
86
|
+
:apply_method => 'immediate'}
|
87
|
+
params << {:parameter_name => 'collation_server',
|
88
|
+
:parameter_value => 'utf8_unicode_ci',
|
89
|
+
:apply_method => 'immediate'}
|
90
|
+
params
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_db_parameter_group(name)
|
94
|
+
aws_rds_client.describe_db_parameter_groups(:db_parameter_group_name => name)
|
95
|
+
rescue AWS::RDS::Errors::DBParameterGroupNotFound
|
96
|
+
aws_rds_client.create_db_parameter_group(:db_parameter_group_name => name,
|
97
|
+
:db_parameter_group_family => 'mysql5.5', :description => name)
|
98
|
+
aws_rds_client.modify_db_parameter_group(:db_parameter_group_name => name,
|
99
|
+
:parameters => db_parameter_group_names)
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete_db_parameter_group(name)
|
103
|
+
aws_rds_client.describe_db_parameter_groups(:db_parameter_group_name => name)
|
104
|
+
aws_rds_client.delete_db_parameter_group(:db_parameter_group_name => name)
|
105
|
+
rescue AWS::RDS::Errors::DBParameterGroupNotFound
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_subnet_group(name, subnet_ids)
|
109
|
+
aws_rds_client.create_db_subnet_group(
|
110
|
+
:db_subnet_group_name => name,
|
111
|
+
:db_subnet_group_description => name,
|
112
|
+
:subnet_ids => subnet_ids
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def subnet_group_names
|
117
|
+
aws_rds_client.describe_db_subnet_groups.data[:db_subnet_groups].map { |sg| sg[:db_subnet_group_name] }
|
118
|
+
end
|
119
|
+
|
120
|
+
def delete_subnet_group(name)
|
121
|
+
aws_rds_client.delete_db_subnet_group(:db_subnet_group_name => name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def delete_subnet_groups
|
125
|
+
subnet_group_names.each { |name| delete_subnet_group(name) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def create_vpc_db_security_group(vpc, name)
|
129
|
+
group_spec = {
|
130
|
+
"name" => name,
|
131
|
+
"ingress" => [
|
132
|
+
{
|
133
|
+
"ports" => DEFAULT_MYSQL_PORT,
|
134
|
+
"protocol" => DEFAULT_RDS_PROTOCOL,
|
135
|
+
"sources" => vpc.cidr_block,
|
136
|
+
},
|
137
|
+
],
|
138
|
+
}
|
139
|
+
|
140
|
+
vpc.create_security_groups([group_spec])
|
141
|
+
end
|
142
|
+
|
143
|
+
def security_group_names
|
144
|
+
aws_rds_client.describe_db_security_groups.data[:db_security_groups].map { |sg| sg[:db_security_group_name] }
|
145
|
+
end
|
146
|
+
|
147
|
+
def delete_security_group(name)
|
148
|
+
aws_rds_client.delete_db_security_group(:db_security_group_name => name)
|
149
|
+
end
|
150
|
+
|
151
|
+
def delete_security_groups
|
152
|
+
security_group_names.each do |name|
|
153
|
+
delete_security_group(name) unless name == "default"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def aws_rds
|
158
|
+
aws_provider.rds
|
159
|
+
end
|
160
|
+
|
161
|
+
def aws_rds_client
|
162
|
+
aws_provider.rds_client
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
attr_reader :aws_provider
|
168
|
+
|
169
|
+
def generate_user
|
170
|
+
generate_credential("u", 7)
|
171
|
+
end
|
172
|
+
|
173
|
+
def generate_password
|
174
|
+
generate_credential("p", 16)
|
175
|
+
end
|
176
|
+
|
177
|
+
def generate_credential(prefix, length)
|
178
|
+
"#{prefix}#{SecureRandom.hex(length)}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|