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,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
|