sumomo 0.8.3 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,25 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Sumomo
3
- module Stack
4
+ module Stack
5
+ def cloudflare_hosted_zone(domain_name:, key:, email:)
6
+ root_name = /(?<root_name>[^.]+\.[^.]+)$/.match(domain_name)[:root_name]
4
7
 
5
- def cloudflare_hosted_zone(domain_name:,key:,email:)
6
- root_name = /(?<root_name>[^.]+\.[^.]+)$/.match(domain_name)[:root_name]
8
+ hz = make 'AWS::Route53::HostedZone' do
9
+ Name domain_name
10
+ end
7
11
 
8
- hz = make "AWS::Route53::HostedZone" do
9
- Name domain_name
10
- end
12
+ (0..3).each do |i|
13
+ make 'Custom::CloudflareDNSEntry' do
14
+ Key key
15
+ Email email
16
+ Domain root_name
17
+ Entry domain_name.sub(/#{root_name}$/, '').chomp('.')
18
+ NS hz.NameServers[i]
19
+ end
20
+ end
11
21
 
12
- for i in 0..3
13
- make "Custom::CloudflareDNSEntry" do
14
- Key key
15
- Email email
16
- Domain root_name
17
- Entry domain_name.sub(/#{root_name}$/, "").chomp(".")
18
- NS hz.NameServers[i]
19
- end
20
- end
21
-
22
- return hz
23
- end
24
- end
25
- end
22
+ hz
23
+ end
24
+ end
25
+ end
@@ -1,179 +1,172 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Sumomo
3
- module Stack
4
-
5
- def get_azs
6
- resp = @ec2.describe_availability_zones
7
-
8
- Array(resp.availability_zones.map do |x|
9
- x.zone_name
10
- end)
11
- end
12
-
13
- def allow_port(thing)
14
- if (thing == :all)
15
- {
16
- "IpProtocol" => "-1",
17
- "ToPort" => 65535,
18
- "FromPort" => 0,
19
- "CidrIp" => "0.0.0.0/0"
20
- }
21
- elsif thing.is_a? Integer and thing > 0 and thing < 65536
22
- # its a port!
23
- {
24
- "IpProtocol" => "tcp",
25
- "ToPort" => thing,
26
- "FromPort" => thing,
27
- "CidrIp" => "0.0.0.0/0"
28
- }
29
- elsif thing.is_a? String and /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/.match(thing)
30
- # its a cidr!
31
- {
32
- "IpProtocol" => "tcp",
33
- "ToPort" => 65535,
34
- "FromPort" => 0,
35
- "CidrIp" => thing
36
- }
37
- elsif thing.is_a? Hash
38
- # more shit
39
- {
40
- "IpProtocol" => thing[:protocol] || "tcp",
41
- "ToPort" => thing[:port] || thing[:end_port] || 0,
42
- "FromPort" => thing[:port] || thing[:start_port] || 65535,
43
- "CidrIp" => thing[:cidr] || "0.0.0.0/0"
44
- }
45
- else
46
- raise "utils.rb allow: please allow something"
47
- end
48
- end
49
-
50
- def http_listener(port: 80, instance_port: port)
51
- {
52
- "LoadBalancerPort" => port,
53
- "InstancePort" => instance_port,
54
- "Protocol" => "HTTP"
55
- }
56
- end
57
-
58
- def https_listener(cert_arn:, instance_port: 80, port: 443)
59
- res = http_listener(instance_port)
60
- res["LoadBalancerPort"] = lb_port
61
- res["Protocol"] = "HTTPS"
62
- res["SSLCertificateId"] = cert_arn
63
-
64
- return res
65
- end
66
-
67
- def elb_tcp_health_check(port: 80, healthy_threshold: 2, interval: 10, timeout: 5, unhealthy_threshold: 10, path: "/")
68
- elb_health_check(port: port,
69
- healthy_threshold: healthy_threshold,
70
- interval: interval,
71
- timeout: timeout,
72
- unhealthy_threshold: unhealthy_threshold,
73
- path: path,
74
- check_type: "TCP")
75
- end
76
-
77
- def elb_health_check(port: 80,
78
- healthy_threshold: 2,
79
- interval: 10,
80
- timeout: 5,
81
- unhealthy_threshold: 10,
82
- path: "/",
83
- check_type: "HTTP")
84
-
85
- options[:path] = "/#{options[:path]}"
86
- options[:path].gsub!(/^[\/]+/, "/")
87
- {
88
- "HealthyThreshold" => options[:healthy_threshold] || 2,
89
- "Interval" => options[:interval] || 10,
90
- "Target" => "#{check_type}:#{port}#{options[:path]}",
91
- "Timeout" => options[:timeout] || 5,
92
- "UnhealthyThreshold" => options[:unhealthy_threshold] || 10
93
- }
94
- end
95
-
96
- def initscript(wait_handle, asgname, script)
97
-
98
- call("Fn::Base64",
99
- call("Fn::Join", "", [
100
-
101
- "#!/bin/bash -v\n",
102
- "yum update -y aws-cfn-bootstrap\n",
103
-
104
- "# Helper function\n",
105
- "function error_exit\n",
106
- "{\n",
107
- " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" \"", wait_handle, "\"\n",
108
- " exit 1\n",
109
- "}\n",
110
-
111
- "# Run init meta\n",
112
- "/opt/aws/bin/cfn-init -s ", ref("AWS::StackId"), " -r ", asgname, " ",
113
- " --region ", ref("AWS::Region"), " || error_exit 'Failed to run cfn-init'\n",
114
-
115
- "# Run script\n",
116
- script,
117
-
118
- "\n",
119
-
120
- "# All is well so signal success\n",
121
- "/opt/aws/bin/cfn-signal -e 0 -r \"Setup complete\" \"", wait_handle, "\"\n"
122
- ]))
123
- end
124
-
125
- class EC2Tasks
126
- def initialize(bucket_name, &block)
127
- @script = ""
128
- @bucket_name = bucket_name
129
- @tags = []
130
- instance_eval(&block) if block
131
- end
132
-
133
- def mkdir(name)
134
- @script += <<-SNIPPET
135
- sudo mkdir -p #{name}
136
- SNIPPET
137
- end
138
-
139
- def download_file(name, local_path)
140
- @script += <<-SNIPPET
141
- sudo aws s3 cp s3://#{@bucket_name}/uploads/#{name} #{local_path}
142
- SNIPPET
143
- end
144
-
145
- def script
146
- @script
147
- end
148
-
149
- def tags
150
- @tags
151
- end
152
-
153
- def tag(name, value)
154
- @tags << [name, value]
155
- end
156
- end
157
-
158
- def make_spotter(
159
- price:,
160
- network:,
161
- layer:,
162
- ec2_sns_arn:nil,
163
- ecs_cluster:nil,
164
- eip:nil,
165
- &block)
166
- update_time = Time.now.to_i
167
-
168
- spot = make "Custom::SelectSpot" do
169
- DateTime update_time
170
- ExcludeString "1.,2.,small,micro"
171
- LookBack 3
172
- TargetPrice price
173
- end
174
-
175
- switcher1_src = define_custom_resource(name: "ASGSelector1", code: <<-CODE
176
-
4
+ module Stack
5
+ def get_azs
6
+ resp = @ec2.describe_availability_zones
7
+
8
+ Array(resp.availability_zones.map(&:zone_name))
9
+ end
10
+
11
+ def allow_port(thing)
12
+ if thing == :all
13
+ {
14
+ 'IpProtocol' => '-1',
15
+ 'ToPort' => 65_535,
16
+ 'FromPort' => 0,
17
+ 'CidrIp' => '0.0.0.0/0'
18
+ }
19
+ elsif thing.is_a?(Integer) && (thing > 0) && (thing < 65_536)
20
+ # its a port!
21
+ {
22
+ 'IpProtocol' => 'tcp',
23
+ 'ToPort' => thing,
24
+ 'FromPort' => thing,
25
+ 'CidrIp' => '0.0.0.0/0'
26
+ }
27
+ elsif thing.is_a?(String) && %r{[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+}.match(thing)
28
+ # its a cidr!
29
+ {
30
+ 'IpProtocol' => 'tcp',
31
+ 'ToPort' => 65_535,
32
+ 'FromPort' => 0,
33
+ 'CidrIp' => thing
34
+ }
35
+ elsif thing.is_a? Hash
36
+ # more shit
37
+ {
38
+ 'IpProtocol' => thing[:protocol] || 'tcp',
39
+ 'ToPort' => thing[:port] || thing[:end_port] || 0,
40
+ 'FromPort' => thing[:port] || thing[:start_port] || 65_535,
41
+ 'CidrIp' => thing[:cidr] || '0.0.0.0/0'
42
+ }
43
+ else
44
+ raise 'utils.rb allow: please allow something'
45
+ end
46
+ end
47
+
48
+ def http_listener(port: 80, instance_port: port)
49
+ {
50
+ 'LoadBalancerPort' => port,
51
+ 'InstancePort' => instance_port,
52
+ 'Protocol' => 'HTTP'
53
+ }
54
+ end
55
+
56
+ def https_listener(cert_arn:, instance_port: 80, port: 443)
57
+ res = http_listener(instance_port)
58
+ res['LoadBalancerPort'] = lb_port
59
+ res['Protocol'] = 'HTTPS'
60
+ res['SSLCertificateId'] = cert_arn
61
+
62
+ res
63
+ end
64
+
65
+ def elb_tcp_health_check(port: 80, healthy_threshold: 2, interval: 10, timeout: 5, unhealthy_threshold: 10, path: '/')
66
+ elb_health_check(port: port,
67
+ healthy_threshold: healthy_threshold,
68
+ interval: interval,
69
+ timeout: timeout,
70
+ unhealthy_threshold: unhealthy_threshold,
71
+ path: path,
72
+ check_type: 'TCP')
73
+ end
74
+
75
+ def elb_health_check(port: 80,
76
+ healthy_threshold: 2,
77
+ interval: 10,
78
+ timeout: 5,
79
+ unhealthy_threshold: 10,
80
+ path: '/',
81
+ check_type: 'HTTP')
82
+
83
+ options[:path] = "/#{options[:path]}"
84
+ options[:path].gsub!(%r{^[/]+}, '/')
85
+ {
86
+ 'HealthyThreshold' => options[:healthy_threshold] || 2,
87
+ 'Interval' => options[:interval] || 10,
88
+ 'Target' => "#{check_type}:#{port}#{options[:path]}",
89
+ 'Timeout' => options[:timeout] || 5,
90
+ 'UnhealthyThreshold' => options[:unhealthy_threshold] || 10
91
+ }
92
+ end
93
+
94
+ def initscript(wait_handle, asgname, script)
95
+ call('Fn::Base64',
96
+ call('Fn::Join', '', [
97
+
98
+ "#!/bin/bash -v\n",
99
+ "yum update -y aws-cfn-bootstrap\n",
100
+
101
+ "# Helper function\n",
102
+ "function error_exit\n",
103
+ "{\n",
104
+ ' /opt/aws/bin/cfn-signal -e 1 -r "$1" "', wait_handle, "\"\n",
105
+ " exit 1\n",
106
+ "}\n",
107
+
108
+ "# Run init meta\n",
109
+ '/opt/aws/bin/cfn-init -s ', ref('AWS::StackId'), ' -r ', asgname, ' ',
110
+ ' --region ', ref('AWS::Region'), " || error_exit 'Failed to run cfn-init'\n",
111
+
112
+ "# Run script\n",
113
+ script,
114
+
115
+ "\n",
116
+
117
+ "# All is well so signal success\n",
118
+ '/opt/aws/bin/cfn-signal -e 0 -r "Setup complete" "', wait_handle, "\"\n"
119
+ ]))
120
+ end
121
+
122
+ class EC2Tasks
123
+ def initialize(bucket_name, &block)
124
+ @script = ''
125
+ @bucket_name = bucket_name
126
+ @tags = []
127
+ instance_eval(&block) if block
128
+ end
129
+
130
+ def mkdir(name)
131
+ @script += <<~SNIPPET
132
+ sudo mkdir -p #{name}
133
+ SNIPPET
134
+ end
135
+
136
+ def download_file(name, local_path)
137
+ @script += <<~SNIPPET
138
+ sudo aws s3 cp s3://#{@bucket_name}/uploads/#{name} #{local_path}
139
+ SNIPPET
140
+ end
141
+
142
+ attr_reader :script
143
+
144
+ attr_reader :tags
145
+
146
+ def tag(name, value)
147
+ @tags << [name, value]
148
+ end
149
+ end
150
+
151
+ def make_spotter(
152
+ price:,
153
+ network:,
154
+ layer:,
155
+ ec2_sns_arn: nil,
156
+ ecs_cluster: nil,
157
+ eip: nil,
158
+ &block
159
+ )
160
+ update_time = Time.now.to_i
161
+
162
+ spot = make 'Custom::SelectSpot' do
163
+ DateTime update_time
164
+ ExcludeString '1.,2.,small,micro'
165
+ LookBack 3
166
+ TargetPrice price
167
+ end
168
+
169
+ switcher1_src = define_custom_resource(name: 'ASGSelector1', code: <<-CODE
177
170
  store.get("num1", function(num) {
178
171
  num = parseInt(num);
179
172
  if (request.RequestType != "Delete")
@@ -190,10 +183,10 @@ sudo aws s3 cp s3://#{@bucket_name}/uploads/#{name} #{local_path}
190
183
  store.put("num1", String(1));
191
184
  Cloudformation.send(request, context, Cloudformation.SUCCESS, {Num: 1}, "Success", String(1));
192
185
  });
193
- CODE
194
- )
186
+ CODE
187
+ )
195
188
 
196
- switcher2_src = define_custom_resource(name: "ASGSelector2", code: <<-CODE
189
+ switcher2_src = define_custom_resource(name: 'ASGSelector2', code: <<-CODE
197
190
  store.get("num2", function(num) {
198
191
  num = parseInt(num);
199
192
  if (request.RequestType != "Delete")
@@ -210,224 +203,231 @@ sudo aws s3 cp s3://#{@bucket_name}/uploads/#{name} #{local_path}
210
203
  store.put("num2", String(1));
211
204
  Cloudformation.send(request, context, Cloudformation.SUCCESS, {Num: 1}, "Success", String(0));
212
205
  });
213
- CODE
214
- )
215
-
216
- size_1 = make_custom switcher1_src, name: "ASGSelector1Value" do
217
- DateTime update_time
218
- end
219
-
220
- size_2 = make_custom switcher2_src, name: "ASGSelector2Value" do
221
- DateTime update_time
222
- end
223
-
224
- make_autoscaling_group(
225
- type: spot,
226
- network: network,
227
- layer: "ecs",
228
- zone: spot.Zone,
229
- spot_price: price,
230
- min_size: size_1,
231
- ec2_sns_arn: ec2_sns_arn,
232
- ecs_cluster: ecs_cluster,
233
- eip: eip, &block)
234
-
235
- make_autoscaling_group(
236
- type: spot,
237
- network: network,
238
- layer: "ecs",
239
- zone: spot.Zone,
240
- spot_price: price,
241
- min_size: size_2,
242
- ec2_sns_arn: ec2_sns_arn,
243
- ecs_cluster: ecs_cluster,
244
- eip: eip)
245
- end
246
-
247
- def make_autoscaling_group(
248
- network:,
249
- layer:,
250
- zone:nil,
251
- type:"m3.medium",
252
- name:nil,
253
- elb:nil,
254
- min_size:1,
255
- max_size:min_size,
256
- vol_size:10,
257
- vol_type:"gp2",
258
- keypair:@master_key_name,
259
- has_public_ips:true,
260
- ingress:nil,
261
- egress:nil,
262
- machine_tag:nil,
263
- ec2_sns_arn:nil,
264
- ami_name:nil,
265
- ebs_root_device:nil,
266
- spot_price:nil,
267
- script: nil,
268
- ecs_cluster: nil,
269
- docker_username:"",
270
- docker_email:"",
271
- docker_password: "",
272
- eip:nil,
273
- policies:[],
274
- &block)
275
-
276
- if ami_name == nil
277
-
278
- @ami_lookup_resources ||= {}
279
-
280
- if !@ami_lookup_resources[type]
281
- @ami_lookup_resources[type] = make "Custom::AMILookup" do
282
- InstanceType type
283
- end
284
- end
285
-
286
- ami_name = @ami_lookup_resources[type]
287
- ebs_root_device = @ami_lookup_resources[type].RootDeviceName if ebs_root_device == nil
288
- end
289
-
290
- tasks = EC2Tasks.new(@bucket_name, &block)
291
-
292
- task_script = tasks.script
293
-
294
- ingress ||= [ allow_port(:all) ]
295
- egress ||= [ allow_port(:all) ]
296
- machine_tag ||= ref("AWS::StackName")
297
- name ||= make_default_resource_name("AutoScalingGroup")
298
- script ||= ""
299
-
300
- bucket_name = @bucket_name
301
-
302
- script += "\n#{task_script}\n"
303
-
304
- if ecs_cluster
305
- script += <<-ECS_START
306
-
307
- yum update
308
- yum groupinstall "Development Tools"
309
- yum install -y python screen git gcc-c++ ecs-init
310
- curl -sSL https://get.docker.com/ | sh
311
-
312
- cp /ecs.config /etc/ecs/ecs.config
313
-
314
- service docker start
315
- start ecs
316
-
317
- curl http://localhost:51678/v1/metadata > /home/ec2-user/ecs_info
318
-
319
- ECS_START
320
- end
321
-
322
- if eip
323
- script += <<-EIP_ALLOCATE
324
- aws ec2 associate-address --region `cat /etc/aws_region` --instance-id `curl http://169.254.169.254/latest/meta-data/instance-id` --allocation-id `cat /etc/eip_allocation_id`
325
- EIP_ALLOCATE
326
- end
327
-
328
- script += "\nservice spot-watcher start" if spot_price and ec2_sns_arn
329
-
330
- raise "ec2: ingress option needs to be an array" if !ingress.is_a? Array
331
- raise "ec2: egress option needs to be an array" if !egress.is_a? Array
332
-
333
- web_sec_group = make "AWS::EC2::SecurityGroup" do
334
- GroupDescription "Security group for layer: #{layer}"
335
- SecurityGroupIngress ingress
336
- SecurityGroupEgress egress
337
- VpcId network.vpc
338
- end
339
-
340
- wait_handle = make "AWS::CloudFormation::WaitConditionHandle"
341
-
342
- user_data = initscript(wait_handle, name, script)
343
-
344
- role_policy_doc = {
345
- "Version" => "2012-10-17",
346
- "Statement" => [{
347
- "Effect" => "Allow",
348
- "Principal" => {"Service" => ["ec2.amazonaws.com"]},
349
- "Action" => ["sts:AssumeRole"]
350
- }]
351
- }
352
-
353
- asg_role = make "AWS::IAM::Role" do
354
- AssumeRolePolicyDocument role_policy_doc
355
- Path "/"
356
- Policies [{
357
- "PolicyName" => "root",
358
- "PolicyDocument" => {
359
- "Version" => "2012-10-17",
360
- "Statement" => [{
361
- "Effect" => "Allow",
362
- "Action" => ["sns:Publish"],
363
- "Resource" => "*"
364
- },
365
- {
366
- "Effect" => "Allow",
367
- "Action" => ["s3:DeleteObject", "s3:GetObject", "s3:PutObject"],
368
- "Resource" => "arn:aws:s3:::#{bucket_name}/uploads/*"
369
- },
370
- {
371
- "Effect" => "Allow",
372
- "Action" => [
373
- "ec2:AllocateAddress",
374
- "ec2:AssociateAddress",
375
- "ec2:DescribeAddresses",
376
- "ec2:DisassociateAddress"
377
- ],
378
- "Resource" => "*"
379
- },
380
- {
381
- "Effect" => "Allow",
382
- "Action" => [
383
- "ecs:DeregisterContainerInstance",
384
- "ecs:DiscoverPollEndpoint",
385
- "ecs:Poll",
386
- "ecs:RegisterContainerInstance",
387
- "ecs:StartTelemetrySession",
388
- "ecs:Submit*",
389
- "ecr:GetAuthorizationToken",
390
- "ecr:BatchCheckLayerAvailability",
391
- "ecr:GetDownloadUrlForLayer",
392
- "ecr:BatchGetImage",
393
- "logs:CreateLogStream",
394
- "logs:PutLogEvents"
395
- ],
396
- "Resource": "*"
397
- }] + policies
398
- }
399
- }]
400
- end
401
-
402
- asg_profile = make "AWS::IAM::InstanceProfile" do
403
- Path "/"
404
- Roles [ asg_role ]
405
- end
406
-
407
- launch_config = make "AWS::AutoScaling::LaunchConfiguration" do
408
- AssociatePublicIpAddress has_public_ips
409
- KeyName keypair
410
- SecurityGroups [ web_sec_group ]
411
- ImageId ami_name
412
- UserData user_data
413
- InstanceType type
414
- IamInstanceProfile asg_profile
415
- SpotPrice spot_price if spot_price
416
- BlockDeviceMappings [{
417
- "DeviceName" => ebs_root_device,
418
- "Ebs" => {
419
- "VolumeType" => vol_type,
420
- "VolumeSize" => vol_size,
421
- }
422
- }]
423
- end
424
-
425
- zones_used = network.azs
426
- subnet_ids = network.subnets[layer].map { |x| x[:name] }
427
-
428
- if zone
429
- # if we only specified a single zone, then we have to do some processing
430
- res = define_custom_resource(name: "SubnetIdentifierCodeFor#{name}", code: <<-CODE
206
+ CODE
207
+ )
208
+
209
+ size_1 = make_custom switcher1_src, name: 'ASGSelector1Value' do
210
+ DateTime update_time
211
+ end
212
+
213
+ size_2 = make_custom switcher2_src, name: 'ASGSelector2Value' do
214
+ DateTime update_time
215
+ end
216
+
217
+ make_autoscaling_group(
218
+ type: spot,
219
+ network: network,
220
+ layer: 'ecs',
221
+ zone: spot.Zone,
222
+ spot_price: price,
223
+ min_size: size_1,
224
+ ec2_sns_arn: ec2_sns_arn,
225
+ ecs_cluster: ecs_cluster,
226
+ eip: eip, &block
227
+ )
228
+
229
+ make_autoscaling_group(
230
+ type: spot,
231
+ network: network,
232
+ layer: 'ecs',
233
+ zone: spot.Zone,
234
+ spot_price: price,
235
+ min_size: size_2,
236
+ ec2_sns_arn: ec2_sns_arn,
237
+ ecs_cluster: ecs_cluster,
238
+ eip: eip
239
+ )
240
+ end
241
+
242
+ def make_autoscaling_group(
243
+ network:,
244
+ layer:,
245
+ zone: nil,
246
+ type: 'm3.medium',
247
+ name: nil,
248
+ elb: nil,
249
+ min_size: 1,
250
+ max_size: min_size,
251
+ vol_size: 10,
252
+ vol_type: 'gp2',
253
+ keypair: @master_key_name,
254
+ has_public_ips: true,
255
+ ingress: nil,
256
+ egress: nil,
257
+ machine_tag: nil,
258
+ ec2_sns_arn: nil,
259
+ ami_name: nil,
260
+ ebs_root_device: nil,
261
+ spot_price: nil,
262
+ script: nil,
263
+ ecs_cluster: nil,
264
+ docker_username: '',
265
+ docker_email: '',
266
+ docker_password: '',
267
+ eip: nil,
268
+ policies: [],
269
+ &block
270
+ )
271
+
272
+ if ami_name.nil?
273
+
274
+ @ami_lookup_resources ||= {}
275
+
276
+ unless @ami_lookup_resources[type]
277
+ @ami_lookup_resources[type] = make 'Custom::AMILookup' do
278
+ InstanceType type
279
+ end
280
+ end
281
+
282
+ ami_name = @ami_lookup_resources[type]
283
+ if ebs_root_device.nil?
284
+ ebs_root_device = @ami_lookup_resources[type].RootDeviceName
285
+ end
286
+ end
287
+
288
+ tasks = EC2Tasks.new(@bucket_name, &block)
289
+
290
+ task_script = tasks.script
291
+
292
+ ingress ||= [allow_port(:all)]
293
+ egress ||= [allow_port(:all)]
294
+ machine_tag ||= ref('AWS::StackName')
295
+ name ||= make_default_resource_name('AutoScalingGroup')
296
+ script ||= ''
297
+
298
+ bucket_name = @bucket_name
299
+
300
+ script += "\n#{task_script}\n"
301
+
302
+ if ecs_cluster
303
+ script += <<~ECS_START
304
+
305
+ yum update
306
+ yum groupinstall "Development Tools"
307
+ yum install -y python screen git gcc-c++ ecs-init
308
+ curl -sSL https://get.docker.com/ | sh
309
+
310
+ cp /ecs.config /etc/ecs/ecs.config
311
+
312
+ service docker start
313
+ start ecs
314
+
315
+ curl http://localhost:51678/v1/metadata > /home/ec2-user/ecs_info
316
+
317
+ ECS_START
318
+ end
319
+
320
+ if eip
321
+ script += <<~EIP_ALLOCATE
322
+ aws ec2 associate-address --region `cat /etc/aws_region` --instance-id `curl http://169.254.169.254/latest/meta-data/instance-id` --allocation-id `cat /etc/eip_allocation_id`
323
+ EIP_ALLOCATE
324
+ end
325
+
326
+ script += "\nservice spot-watcher start" if spot_price && ec2_sns_arn
327
+
328
+ unless ingress.is_a? Array
329
+ raise 'ec2: ingress option needs to be an array'
330
+ end
331
+ raise 'ec2: egress option needs to be an array' unless egress.is_a? Array
332
+
333
+ web_sec_group = make 'AWS::EC2::SecurityGroup' do
334
+ GroupDescription "Security group for layer: #{layer}"
335
+ SecurityGroupIngress ingress
336
+ SecurityGroupEgress egress
337
+ VpcId network.vpc
338
+ end
339
+
340
+ wait_handle = make 'AWS::CloudFormation::WaitConditionHandle'
341
+
342
+ user_data = initscript(wait_handle, name, script)
343
+
344
+ role_policy_doc = {
345
+ 'Version' => '2012-10-17',
346
+ 'Statement' => [{
347
+ 'Effect' => 'Allow',
348
+ 'Principal' => { 'Service' => ['ec2.amazonaws.com'] },
349
+ 'Action' => ['sts:AssumeRole']
350
+ }]
351
+ }
352
+
353
+ asg_role = make 'AWS::IAM::Role' do
354
+ AssumeRolePolicyDocument role_policy_doc
355
+ Path '/'
356
+ Policies [{
357
+ 'PolicyName' => 'root',
358
+ 'PolicyDocument' => {
359
+ 'Version' => '2012-10-17',
360
+ 'Statement' => [{
361
+ 'Effect' => 'Allow',
362
+ 'Action' => ['sns:Publish'],
363
+ 'Resource' => '*'
364
+ },
365
+ {
366
+ 'Effect' => 'Allow',
367
+ 'Action' => ['s3:DeleteObject', 's3:GetObject', 's3:PutObject'],
368
+ 'Resource' => "arn:aws:s3:::#{bucket_name}/uploads/*"
369
+ },
370
+ {
371
+ 'Effect' => 'Allow',
372
+ 'Action' => [
373
+ 'ec2:AllocateAddress',
374
+ 'ec2:AssociateAddress',
375
+ 'ec2:DescribeAddresses',
376
+ 'ec2:DisassociateAddress'
377
+ ],
378
+ 'Resource' => '*'
379
+ },
380
+ {
381
+ 'Effect' => 'Allow',
382
+ 'Action' => [
383
+ 'ecs:DeregisterContainerInstance',
384
+ 'ecs:DiscoverPollEndpoint',
385
+ 'ecs:Poll',
386
+ 'ecs:RegisterContainerInstance',
387
+ 'ecs:StartTelemetrySession',
388
+ 'ecs:Submit*',
389
+ 'ecr:GetAuthorizationToken',
390
+ 'ecr:BatchCheckLayerAvailability',
391
+ 'ecr:GetDownloadUrlForLayer',
392
+ 'ecr:BatchGetImage',
393
+ 'logs:CreateLogStream',
394
+ 'logs:PutLogEvents'
395
+ ],
396
+ "Resource": '*'
397
+ }] + policies
398
+ }
399
+ }]
400
+ end
401
+
402
+ asg_profile = make 'AWS::IAM::InstanceProfile' do
403
+ Path '/'
404
+ Roles [asg_role]
405
+ end
406
+
407
+ launch_config = make 'AWS::AutoScaling::LaunchConfiguration' do
408
+ AssociatePublicIpAddress has_public_ips
409
+ KeyName keypair
410
+ SecurityGroups [web_sec_group]
411
+ ImageId ami_name
412
+ UserData user_data
413
+ InstanceType type
414
+ IamInstanceProfile asg_profile
415
+ SpotPrice spot_price if spot_price
416
+ BlockDeviceMappings [{
417
+ 'DeviceName' => ebs_root_device,
418
+ 'Ebs' => {
419
+ 'VolumeType' => vol_type,
420
+ 'VolumeSize' => vol_size
421
+ }
422
+ }]
423
+ end
424
+
425
+ zones_used = network.azs
426
+ subnet_ids = network.subnets[layer].map { |x| x[:name] }
427
+
428
+ if zone
429
+ # if we only specified a single zone, then we have to do some processing
430
+ res = define_custom_resource(name: "SubnetIdentifierCodeFor#{name}", code: <<-CODE
431
431
  var ids = {};
432
432
  var zones = request.ResourceProperties.SubnetZones;
433
433
  for (var i=0;i<zones.length;i++)
@@ -436,99 +436,98 @@ aws ec2 associate-address --region `cat /etc/aws_region` --instance-id `curl htt
436
436
  }
437
437
 
438
438
  Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success", ids[request.ResourceProperties.Zone]);
439
- CODE
440
- )
441
-
442
- identifier = make_custom res, name: "SubnetIdentifierFor#{name}" do
443
- SubnetIds network.subnets[layer].map { |x| x[:name] }
444
- SubnetZones network.subnets[layer].map { |x| x[:zone] }
445
- Zone zone
446
- end
447
-
448
- zones_used = [ zone ]
449
- subnet_ids = [ identifier ]
450
- end
451
-
452
-
453
- asg = make "AWS::AutoScaling::AutoScalingGroup", name: name do
454
- depends_on network.attachment
455
-
456
- AvailabilityZones zones_used
457
-
458
- Cooldown 30
459
- MinSize min_size
460
- MaxSize max_size
461
-
462
- VPCZoneIdentifier subnet_ids
463
-
464
- LaunchConfigurationName launch_config
465
- LoadBalancerNames [ elb ] if elb
466
-
467
- NotificationConfigurations [
468
- {
469
- "NotificationTypes" => [
470
- "autoscaling:EC2_INSTANCE_LAUNCH",
471
- "autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
472
- "autoscaling:EC2_INSTANCE_TERMINATE",
473
- "autoscaling:EC2_INSTANCE_TERMINATE_ERROR",
474
- "autoscaling:TEST_NOTIFICATION"
475
- ],
476
- "TopicARN" => ec2_sns_arn
477
- }
478
- ] if ec2_sns_arn
479
-
480
- file "/etc/aws_region", content: "{{ region }}", context: {
481
- region: ref("AWS::Region")
482
- }
483
-
484
- if ec2_sns_arn
485
- file "/etc/sns_arn", content: "{{ sns_arn }}", context: {
486
- sns_arn: ec2_sns_arn
487
- }
488
- end
489
-
490
- if eip
491
- file "/etc/eip_allocation_id", content: "{{ id }}", context: {
492
- id: eip.AllocationId
493
- }
494
- end
495
-
496
- if spot_price and ec2_sns_arn
497
- watcher = File.read( File.join( Gem.loaded_specs['sumomo'].full_gem_path, "data", "sumomo", "sources", "spot-watcher.sh" ) )
498
- poller = File.read( File.join( Gem.loaded_specs['sumomo'].full_gem_path, "data", "sumomo", "sources", "spot-watcher-poller.sh" ) )
499
-
500
- file "/etc/init.d/spot-watcher", content: watcher, mode: "000700"
501
- file "/bin/spot-watcher", content: poller, mode: "000700", context: {
502
- sns_arn: ec2_sns_arn,
503
- region: ref("AWS::Region")
504
- }
505
- end
506
-
507
- if ecs_cluster
508
- ecs_config = <<-CONFIG
509
- ECS_CLUSTER={{cluster_name}}
510
- ECS_ENGINE_AUTH_TYPE=docker
511
- ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"{{docker_username}}","password":"{{docker_password}}","email":"{{docker_email}}"}}
512
- CONFIG
513
-
514
- file "/ecs.config", content: ecs_config, context: {
515
- cluster_name: ecs_cluster,
516
- docker_username: docker_username,
517
- docker_password: docker_password,
518
- docker_email: docker_email
519
- }
520
- end
521
-
522
- tag "Name", machine_tag, propagate_at_launch: true
523
-
524
- tasks.tags.each do |t|
525
- tag t[0], t[1], propagate_at_launch: true
526
- end
527
-
528
- end
529
-
530
- asg
531
-
532
- end
533
- end
534
- end
439
+ CODE
440
+ )
441
+
442
+ identifier = make_custom res, name: "SubnetIdentifierFor#{name}" do
443
+ SubnetIds network.subnets[layer].map { |x| x[:name] }
444
+ SubnetZones network.subnets[layer].map { |x| x[:zone] }
445
+ Zone zone
446
+ end
447
+
448
+ zones_used = [zone]
449
+ subnet_ids = [identifier]
450
+ end
451
+
452
+ asg = make 'AWS::AutoScaling::AutoScalingGroup', name: name do
453
+ depends_on network.attachment
454
+
455
+ AvailabilityZones zones_used
456
+
457
+ Cooldown 30
458
+ MinSize min_size
459
+ MaxSize max_size
460
+
461
+ VPCZoneIdentifier subnet_ids
462
+
463
+ LaunchConfigurationName launch_config
464
+ LoadBalancerNames [elb] if elb
465
+
466
+ if ec2_sns_arn
467
+ NotificationConfigurations [
468
+ {
469
+ 'NotificationTypes' => [
470
+ 'autoscaling:EC2_INSTANCE_LAUNCH',
471
+ 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR',
472
+ 'autoscaling:EC2_INSTANCE_TERMINATE',
473
+ 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR',
474
+ 'autoscaling:TEST_NOTIFICATION'
475
+ ],
476
+ 'TopicARN' => ec2_sns_arn
477
+ }
478
+ ]
479
+ end
480
+
481
+ file '/etc/aws_region', content: '{{ region }}', context: {
482
+ region: ref('AWS::Region')
483
+ }
484
+
485
+ if ec2_sns_arn
486
+ file '/etc/sns_arn', content: '{{ sns_arn }}', context: {
487
+ sns_arn: ec2_sns_arn
488
+ }
489
+ end
490
+
491
+ if eip
492
+ file '/etc/eip_allocation_id', content: '{{ id }}', context: {
493
+ id: eip.AllocationId
494
+ }
495
+ end
496
+
497
+ if spot_price && ec2_sns_arn
498
+ watcher = File.read(File.join(Gem.loaded_specs['sumomo'].full_gem_path, 'data', 'sumomo', 'sources', 'spot-watcher.sh'))
499
+ poller = File.read(File.join(Gem.loaded_specs['sumomo'].full_gem_path, 'data', 'sumomo', 'sources', 'spot-watcher-poller.sh'))
500
+
501
+ file '/etc/init.d/spot-watcher', content: watcher, mode: '000700'
502
+ file '/bin/spot-watcher', content: poller, mode: '000700', context: {
503
+ sns_arn: ec2_sns_arn,
504
+ region: ref('AWS::Region')
505
+ }
506
+ end
507
+
508
+ if ecs_cluster
509
+ ecs_config = <<~CONFIG
510
+ ECS_CLUSTER={{cluster_name}}
511
+ ECS_ENGINE_AUTH_TYPE=docker
512
+ ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"{{docker_username}}","password":"{{docker_password}}","email":"{{docker_email}}"}}
513
+ CONFIG
514
+
515
+ file '/ecs.config', content: ecs_config, context: {
516
+ cluster_name: ecs_cluster,
517
+ docker_username: docker_username,
518
+ docker_password: docker_password,
519
+ docker_email: docker_email
520
+ }
521
+ end
522
+
523
+ tag 'Name', machine_tag, propagate_at_launch: true
524
+
525
+ tasks.tags.each do |t|
526
+ tag t[0], t[1], propagate_at_launch: true
527
+ end
528
+ end
529
+
530
+ asg
531
+ end
532
+ end
533
+ end