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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +9 -0
  4. data/.rubocop_todo.yml +59 -0
  5. data/.travis.yml +22 -0
  6. data/CODE_OF_CONDUCT.md +13 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +148 -0
  10. data/Rakefile +43 -0
  11. data/bin/console +11 -0
  12. data/bin/setup +7 -0
  13. data/enscalator.gemspec +57 -0
  14. data/exe/enscalator +13 -0
  15. data/lib/enscalator/core/cf_parameters.rb +146 -0
  16. data/lib/enscalator/core/cf_resources.rb +225 -0
  17. data/lib/enscalator/core/instance_type.rb +205 -0
  18. data/lib/enscalator/core/network_config.rb +21 -0
  19. data/lib/enscalator/core.rb +10 -0
  20. data/lib/enscalator/enapp.rb +248 -0
  21. data/lib/enscalator/helpers/dns.rb +62 -0
  22. data/lib/enscalator/helpers/stack.rb +107 -0
  23. data/lib/enscalator/helpers/sub_process.rb +72 -0
  24. data/lib/enscalator/helpers/wrappers.rb +55 -0
  25. data/lib/enscalator/helpers.rb +127 -0
  26. data/lib/enscalator/plugins/amazon_linux.rb +93 -0
  27. data/lib/enscalator/plugins/auto_scale.rb +80 -0
  28. data/lib/enscalator/plugins/core_os.rb +88 -0
  29. data/lib/enscalator/plugins/couchbase.rb +98 -0
  30. data/lib/enscalator/plugins/debian.rb +71 -0
  31. data/lib/enscalator/plugins/elastic_beanstalk.rb +74 -0
  32. data/lib/enscalator/plugins/elasticache.rb +168 -0
  33. data/lib/enscalator/plugins/elasticsearch_amazon.rb +75 -0
  34. data/lib/enscalator/plugins/elasticsearch_bitnami.rb +198 -0
  35. data/lib/enscalator/plugins/elasticsearch_opsworks.rb +225 -0
  36. data/lib/enscalator/plugins/elb.rb +139 -0
  37. data/lib/enscalator/plugins/nat_gateway.rb +71 -0
  38. data/lib/enscalator/plugins/rds.rb +141 -0
  39. data/lib/enscalator/plugins/redis.rb +38 -0
  40. data/lib/enscalator/plugins/rethink_db.rb +21 -0
  41. data/lib/enscalator/plugins/route53.rb +143 -0
  42. data/lib/enscalator/plugins/ubuntu.rb +85 -0
  43. data/lib/enscalator/plugins/user-data/elasticsearch +367 -0
  44. data/lib/enscalator/plugins/vpc_peering_connection.rb +48 -0
  45. data/lib/enscalator/plugins.rb +30 -0
  46. data/lib/enscalator/rich_template_dsl.rb +209 -0
  47. data/lib/enscalator/templates/vpc_peering.rb +112 -0
  48. data/lib/enscalator/templates.rb +20 -0
  49. data/lib/enscalator/version.rb +5 -0
  50. data/lib/enscalator/vpc.rb +11 -0
  51. data/lib/enscalator/vpc_with_nat_gateway.rb +311 -0
  52. data/lib/enscalator/vpc_with_nat_instance.rb +402 -0
  53. data/lib/enscalator.rb +103 -0
  54. 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