sumomo 0.8.4 → 0.8.6

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.
@@ -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