enscalator 0.4.0.pre.alpha.pre.16
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.
- 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
|