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.
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,402 @@
1
+ module Enscalator
2
+ module Templates
3
+ # Amazon AWS Virtual Private Cloud template with NAT instance
4
+ class VPCWithNATInstance < Enscalator::RichTemplateDSL
5
+ # Subnet size (256 addresses)
6
+ SUBNET_CIDR_BLOCK_SIZE = 24
7
+
8
+ # Template method
9
+ def tpl
10
+ description = <<-EOS.gsub(/^\s+\|/, '')
11
+ |AWS CloudFormation template for the VPC environment.
12
+ |For each availability zone stack creates: the public subnet, internet gateway, NAT EC2 instance
13
+ |internet access from private subnets, routing configuration for corresponding subnets
14
+ |and security groups.
15
+ EOS
16
+ value Description: description
17
+
18
+ nat_key_name = gen_ssh_key_name('vpc-nat', region, stack_name)
19
+ pre_run { create_ssh_key nat_key_name, region, force_create: false }
20
+
21
+ parameter_ec2_instance_type 'NAT', type: 'c4.large'
22
+
23
+ mapping 'AWSNATAMI',
24
+ 'us-east-1': { AMI: 'ami-68115b02' },
25
+ 'us-west-1': { AMI: 'ami-ef1a718f' },
26
+ 'us-west-2': { AMI: 'ami-77a4b816' },
27
+ 'eu-west-1': { AMI: 'ami-c0993ab3' },
28
+ 'eu-central-1': { AMI: 'ami-0b322e67' },
29
+ 'ap-northeast-1': { AMI: 'ami-f885ae96' },
30
+ 'ap-southeast-1': { AMI: 'ami-e2fc3f81' },
31
+ 'ap-southeast-2': { AMI: 'ami-e3217a80' },
32
+ 'sa-east-1': { AMI: 'ami-8631b5ea' }
33
+
34
+ mapping 'AWSRegionNetConfig', Core::NetworkConfig.mapping_vpc_net
35
+
36
+ resource 'VPC',
37
+ Type: 'AWS::EC2::VPC',
38
+ Properties: {
39
+ CidrBlock: find_in_map('AWSRegionNetConfig', ref('AWS::Region'), 'VPC'),
40
+ EnableDnsSupport: 'true',
41
+ EnableDnsHostnames: 'true',
42
+ Tags: [
43
+ {
44
+ Key: 'Name',
45
+ Value: aws_stack_name
46
+ },
47
+ {
48
+ Key: 'Application',
49
+ Value: aws_stack_name
50
+ },
51
+ {
52
+ Key: 'Network',
53
+ Value: 'Public'
54
+ }
55
+ ]
56
+ }
57
+
58
+ resource 'InternetGateway',
59
+ Type: 'AWS::EC2::InternetGateway',
60
+ Properties: {
61
+ Tags: [
62
+ {
63
+ Key: 'Name',
64
+ Value: 'Public Gateway'
65
+ },
66
+ {
67
+ Key: 'Application',
68
+ Value: aws_stack_name
69
+ },
70
+ {
71
+ Key: 'Network',
72
+ Value: 'Public'
73
+ }
74
+ ]
75
+ }
76
+
77
+ resource 'GatewayToInternet',
78
+ DependsOn: %w( VPC InternetGateway ),
79
+ Type: 'AWS::EC2::VPCGatewayAttachment',
80
+ Properties: {
81
+ VpcId: ref('VPC'),
82
+ InternetGatewayId: ref('InternetGateway')
83
+ }
84
+
85
+ resource 'PublicRouteTable',
86
+ DependsOn: ['VPC'],
87
+ Type: 'AWS::EC2::RouteTable',
88
+ Properties: {
89
+ VpcId: ref('VPC'),
90
+ Tags: [
91
+ {
92
+ Key: 'Name',
93
+ Value: 'Public'
94
+ },
95
+ {
96
+ Key: 'Application',
97
+ Value: aws_stack_name
98
+ },
99
+ {
100
+ Key: 'Network',
101
+ Value: 'Public'
102
+ }
103
+ ]
104
+ }
105
+
106
+ resource 'PublicRoute',
107
+ DependsOn: %w( PublicRouteTable InternetGateway ),
108
+ Type: 'AWS::EC2::Route',
109
+ Properties: {
110
+ RouteTableId: ref('PublicRouteTable'),
111
+ DestinationCidrBlock: '0.0.0.0/0',
112
+ GatewayId: ref('InternetGateway')
113
+ }
114
+
115
+ resource 'PublicNetworkAcl',
116
+ DependsOn: ['VPC'],
117
+ Type: 'AWS::EC2::NetworkAcl',
118
+ Properties: {
119
+ VpcId: ref('VPC'),
120
+ Tags: [
121
+ {
122
+ Key: 'Name',
123
+ Value: 'Public'
124
+ },
125
+ {
126
+ Key: 'Application',
127
+ Value: aws_stack_name
128
+ },
129
+ {
130
+ Key: 'Network',
131
+ Value: 'Public'
132
+ }
133
+ ]
134
+ }
135
+
136
+ resource 'InboundHTTPPublicNetworkAclEntry',
137
+ DependsOn: ['PublicNetworkAcl'],
138
+ Type: 'AWS::EC2::NetworkAclEntry',
139
+ Properties: {
140
+ NetworkAclId: ref('PublicNetworkAcl'),
141
+ RuleNumber: '100',
142
+ Protocol: '-1',
143
+ RuleAction: 'allow',
144
+ Egress: 'false',
145
+ CidrBlock: '0.0.0.0/0',
146
+ PortRange: { From: '0', To: '65535' }
147
+ }
148
+
149
+ resource 'OutboundHTTPPublicNetworkAclEntry',
150
+ DependsOn: ['PublicNetworkAcl'],
151
+ Type: 'AWS::EC2::NetworkAclEntry',
152
+ Properties: {
153
+ NetworkAclId: ref('PublicNetworkAcl'),
154
+ RuleNumber: '100',
155
+ Protocol: '-1',
156
+ RuleAction: 'allow',
157
+ Egress: 'true',
158
+ CidrBlock: '0.0.0.0/0',
159
+ PortRange: { From: '0', To: '65535' }
160
+ }
161
+
162
+ resource 'PrivateNetworkAcl',
163
+ DependsOn: ['VPC'],
164
+ Type: 'AWS::EC2::NetworkAcl',
165
+ Properties: {
166
+ VpcId: ref('VPC'),
167
+ Tags: [
168
+ {
169
+ Key: 'Name',
170
+ Value: 'Private'
171
+ },
172
+ {
173
+ Key: 'Application',
174
+ Value: aws_stack_name
175
+ },
176
+ {
177
+ Key: 'Network',
178
+ Value: 'Private'
179
+ }
180
+ ]
181
+ }
182
+
183
+ resource 'InboundPrivateNetworkAclEntry',
184
+ DependsOn: ['PrivateNetworkAcl'],
185
+ Type: 'AWS::EC2::NetworkAclEntry',
186
+ Properties: {
187
+ NetworkAclId: ref('PrivateNetworkAcl'),
188
+ RuleNumber: '100',
189
+ Protocol: '6',
190
+ RuleAction: 'allow',
191
+ Egress: 'false',
192
+ CidrBlock: '0.0.0.0/0',
193
+ PortRange: { From: '0', To: '65535' }
194
+ }
195
+
196
+ resource 'OutBoundPrivateNetworkAclEntry',
197
+ DependsOn: ['PrivateNetworkAcl'],
198
+ Type: 'AWS::EC2::NetworkAclEntry',
199
+ Properties: {
200
+ NetworkAclId: ref('PrivateNetworkAcl'),
201
+ RuleNumber: '100',
202
+ Protocol: '6',
203
+ RuleAction: 'allow',
204
+ Egress: 'true',
205
+ CidrBlock: '0.0.0.0/0',
206
+ PortRange: { From: '0', To: '65535' }
207
+ }
208
+
209
+ resource 'NATSecurityGroup',
210
+ DependsOn: ['PrivateSecurityGroup'],
211
+ Type: 'AWS::EC2::SecurityGroup',
212
+ Properties: {
213
+ GroupDescription: 'Enable internal access to the NAT device',
214
+ VpcId: ref('VPC'),
215
+ SecurityGroupIngress: [
216
+ {
217
+ IpProtocol: 'tcp',
218
+ FromPort: '22',
219
+ ToPort: '22',
220
+ SourceSecurityGroupId: ref('PrivateSecurityGroup')
221
+ },
222
+ {
223
+ IpProtocol: 'tcp',
224
+ FromPort: '80',
225
+ ToPort: '80',
226
+ SourceSecurityGroupId: ref('PrivateSecurityGroup')
227
+ },
228
+ {
229
+ IpProtocol: 'tcp',
230
+ FromPort: '443',
231
+ ToPort: '443',
232
+ SourceSecurityGroupId: ref('PrivateSecurityGroup')
233
+ },
234
+ {
235
+ IpProtocol: 'tcp',
236
+ FromPort: '465',
237
+ ToPort: '465',
238
+ SourceSecurityGroupId: ref('PrivateSecurityGroup')
239
+ },
240
+ {
241
+ IpProtocol: 'icmp',
242
+ FromPort: '-1',
243
+ ToPort: '-1',
244
+ CidrIp: '10.0.0.0/8'
245
+ }
246
+ ],
247
+ SecurityGroupEgress: [
248
+ {
249
+ IpProtocol: '-1',
250
+ FromPort: '0',
251
+ ToPort: '65535',
252
+ CidrIp: '0.0.0.0/0'
253
+ }
254
+ ],
255
+ Tags: [
256
+ {
257
+ Key: 'Name',
258
+ Value: 'NAT'
259
+ }
260
+ ]
261
+ }
262
+
263
+ resource 'PrivateSecurityGroup',
264
+ DependsOn: ['VPC'],
265
+ Type: 'AWS::EC2::SecurityGroup',
266
+ Properties: {
267
+ GroupDescription: 'Allow the Application instances to access the NAT device',
268
+ VpcId: ref('VPC'),
269
+ SecurityGroupEgress: [
270
+ {
271
+ IpProtocol: 'tcp',
272
+ FromPort: '0',
273
+ ToPort: '65535',
274
+ CidrIp: '10.0.0.0/8'
275
+ }
276
+ ],
277
+ SecurityGroupIngress: [
278
+ {
279
+ IpProtocol: 'tcp',
280
+ FromPort: '0',
281
+ ToPort: '65535',
282
+ CidrIp: '10.0.0.0/8'
283
+ }
284
+ ],
285
+ Tags: [
286
+ {
287
+ Key: 'Name',
288
+ Value: 'Private'
289
+ }
290
+ ]
291
+ }
292
+
293
+ current_cidr_block = Core::NetworkConfig.mapping_vpc_net[region.to_sym][:VPC]
294
+ public_cidr_blocks =
295
+ IPAddress(current_cidr_block).subnet(SUBNET_CIDR_BLOCK_SIZE).map(&:to_string).first(availability_zones.size)
296
+
297
+ availability_zones.zip(public_cidr_blocks).each do |pair, cidr_block|
298
+ suffix = pair.first
299
+ public_subnet_name = "PublicSubnet#{suffix.upcase}"
300
+ resource public_subnet_name,
301
+ DependsOn: ['VPC'],
302
+ Type: 'AWS::EC2::Subnet',
303
+ Properties: {
304
+ VpcId: ref('VPC'),
305
+ AvailabilityZone: join('', ref('AWS::Region'), suffix.to_s),
306
+ CidrBlock: cidr_block,
307
+ Tags: [
308
+ {
309
+ Key: 'Name',
310
+ Value: "Public #{suffix.upcase}"
311
+ },
312
+ {
313
+ Key: 'Application',
314
+ Value: aws_stack_name
315
+ },
316
+ {
317
+ Key: 'Network',
318
+ Value: 'Public'
319
+ }
320
+ ]
321
+ }
322
+
323
+ resource "PublicSubnetRouteTableAssociation#{suffix.upcase}",
324
+ DependsOn: [public_subnet_name, 'PublicRouteTable'],
325
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
326
+ Properties: {
327
+ SubnetId: ref(public_subnet_name),
328
+ RouteTableId: ref('PublicRouteTable')
329
+ }
330
+
331
+ nat_device_name = "NATDevice#{suffix.upcase}"
332
+ resource nat_device_name,
333
+ DependsOn: [public_subnet_name, 'NATSecurityGroup'],
334
+ Type: 'AWS::EC2::Instance',
335
+ Properties: {
336
+ InstanceType: ref('NATInstanceType'),
337
+ KeyName: nat_key_name,
338
+ SourceDestCheck: 'false',
339
+ ImageId: find_in_map('AWSNATAMI', ref('AWS::Region'), 'AMI'),
340
+ NetworkInterfaces: [
341
+ {
342
+ AssociatePublicIpAddress: 'true',
343
+ DeviceIndex: '0',
344
+ SubnetId: ref(public_subnet_name),
345
+ GroupSet: [ref('NATSecurityGroup')]
346
+ }
347
+ ],
348
+ Tags: [
349
+ {
350
+ Key: 'Name',
351
+ Value: nat_device_name
352
+ }
353
+ ]
354
+ }
355
+
356
+ private_route_table_name = "PrivateRouteTable#{suffix.upcase}"
357
+ resource private_route_table_name,
358
+ DependsOn: ['VPC'],
359
+ Type: 'AWS::EC2::RouteTable',
360
+ Properties: {
361
+ VpcId: ref('VPC'),
362
+ Tags: [
363
+ {
364
+ Key: 'Name',
365
+ Value: "Private #{suffix.upcase}"
366
+ },
367
+ {
368
+ Key: 'Application',
369
+ Value: aws_stack_name
370
+ },
371
+ {
372
+ Key: 'Network',
373
+ Value: 'Private'
374
+ }
375
+ ]
376
+ }
377
+
378
+ resource "PrivateRoute#{suffix.upcase}",
379
+ DependsOn: [private_route_table_name, nat_device_name],
380
+ Type: 'AWS::EC2::Route',
381
+ Properties: {
382
+ RouteTableId: ref(private_route_table_name),
383
+ DestinationCidrBlock: '0.0.0.0/0',
384
+ InstanceId: ref(nat_device_name)
385
+ }
386
+
387
+ output public_subnet_name,
388
+ Description: "Created Subnet #{suffix.upcase}",
389
+ Value: ref(public_subnet_name)
390
+ end
391
+
392
+ output 'VpcId',
393
+ Description: 'Created VPC',
394
+ Value: ref('VPC')
395
+
396
+ output 'PrivateSecurityGroup',
397
+ Description: 'SecurityGroup to add private resources',
398
+ Value: ref('PrivateSecurityGroup')
399
+ end # def tpl
400
+ end # class VPC
401
+ end # module Templates
402
+ end # module Enscalator
data/lib/enscalator.rb ADDED
@@ -0,0 +1,103 @@
1
+ require 'trollop'
2
+ require 'ipaddr'
3
+ require 'ipaddress'
4
+ require 'digest'
5
+ require 'aws-sdk'
6
+ require 'active_support'
7
+ require 'active_support/core_ext'
8
+ require 'active_support/inflector'
9
+ require 'active_support/inflector/inflections'
10
+ require 'enscalator/version'
11
+ require 'enscalator/helpers'
12
+ require 'enscalator/core'
13
+ require 'enscalator/plugins'
14
+ require 'enscalator/rich_template_dsl'
15
+ require 'enscalator/vpc_with_nat_gateway'
16
+ require 'enscalator/vpc_with_nat_instance'
17
+ require 'enscalator/vpc'
18
+ require 'enscalator/enapp'
19
+ require 'enscalator/templates'
20
+
21
+ # Namespace for Enscalator related code
22
+ module Enscalator
23
+ # Main method to actually run Enscalator
24
+ # @param [Array] argv list of command-line arguments
25
+ def self.run!(argv)
26
+ argv_dup = argv.dup
27
+ display_name = name.downcase
28
+ parser = Trollop::Parser.new do
29
+ banner "Usage: #{display_name} [arguments]"
30
+
31
+ opt :list_templates, 'List all available templates', default: false, short: 'l'
32
+ opt :template, 'Template name', type: String, short: 't'
33
+ opt :template_file, 'Template filename', type: String, short: 'f'
34
+ opt :region, 'AWS Region', type: String, default: 'us-east-1', short: 'r'
35
+ opt :parameters, "Parameters 'Key1=Value1;Key2=Value2'", type: String, short: 'p'
36
+ opt :stack_name, 'Stack name', type: String, short: 's'
37
+ opt :private_hosted_zone, "Private hosted zone (e.x. 'default-vpc.internal')", type: String, short: 'z'
38
+ opt :public_hosted_zone, 'Public hosted zone', type: String, short: 'g'
39
+ opt :create_stack, 'Create the stack', default: false, short: 'c'
40
+ opt :update_stack, 'Update already deployed stack', default: false, short: 'u'
41
+ opt :pre_run, 'Use pre-run hooks', default: true, short: 'e'
42
+ opt :post_run, 'Use post-run hooks', default: true, short: 'o'
43
+ opt :expand, 'Print generated JSON template', default: false, short: 'x'
44
+ opt :capabilities, 'AWS capabilities', default: 'CAPABILITY_IAM', short: 'a'
45
+ opt :vpc_stack_name, 'VPC stack name', default: 'default-vpc', short: 'n'
46
+ opt :availability_zone, 'Deploy to specific availability zone', default: 'all', short: 'd'
47
+ opt :profile, 'Use a specific profile from your credential file', type: String, default: nil
48
+
49
+ conflicts :list_templates, :create_stack, :update_stack, :expand
50
+ end
51
+
52
+ opts = Trollop.with_standard_exception_handling(parser) do
53
+ fail Trollop::HelpNeeded if argv.empty?
54
+ parser.parse argv
55
+ end
56
+
57
+ if opts[:availability_zone_given]
58
+ valid_values = ('a'..'e').to_a << 'all'
59
+ unless valid_values.include? opts[:availability_zone]
60
+ STDERR.puts %(Availability zone can be only one off "#{valid_values.join(',')}")
61
+ exit
62
+ end
63
+ end
64
+
65
+ # fallback to create_stack action when no action was given
66
+ if !opts[:create_stack] && !opts[:update_stack]
67
+ opts[:create_stack] = true
68
+ end
69
+
70
+ # load template from given file and update template list
71
+ if opts[:template_file]
72
+ unless File.exist?(opts[:template_file])
73
+ abort(format('Unable to find file "%s"', opts[:template_file]))
74
+ end
75
+ load(opts[:template_file])
76
+ unless Enscalator::Templates.all_valid?
77
+ STDERR.puts 'Some templates missing required tpl method:'
78
+ Enscalator::Templates.all.select { |a| !a.instance_methods.include?(:tpl) }.each do |tpl|
79
+ STDERR.puts tpl.name.demodulize
80
+ end
81
+ exit
82
+ end
83
+ end
84
+
85
+ templates = Enscalator::Templates.constants.map(&:to_s)
86
+
87
+ if opts[:list_templates]
88
+ STDERR.puts 'Available templates:'
89
+ STDERR.puts templates.sort
90
+ exit
91
+ end
92
+
93
+ if opts[:template] && templates.include?(opts[:template])
94
+ # for stack_name use template name as a base and convert it from camelcase to underscore case
95
+ opts[:stack_name] ||= opts[:template].underscore.gsub(/[_]/, '-')
96
+ Object.const_get("Enscalator::Templates::#{opts[:template]}").new(opts.merge(ARGV: argv_dup)).exec!
97
+ elsif opts[:template_given] && !opts[:template].empty?
98
+ STDERR.puts %(Template "#{opts[:template]}" doesn't exist)
99
+ else
100
+ STDERR.puts 'Template name cannot be an empty string'
101
+ end
102
+ end
103
+ end