enscalator 0.4.0.pre.alpha.pre.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +9 -0
- data/.rubocop_todo.yml +59 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/Rakefile +43 -0
- data/bin/console +11 -0
- data/bin/setup +7 -0
- data/enscalator.gemspec +57 -0
- data/exe/enscalator +13 -0
- data/lib/enscalator/core/cf_parameters.rb +146 -0
- data/lib/enscalator/core/cf_resources.rb +225 -0
- data/lib/enscalator/core/instance_type.rb +205 -0
- data/lib/enscalator/core/network_config.rb +21 -0
- data/lib/enscalator/core.rb +10 -0
- data/lib/enscalator/enapp.rb +248 -0
- data/lib/enscalator/helpers/dns.rb +62 -0
- data/lib/enscalator/helpers/stack.rb +107 -0
- data/lib/enscalator/helpers/sub_process.rb +72 -0
- data/lib/enscalator/helpers/wrappers.rb +55 -0
- data/lib/enscalator/helpers.rb +127 -0
- data/lib/enscalator/plugins/amazon_linux.rb +93 -0
- data/lib/enscalator/plugins/auto_scale.rb +80 -0
- data/lib/enscalator/plugins/core_os.rb +88 -0
- data/lib/enscalator/plugins/couchbase.rb +98 -0
- data/lib/enscalator/plugins/debian.rb +71 -0
- data/lib/enscalator/plugins/elastic_beanstalk.rb +74 -0
- data/lib/enscalator/plugins/elasticache.rb +168 -0
- data/lib/enscalator/plugins/elasticsearch_amazon.rb +75 -0
- data/lib/enscalator/plugins/elasticsearch_bitnami.rb +198 -0
- data/lib/enscalator/plugins/elasticsearch_opsworks.rb +225 -0
- data/lib/enscalator/plugins/elb.rb +139 -0
- data/lib/enscalator/plugins/nat_gateway.rb +71 -0
- data/lib/enscalator/plugins/rds.rb +141 -0
- data/lib/enscalator/plugins/redis.rb +38 -0
- data/lib/enscalator/plugins/rethink_db.rb +21 -0
- data/lib/enscalator/plugins/route53.rb +143 -0
- data/lib/enscalator/plugins/ubuntu.rb +85 -0
- data/lib/enscalator/plugins/user-data/elasticsearch +367 -0
- data/lib/enscalator/plugins/vpc_peering_connection.rb +48 -0
- data/lib/enscalator/plugins.rb +30 -0
- data/lib/enscalator/rich_template_dsl.rb +209 -0
- data/lib/enscalator/templates/vpc_peering.rb +112 -0
- data/lib/enscalator/templates.rb +20 -0
- data/lib/enscalator/version.rb +5 -0
- data/lib/enscalator/vpc.rb +11 -0
- data/lib/enscalator/vpc_with_nat_gateway.rb +311 -0
- data/lib/enscalator/vpc_with_nat_instance.rb +402 -0
- data/lib/enscalator.rb +103 -0
- metadata +427 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Amazon Linux appliance
|
4
|
+
module AmazonLinux
|
5
|
+
class << self
|
6
|
+
# Supported storage types in AWS
|
7
|
+
STORAGE = [:ebs, :'instance-store']
|
8
|
+
|
9
|
+
# Supported EBS volume types
|
10
|
+
EBS_VOLUME_TYPES = [:standard, :gp2]
|
11
|
+
|
12
|
+
# Supported Debian image architectures
|
13
|
+
ARCH = [:x86_64, :i386]
|
14
|
+
|
15
|
+
# @param [Symbol, String] region name
|
16
|
+
# @param [Symbol, String] release a codename or version number
|
17
|
+
# @param [Symbol] storage storage kind
|
18
|
+
# @param [Symbol] arch architecture
|
19
|
+
# @param [Symbol] ebs_type (:standard or :gp2)
|
20
|
+
# @param [Hash] filters search filters
|
21
|
+
# @raise [ArgumentError] if storage is nil, empty or not one of supported values
|
22
|
+
# @raise [ArgumentError] if arch is nil, empty or not one of supported values
|
23
|
+
# @return [String] first ami-id found for the query
|
24
|
+
def get_ami(region:, release: '2015.09.1', storage: :ebs, arch: :x86_64, ebs_type: :gp2, filters: {})
|
25
|
+
fail ArgumentError, "storage can only be one of #{STORAGE}" unless STORAGE.include? storage
|
26
|
+
fail ArgumentError, "arch can only be one of #{ARCH}" unless ARCH.include? arch
|
27
|
+
fail ArgumentError, "ebs_type can only be one of #{EBS_VOLUME_TYPES}" unless EBS_VOLUME_TYPES.include? ebs_type
|
28
|
+
|
29
|
+
client = Aws::EC2::Client.new(region: region)
|
30
|
+
|
31
|
+
resp = client.describe_images(
|
32
|
+
owners: ['amazon'],
|
33
|
+
filters: [
|
34
|
+
{
|
35
|
+
name: 'name',
|
36
|
+
values: %W(amzn-ami-hvm-#{release}* amzn-ami-pv-#{release}*)
|
37
|
+
},
|
38
|
+
{
|
39
|
+
name: 'root-device-type',
|
40
|
+
values: [storage.to_s]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
name: 'architecture',
|
44
|
+
values: [arch.to_s]
|
45
|
+
}
|
46
|
+
] + filters_map_to_array(filters)
|
47
|
+
)
|
48
|
+
|
49
|
+
err_msg = format('Could not find any Linux Amazon Ami that fits the criteria: %s, %s, %s, %s, %s, %s',
|
50
|
+
region, release, storage, arch, ebs_type, filters)
|
51
|
+
fail StandardError, err_msg unless resp.images
|
52
|
+
|
53
|
+
images = resp.images.sort_by(&:creation_date).reverse
|
54
|
+
images = images.select { |i| i.block_device_mappings.first.ebs.volume_type == ebs_type.to_s } if storage == :ebs
|
55
|
+
images.first.image_id
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def filters_map_to_array(filters)
|
61
|
+
filters.map do |k, v|
|
62
|
+
{
|
63
|
+
name: k.to_s,
|
64
|
+
values: v.is_a?(Array) ? v : [v.to_s]
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create AMI id parameter for an Amazon linux instance
|
71
|
+
#
|
72
|
+
# @param [Symbol, String] region name
|
73
|
+
# @param [Symbol, String] release a codename or version number
|
74
|
+
# @param [Symbol] storage storage kind (ebs or instance_store)
|
75
|
+
# @param [String] arch architecture (x86_64 or i386)
|
76
|
+
# @param [Symbol] ebs_type (:standard or :gp2)
|
77
|
+
# @param [Hash] filters filters for the search
|
78
|
+
def amazon_linux_init(region: self.region,
|
79
|
+
release: '2015.09.1',
|
80
|
+
storage: :ebs,
|
81
|
+
arch: :x86_64,
|
82
|
+
ebs_type: :gp2,
|
83
|
+
filters: {})
|
84
|
+
parameter_ami 'AmazonLinux', AmazonLinux.get_ami(region: region,
|
85
|
+
release: release,
|
86
|
+
storage: storage,
|
87
|
+
arch: arch,
|
88
|
+
ebs_type: ebs_type,
|
89
|
+
filters: filters)
|
90
|
+
end
|
91
|
+
end # class AmazonLinux
|
92
|
+
end # module Plugins
|
93
|
+
end # module Enscalator
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Auto scaling group plugin
|
4
|
+
module AutoScale
|
5
|
+
# Create new auto scaling group
|
6
|
+
#
|
7
|
+
# @param [String] image_id image id that will be used to launch instance
|
8
|
+
# @param [String] auto_scale_name auto scaling group name (default: stack name)
|
9
|
+
# @param [Hash] launch_config_props dictionary that is used to overwrite default launch configuration settings
|
10
|
+
# Reference: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-launchconfig.html
|
11
|
+
# @param [Hash] auto_scale_props dictionary that is used to overwrite default auto scaling group settings
|
12
|
+
# Reference: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html
|
13
|
+
# @param [Array] auto_scale_tags list of tags that is added to auto scaling group tags
|
14
|
+
# @return [String] auto scaling group resource name
|
15
|
+
def auto_scale_init(image_id,
|
16
|
+
auto_scale_name: nil,
|
17
|
+
launch_config_props: {},
|
18
|
+
auto_scale_props: {},
|
19
|
+
auto_scale_tags: [])
|
20
|
+
|
21
|
+
name = auto_scale_name || app_template_name
|
22
|
+
auto_scale_name = "#{name}AutoScale"
|
23
|
+
launch_config_resource_name = "#{name}LaunchConfig"
|
24
|
+
auto_scale_resource_name = auto_scale_name
|
25
|
+
auto_scale_key_name = gen_ssh_key_name auto_scale_name.underscore, region, stack_name
|
26
|
+
|
27
|
+
pre_run do
|
28
|
+
create_ssh_key auto_scale_key_name,
|
29
|
+
region,
|
30
|
+
force_create: false
|
31
|
+
end
|
32
|
+
|
33
|
+
resource launch_config_resource_name,
|
34
|
+
Type: 'AWS::AutoScaling::LaunchConfiguration',
|
35
|
+
Properties: {
|
36
|
+
ImageId: image_id,
|
37
|
+
InstanceType: 'm3.medium',
|
38
|
+
KeyName: auto_scale_key_name,
|
39
|
+
AssociatePublicIpAddress: false,
|
40
|
+
SecurityGroups: [ref_private_security_group, ref_application_security_group]
|
41
|
+
}.merge(launch_config_props)
|
42
|
+
|
43
|
+
if auto_scale_props.key?(:Tags)
|
44
|
+
warn('Do not use auto_scale_props to set Tags, auto_scale_tags is available for that purpose')
|
45
|
+
auto_scale_props.delete_if { |k, _| k == :Tags }
|
46
|
+
end
|
47
|
+
|
48
|
+
auto_scale_current_tags = [
|
49
|
+
{
|
50
|
+
Key: 'Name',
|
51
|
+
Value: auto_scale_name,
|
52
|
+
PropagateAtLaunch: true
|
53
|
+
}
|
54
|
+
].concat(auto_scale_tags)
|
55
|
+
|
56
|
+
auto_scale_current_properties = {
|
57
|
+
AvailabilityZones: availability_zones.values,
|
58
|
+
VPCZoneIdentifier: ref_application_subnets,
|
59
|
+
LaunchConfigurationName: ref(launch_config_resource_name),
|
60
|
+
MinSize: 0,
|
61
|
+
MaxSize: 1,
|
62
|
+
DesiredCapacity: 1,
|
63
|
+
Tags: auto_scale_current_tags
|
64
|
+
}.merge(auto_scale_props)
|
65
|
+
|
66
|
+
resource auto_scale_resource_name,
|
67
|
+
Type: 'AWS::AutoScaling::AutoScalingGroup',
|
68
|
+
Properties: auto_scale_current_properties
|
69
|
+
|
70
|
+
# return resource name
|
71
|
+
auto_scale_resource_name
|
72
|
+
end
|
73
|
+
|
74
|
+
# Callback to get name of the class which included this module
|
75
|
+
def self.included(klass)
|
76
|
+
send(:define_method, :app_template_name) { "#{klass.name.demodulize}" }
|
77
|
+
end
|
78
|
+
end # module AutoScale
|
79
|
+
end # module Plugins
|
80
|
+
end # module Enscalator
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# CoreOS appliance
|
4
|
+
module CoreOS
|
5
|
+
class << self
|
6
|
+
# CoreOS Release channels
|
7
|
+
# @see https://coreos.com/releases
|
8
|
+
CHANNELS = [:alpha, :beta, :stable].freeze
|
9
|
+
|
10
|
+
# Get CoreOS mapping for specific version from specific channel (stable, beta or alpha)
|
11
|
+
#
|
12
|
+
# @param [Symbol] channel channel identifier
|
13
|
+
# @param [String] tag specific version release tag
|
14
|
+
# @return [Hash] CoreOS mapping for specific version and channel
|
15
|
+
# (if version tag is not given, returns the most latest version number)
|
16
|
+
def get_channel_version(channel: :stable, tag: nil)
|
17
|
+
fail ArgumentError, "channel can only be one of #{CHANNELS}" unless CHANNELS.include? channel
|
18
|
+
base_url = "http://#{channel}.release.core-os.net/amd64-usr"
|
19
|
+
fetch_mapping(base_url, tag)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get CoreOS mapping for specific version regardless of its release channel
|
23
|
+
#
|
24
|
+
# @param [String] tag version tag
|
25
|
+
# @return [Hash] CoreOS mapping for specific version
|
26
|
+
# (if version tag is not given, returns the most latest version number)
|
27
|
+
def get_specific_version(tag: nil)
|
28
|
+
urls = CHANNELS.map { |c| "http://#{c}.release.core-os.net/amd64-usr" }
|
29
|
+
mapping = nil
|
30
|
+
urls.each do |u|
|
31
|
+
mapping ||= fetch_mapping(u, tag)
|
32
|
+
end
|
33
|
+
mapping
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Fetch CoreOS region/virtualization/ami mapping
|
39
|
+
#
|
40
|
+
# @param [String] base_url url excluding version number and mapping file
|
41
|
+
# @param [String] tag specific version release tag
|
42
|
+
# @return [Hash] CoreOS mapping
|
43
|
+
def fetch_mapping(base_url, tag)
|
44
|
+
fail ArgumentError, 'url cannot be empty or nil' if base_url.blank?
|
45
|
+
versions = fetch_versions(base_url.ends_with?('/') ? base_url : base_url + '/')
|
46
|
+
version = if tag && !tag.empty?
|
47
|
+
versions.find { |v| v == Semantic::Version.new(tag) }.to_s
|
48
|
+
else
|
49
|
+
versions.sort.last.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
images = open([base_url, version, 'coreos_production_ami_all.json'].join('/')) { |f| f.read }
|
53
|
+
|
54
|
+
json = JSON.parse(images) if images
|
55
|
+
parse_raw_mapping(json)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Make request to CoreOS release pages, parse response and make a list of versions
|
59
|
+
#
|
60
|
+
# @param [String] url of web page with CoreOS versions
|
61
|
+
# @return [Array] list of Semantic::Version
|
62
|
+
def fetch_versions(url)
|
63
|
+
html = Nokogiri::HTML(open(url))
|
64
|
+
raw_versions = html.xpath('/html/body/a').map { |a| a.children.first.text.chomp('/') }
|
65
|
+
raw_versions.select { |rw| rw =~ /^[0-9]/ }.map { |rw| Semantic::Version.new(rw) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Parse and reformat CoreOS default mapping
|
69
|
+
#
|
70
|
+
# @param [Array] coreos_mapping list of region to virtualization kind mappings
|
71
|
+
# @return [Hash] mapping, that can be referred to with find_in_map
|
72
|
+
def parse_raw_mapping(coreos_mapping)
|
73
|
+
return {} unless coreos_mapping
|
74
|
+
amis = coreos_mapping.empty? ? [] : coreos_mapping['amis']
|
75
|
+
Hash[
|
76
|
+
amis.map { |a| [a['name'], { pv: a['pv'], hvm: a['hvm'] }] }
|
77
|
+
].with_indifferent_access
|
78
|
+
end
|
79
|
+
end # class << self
|
80
|
+
|
81
|
+
# Initialize CoreOS related configurations
|
82
|
+
#
|
83
|
+
def core_os_init
|
84
|
+
mapping 'AWSCoreOSAMI', CoreOS.get_channel_version(channel: :stable)
|
85
|
+
end
|
86
|
+
end # module CoreOS
|
87
|
+
end # module Plugins
|
88
|
+
end # module Enscalator
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Plugin for Couchbase
|
4
|
+
module Couchbase
|
5
|
+
# Couchbase instance
|
6
|
+
#
|
7
|
+
# @param [String] db_name database name
|
8
|
+
# @param [String] bucket bucket
|
9
|
+
# @param [Integer] allocated_storage size of instance primary storage
|
10
|
+
# @param [String] instance_type instance type
|
11
|
+
def couchbase_init(db_name,
|
12
|
+
bucket: nil,
|
13
|
+
allocated_storage: 5,
|
14
|
+
instance_type: 't2.medium')
|
15
|
+
@couchbase_mapping ||=
|
16
|
+
mapping 'AWSCouchbaseAMI', couchbase_ami_mapping
|
17
|
+
|
18
|
+
fail 'You need to provide a bucket for couchbase' if bucket.nil?
|
19
|
+
|
20
|
+
parameter_key_name "Couchbase#{db_name}"
|
21
|
+
|
22
|
+
parameter_allocated_storage "Couchbase#{db_name}",
|
23
|
+
default: allocated_storage,
|
24
|
+
min: 5,
|
25
|
+
max: 1024
|
26
|
+
|
27
|
+
parameter_ec2_instance_type "Couchbase#{db_name}", type: instance_type
|
28
|
+
|
29
|
+
instance_vpc("Couchbase#{db_name}",
|
30
|
+
find_in_map('AWSCouchbaseAMI', ref('AWS::Region'), 'amd64'),
|
31
|
+
ref_resource_subnets.first,
|
32
|
+
[ref_private_security_group, ref_resource_security_group],
|
33
|
+
depends_on: [],
|
34
|
+
properties: {
|
35
|
+
KeyName: ref("Couchbase#{db_name}KeyName"),
|
36
|
+
InstanceType: ref("Couchbase#{db_name}InstanceType"),
|
37
|
+
UserData: Base64.encode64(
|
38
|
+
couchbase_user_data(bucket, 'Administrator', '3fA76JWtzYbm')
|
39
|
+
)
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
# Couchbase ami mapping for x64 images
|
44
|
+
# @return [Hash] aws mapping
|
45
|
+
def couchbase_ami_mapping
|
46
|
+
{
|
47
|
+
'us-west-2': { paravirtual: 'ami-c398c6f3' },
|
48
|
+
'us-west-1': { paravirtual: 'ami-1a554c5f' },
|
49
|
+
'us-east-1': { paravirtual: 'ami-403b4328' },
|
50
|
+
'sa-east-1': { paravirtual: 'ami-59229f44' },
|
51
|
+
'eu-west-1': { paravirtual: 'ami-8129aaf6' },
|
52
|
+
'ap-southeast-1': { paravirtual: 'ami-88745fda' },
|
53
|
+
'ap-northeast-1': { paravirtual: 'ami-6a7b676b' }
|
54
|
+
}.with_indifferent_access
|
55
|
+
end
|
56
|
+
|
57
|
+
# Couchbase user data
|
58
|
+
#
|
59
|
+
# @param [String] bucket couchbase bucket
|
60
|
+
# @return [String] user data script
|
61
|
+
def couchbase_user_data(bucket, user, password)
|
62
|
+
data = <<-EOG
|
63
|
+
#!/usr/bin/env bash
|
64
|
+
while [[ ! -e /opt/couchbase/var/lib/couchbase/couchbase-server.pid ]]; do
|
65
|
+
sleep 20
|
66
|
+
echo "wait for couchbase" >> /tmp/userdatalog
|
67
|
+
service couchbase-server status >> /tmp/userdatalog
|
68
|
+
done
|
69
|
+
|
70
|
+
# Wait for everything to be initialized
|
71
|
+
sleep 1m
|
72
|
+
RAMSIZE=`cat /proc/meminfo | grep MemTotal | awk {'print $2'}`
|
73
|
+
RAMSIZE=`echo "($RAMSIZE/1000*(75/100.0))" | bc -l | xargs printf %0.f`
|
74
|
+
INSTANCE=`curl http://169.254.169.254/latest/meta-data/instance-id`
|
75
|
+
/opt/couchbase/bin/couchbase-cli cluster-init -c 127.0.0.1:8091 \
|
76
|
+
-u #{user} \
|
77
|
+
-p $INSTANCE \
|
78
|
+
--cluster-init-password=#{password} \
|
79
|
+
--cluster-init-ramsize=$RAMSIZE 2>&1 >> /tmp/userdatalog
|
80
|
+
/opt/couchbase/bin/couchbase-cli node-init -c 127.0.0.1:8091 \
|
81
|
+
-u #{user} \
|
82
|
+
-p #{password} 2>&1 >> /tmp/userdatalog
|
83
|
+
/opt/couchbase/bin/couchbase-cli bucket-create -c 127.0.0.1:8091 \
|
84
|
+
--bucket=#{bucket} \
|
85
|
+
--bucket-type=couchbase \
|
86
|
+
--bucket-password=#{password} \
|
87
|
+
--bucket-port=11211 \
|
88
|
+
--bucket-ramsize=$RAMSIZE \
|
89
|
+
--bucket-replica=1 \
|
90
|
+
--bucket-priority=low \
|
91
|
+
--wait \
|
92
|
+
-u #{user} -p #{password} 2>&1 >> /tmp/userdatalog
|
93
|
+
EOG
|
94
|
+
data
|
95
|
+
end
|
96
|
+
end # module Couchbase
|
97
|
+
end # module Plugins
|
98
|
+
end # module Enscalator
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Debian appliance
|
4
|
+
module Debian
|
5
|
+
class << self
|
6
|
+
# Supported storage types in AWS
|
7
|
+
STORAGE = [:ebs, :'instance-store']
|
8
|
+
|
9
|
+
# Supported Debian image architectures
|
10
|
+
ARCH = [:x86_64, :i386]
|
11
|
+
|
12
|
+
# Supported Debian releases
|
13
|
+
RELEASE = { jessie: '8', wheezy: '7' }
|
14
|
+
|
15
|
+
# Structure to hold parsed record
|
16
|
+
Struct.new('Debian', :region, :ami, :virtualization, :arch, :root_storage)
|
17
|
+
|
18
|
+
# @param [Symbol, String] release a codename or version number
|
19
|
+
# @param [Symbol] storage storage kind
|
20
|
+
# @param [Symbol] arch architecture
|
21
|
+
# @raise [ArgumentError] if release is nil, empty or not one of supported values
|
22
|
+
# @raise [ArgumentError] if storage is nil, empty or not one of supported values
|
23
|
+
# @raise [ArgumentError] if arch is nil, empty or not one of supported values
|
24
|
+
# @return [Hash] mapping for Debian amis
|
25
|
+
def get_mapping(release: :jessie, storage: :ebs, arch: :x86_64)
|
26
|
+
fail ArgumentError, 'release can be either codename or version' unless RELEASE.to_a.flatten.include? release
|
27
|
+
fail ArgumentError, "storage can only be one of #{STORAGE}" unless STORAGE.include? storage
|
28
|
+
fail ArgumentError, "arch can only be one of #{ARCH}" unless ARCH.include? arch
|
29
|
+
version = RELEASE.keys.include?(release) ? release : RELEASE.invert[release]
|
30
|
+
url = "https://wiki.debian.org/Cloud/AmazonEC2Image/#{version.capitalize}?action=raw"
|
31
|
+
body = open(url) { |f| f.read }
|
32
|
+
parse_raw_entries(body.split("\r\n").select { |b| b.starts_with?('||') })
|
33
|
+
.select { |i| i.ami =~ /ami[-][a-z0-9]{8}/ }
|
34
|
+
.select { |r| r.root_storage == storage.to_s && r.arch == arch.to_s }
|
35
|
+
.group_by(&:region)
|
36
|
+
.map { |k, v| [k, v.map { |i| [i.virtualization, i.ami] }.to_h] }
|
37
|
+
.to_h
|
38
|
+
.with_indifferent_access
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Parse raw entries and convert them to meaningful structs
|
44
|
+
#
|
45
|
+
# @param [Array] items in its raw form
|
46
|
+
def parse_raw_entries(items)
|
47
|
+
header, *entries = items.map do |item|
|
48
|
+
if (item.include?('Region'))..(item.include?('paravirtual'))
|
49
|
+
item.downcase.split('||').map(&:strip).map { |i| i.delete("'") }.reject(&:empty?)
|
50
|
+
end
|
51
|
+
end.compact
|
52
|
+
amis = entries.select { |e| e.first =~ /[a-z]{2}-/ }.flat_map do |entry|
|
53
|
+
region, *images = entry
|
54
|
+
images.map.with_index(1).map do |ami, i|
|
55
|
+
[region, ami, header[i].nil? ? '' : header[i].split].flatten
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
amis.map { |a| Struct::Debian.new(*a) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
# Create new Debian instance
|
63
|
+
#
|
64
|
+
# @param [String] storage storage kind (usually ebs or ephemeral)
|
65
|
+
# @param [String] arch architecture (amd64 or i386)
|
66
|
+
def debian_init(storage: :ebs, arch: :x86_64)
|
67
|
+
mapping 'AWSDebianAMI', Debian.get_mapping(storage: storage, arch: arch)
|
68
|
+
end
|
69
|
+
end # class Debian
|
70
|
+
end # module Plugins
|
71
|
+
end # module Enscalator
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Collection of methods to work with Elastic Beanstalk
|
4
|
+
module ElasticBeanstalk
|
5
|
+
include Enscalator::Helpers
|
6
|
+
|
7
|
+
# Create new ElasticBeanstalk instance
|
8
|
+
#
|
9
|
+
# @param [String] app_name application name
|
10
|
+
# @param [String] ssh_key name of ssh key to configure instance with
|
11
|
+
# @param [String] solution_stack_name stack name of Elastic Beanstalk solution
|
12
|
+
# @param [String] instance_type default instance type
|
13
|
+
def elastic_beanstalk_app(app_name,
|
14
|
+
stack_name,
|
15
|
+
ssh_key: app_name,
|
16
|
+
solution_stack_name: '64bit Amazon Linux 2015.09 v2.0.4 running Ruby 2.2 (Passenger Standalone)',
|
17
|
+
instance_type: 't2.small')
|
18
|
+
|
19
|
+
properties = {
|
20
|
+
ApplicationName: app_name,
|
21
|
+
Description: "#{app_name} in #{stack_name} stack",
|
22
|
+
ConfigurationTemplates:
|
23
|
+
[
|
24
|
+
{
|
25
|
+
TemplateName: 'DefaultConfiguration',
|
26
|
+
Description: 'Default Configuration Version 1.0 - with SSH access',
|
27
|
+
SolutionStackName: solution_stack_name,
|
28
|
+
OptionSettings: [
|
29
|
+
{
|
30
|
+
'Namespace': 'aws:autoscaling:launchconfiguration',
|
31
|
+
'OptionName': 'EC2KeyName',
|
32
|
+
'Value': ssh_key
|
33
|
+
},
|
34
|
+
{
|
35
|
+
'Namespace': 'aws:ec2:vpc',
|
36
|
+
'OptionName': 'VPCId',
|
37
|
+
'Value': vpc.id
|
38
|
+
},
|
39
|
+
{
|
40
|
+
'Namespace': 'aws:ec2:vpc',
|
41
|
+
'OptionName': 'Subnets',
|
42
|
+
'Value': { 'Fn::Join': [',', ref_application_subnets] }
|
43
|
+
},
|
44
|
+
{
|
45
|
+
'Namespace': 'aws:ec2:vpc',
|
46
|
+
'OptionName': 'ELBSubnets',
|
47
|
+
'Value': { 'Fn::Join': [',', public_subnets] }
|
48
|
+
},
|
49
|
+
{
|
50
|
+
'Namespace': 'aws:autoscaling:launchconfiguration',
|
51
|
+
'OptionName': 'SecurityGroups',
|
52
|
+
'Value': { 'Fn::Join': [',', [ref_application_security_group, ref_private_security_group]] }
|
53
|
+
},
|
54
|
+
{
|
55
|
+
'Namespace': 'aws:autoscaling:launchconfiguration',
|
56
|
+
'OptionName': 'InstanceType',
|
57
|
+
'Value': instance_type
|
58
|
+
}
|
59
|
+
]
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
|
64
|
+
elastic_beanstalk_resource_name = "#{app_name}BeanstalkApp"
|
65
|
+
|
66
|
+
resource elastic_beanstalk_resource_name,
|
67
|
+
Type: 'AWS::ElasticBeanstalk::Application',
|
68
|
+
Properties: properties
|
69
|
+
|
70
|
+
elastic_beanstalk_resource_name
|
71
|
+
end
|
72
|
+
end # module ElasticBeanstalk
|
73
|
+
end # module Plugins
|
74
|
+
end # module Enscalator
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Enscalator
|
2
|
+
module Plugins
|
3
|
+
# Collection of methods to work with ElastiCache (Redis)
|
4
|
+
module Elasticache
|
5
|
+
include Enscalator::Helpers
|
6
|
+
|
7
|
+
# Generates magic number (sha256 hex digest) from given Hash
|
8
|
+
# @param [Object] input used for digest generation
|
9
|
+
# @return [String]
|
10
|
+
def magic_number(input)
|
11
|
+
str = if input.is_a?(String)
|
12
|
+
input
|
13
|
+
elsif input.is_a?(Array)
|
14
|
+
input.flatten.map(&:to_s).sort.join('&')
|
15
|
+
elsif input.is_a?(Hash)
|
16
|
+
flatten_hash(input).map { |k, v| "#{k}=#{v}" }.sort.join('&')
|
17
|
+
else
|
18
|
+
fail("Not supported input format: #{input.class}")
|
19
|
+
end
|
20
|
+
Digest::SHA256.hexdigest(str)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Initialize resources common for all ElastiCache instances
|
24
|
+
# @param [Object] app_name application stack name
|
25
|
+
# @param [Object] cache_node_type node type
|
26
|
+
def init_cluster_resources(app_name, cache_node_type, param_group_seed: nil, **properties)
|
27
|
+
subnet_group_name = "#{app_name}ElasticacheSubnetGroup"
|
28
|
+
resource subnet_group_name,
|
29
|
+
Type: 'AWS::ElastiCache::SubnetGroup',
|
30
|
+
Properties: {
|
31
|
+
Description: 'SubnetGroup for elasticache',
|
32
|
+
SubnetIds: ref_resource_subnets
|
33
|
+
}
|
34
|
+
|
35
|
+
security_group_name =
|
36
|
+
security_group_vpc "#{app_name}RedisSecurityGroup",
|
37
|
+
"Redis Security Group for #{app_name}",
|
38
|
+
ref_vpc_id,
|
39
|
+
security_group_ingress: [
|
40
|
+
{
|
41
|
+
IpProtocol: 'tcp',
|
42
|
+
FromPort: '6379',
|
43
|
+
ToPort: '6389',
|
44
|
+
SourceSecurityGroupId: ref_application_security_group
|
45
|
+
}
|
46
|
+
],
|
47
|
+
tags: {
|
48
|
+
Name: join('-', aws_stack_name, 'res', 'sg'),
|
49
|
+
Application: aws_stack_name
|
50
|
+
}
|
51
|
+
|
52
|
+
parameter_group_required_props = {
|
53
|
+
'reserved-memory': Core::InstanceType.elasticache_instance_type.max_memory(cache_node_type) / 2
|
54
|
+
}
|
55
|
+
|
56
|
+
parameter_group_props = (
|
57
|
+
if !properties.nil? && properties.key?(:parameter_group_properties)
|
58
|
+
properties[:parameter_group_properties]
|
59
|
+
else
|
60
|
+
{}
|
61
|
+
end).merge(parameter_group_required_props)
|
62
|
+
|
63
|
+
# TODO: remove this workaround when related template gets fixed
|
64
|
+
parameter_group_name = if param_group_seed
|
65
|
+
"#{app_name}RedisParameterGroup#{param_group_seed}"
|
66
|
+
else
|
67
|
+
"#{app_name}RedisParameterGroup#{magic_number(parameter_group_props)}"
|
68
|
+
end
|
69
|
+
|
70
|
+
resource parameter_group_name,
|
71
|
+
Type: 'AWS::ElastiCache::ParameterGroup',
|
72
|
+
Properties: {
|
73
|
+
Description: "#{app_name} redis parameter group",
|
74
|
+
CacheParameterGroupFamily: 'redis3.2',
|
75
|
+
Properties: parameter_group_props
|
76
|
+
}
|
77
|
+
|
78
|
+
{
|
79
|
+
subnet_group: subnet_group_name,
|
80
|
+
security_group: security_group_name,
|
81
|
+
parameter_group: parameter_group_name
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create ElastiCache cluster
|
86
|
+
# @param [String] app_name application name
|
87
|
+
# @param [String] cache_node_type instance node type
|
88
|
+
# @param [Integer] num_cache_nodes number of nodes to create
|
89
|
+
def elasticache_cluster_init(app_name, cache_node_type: 'cache.m1.small', num_cache_nodes: 1)
|
90
|
+
cluster_resources = init_cluster_resources(app_name, cache_node_type)
|
91
|
+
|
92
|
+
resource_name = "#{app_name}RedisCluster"
|
93
|
+
resource resource_name,
|
94
|
+
Type: 'AWS::ElastiCache::CacheCluster',
|
95
|
+
Properties: {
|
96
|
+
Engine: 'redis',
|
97
|
+
NumCacheNodes: "#{num_cache_nodes}",
|
98
|
+
CacheNodeType: cache_node_type,
|
99
|
+
CacheSubnetGroupName: ref("#{app_name}ElasticacheSubnetGroup"),
|
100
|
+
CacheParameterGroupName: ref(cluster_resources[:parameter_group]),
|
101
|
+
VpcSecurityGroupIds: [get_att("#{app_name}RedisSecurityGroup", 'GroupId')]
|
102
|
+
}
|
103
|
+
resource_name
|
104
|
+
|
105
|
+
# Unable to get created resource endpoint and port with Fn::GetAtt for engine == redis
|
106
|
+
# For more details see here:
|
107
|
+
# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticache-cache-cluster.html
|
108
|
+
end
|
109
|
+
|
110
|
+
# Create ElastiCache replication group
|
111
|
+
# @param [String] app_name application name
|
112
|
+
# @param [String] cache_node_type instance node type
|
113
|
+
def elasticache_repl_group_init(app_name, cache_node_type: 'cache.t2.small',
|
114
|
+
num_cache_clusters: 2, seed: nil, **properties)
|
115
|
+
if %w(t1).map { |t| cache_node_type.include?(t) }.include?(true)
|
116
|
+
fail "T1 instance types are not supported, got '#{cache_node_type}'"
|
117
|
+
end
|
118
|
+
|
119
|
+
cluster_properties =
|
120
|
+
!properties.nil? && properties.key?(:cluster_properties) ? properties[:cluster_properties] : {}
|
121
|
+
|
122
|
+
cluster_resources = init_cluster_resources(app_name,
|
123
|
+
cache_node_type,
|
124
|
+
param_group_seed: seed,
|
125
|
+
parameter_group_properties: cluster_properties)
|
126
|
+
|
127
|
+
resource_name = "#{app_name}RedisReplicationGroup"
|
128
|
+
resource resource_name,
|
129
|
+
Type: 'AWS::ElastiCache::ReplicationGroup',
|
130
|
+
Properties: {
|
131
|
+
Engine: 'redis',
|
132
|
+
ReplicationGroupDescription: "Redis Replication group for #{app_name}",
|
133
|
+
AutomaticFailoverEnabled: num_cache_clusters > 1 ? 'true' : 'false',
|
134
|
+
NumCacheClusters: num_cache_clusters,
|
135
|
+
CacheNodeType: cache_node_type,
|
136
|
+
CacheSubnetGroupName: ref("#{app_name}ElasticacheSubnetGroup"),
|
137
|
+
CacheParameterGroupName: ref(cluster_resources[:parameter_group]),
|
138
|
+
SecurityGroupIds: [
|
139
|
+
get_att("#{app_name}RedisSecurityGroup", 'GroupId'),
|
140
|
+
ref_private_security_group
|
141
|
+
]
|
142
|
+
}.merge(properties.reject { |k, _v| k == :cluster_properties })
|
143
|
+
|
144
|
+
output "#{app_name}RedisReplicationGroup",
|
145
|
+
Description: "Redis ReplicationGroup #{app_name}",
|
146
|
+
Value: ref("#{app_name}RedisReplicationGroup")
|
147
|
+
|
148
|
+
output "#{app_name}RedisPrimaryEndpointAddress",
|
149
|
+
Description: "Redis Primary Endpoint Address #{app_name}",
|
150
|
+
Value: get_att(resource_name, 'PrimaryEndPoint.Address')
|
151
|
+
|
152
|
+
output "#{app_name}RedisPrimaryEndpointPort",
|
153
|
+
Description: "Redis Primary Endpoint Port #{app_name}",
|
154
|
+
Value: get_att(resource_name, 'PrimaryEndPoint.Port')
|
155
|
+
|
156
|
+
output "#{app_name}RedisReadOnlyEndpointAddresses",
|
157
|
+
Description: "Redis ReadOnly Endpoint Addresses #{app_name}",
|
158
|
+
Value: get_att(resource_name, 'ReadEndPoint.Addresses')
|
159
|
+
|
160
|
+
output "#{app_name}RedisReadOnlyEndpointPorts",
|
161
|
+
Description: "Redis ReadOnly Endpoint Ports #{app_name}",
|
162
|
+
Value: get_att(resource_name, 'ReadEndPoint.Ports')
|
163
|
+
|
164
|
+
resource_name
|
165
|
+
end
|
166
|
+
end # module ElasticCache
|
167
|
+
end # module Plugins
|
168
|
+
end # module Enscalator
|