ciinabox-ecs 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/ext/zip_helper.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'zip'
2
+
3
+
4
+ module Ciinabox
5
+ module Util
6
+ ###
7
+ ### taken from https://github.com/rubyzip/rubyzip/tree/master/samples
8
+ ###
9
+ class ZipFileGenerator
10
+
11
+ # Initialize with the directory to zip and the location of the output archive.
12
+ def initialize(input_dir, output_file)
13
+ @input_dir = input_dir
14
+ @output_file = output_file
15
+ end
16
+
17
+ # Zip the input directory.
18
+ def write
19
+ entries = Dir.entries(@input_dir) - %w(. ..)
20
+
21
+ ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
22
+ write_entries entries, '', zipfile
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # A helper method to make the recursion work.
29
+ def write_entries(entries, path, zipfile)
30
+ entries.each do |e|
31
+ zipfile_path = path == '' ? e : File.join(path, e)
32
+ disk_file_path = File.join(@input_dir, zipfile_path)
33
+ puts "Deflating #{disk_file_path}"
34
+
35
+ if File.directory? disk_file_path
36
+ recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
37
+ else
38
+ put_into_archive(disk_file_path, zipfile, zipfile_path)
39
+ end
40
+ end
41
+ end
42
+
43
+ def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
44
+ zipfile.mkdir zipfile_path
45
+ subdir = Dir.entries(disk_file_path) - %w(. ..)
46
+ write_entries subdir, zipfile_path, zipfile
47
+ end
48
+
49
+ def put_into_archive(disk_file_path, zipfile, zipfile_path)
50
+ zipfile.get_output_stream(zipfile_path) do |f|
51
+ f.write(File.open(disk_file_path, 'rb').read)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4
+ cd $DIR/..
5
+ rm -rf lib
6
+
7
+ function pipinstall () {
8
+ if [ $(which pip) == '' ]; then
9
+ echo "ERROR! No pip installed. Try installing either python3 pip or docker"
10
+ exit -1
11
+ fi
12
+
13
+ pip install aws-acm-cert-validator==0.1.11 -t lib
14
+ }
15
+
16
+ if [ $(which docker) == '' ]; then
17
+ pipinstall
18
+ else
19
+ docker run --rm -v $DIR/..:/dst -w /dst -u 1000 python:3-alpine pip install aws-acm-cert-validator==0.1.11 -t lib
20
+ fi
@@ -0,0 +1,121 @@
1
+ CloudFormation do
2
+
3
+ # Template metadata
4
+ AWSTemplateFormatVersion '2010-09-09'
5
+ Description "ciinabox - Bastion v#{ciinabox_version}"
6
+
7
+ # Parameters
8
+ Parameter('EnvironmentType'){ Type 'String' }
9
+ Parameter('EnvironmentName'){ Type 'String' }
10
+ Parameter('VPC'){ Type 'String' }
11
+ Parameter('RouteTablePrivateA'){ Type 'String' }
12
+ Parameter('RouteTablePrivateB'){ Type 'String' }
13
+ Parameter('SubnetPublicA'){ Type 'String' }
14
+ Parameter('SubnetPublicB'){ Type 'String' }
15
+ Parameter('SecurityGroupBackplane'){ Type 'String' }
16
+ Parameter('SecurityGroupOps'){ Type 'String' }
17
+ Parameter('SecurityGroupDev'){ Type 'String' }
18
+
19
+ # Global mappings
20
+ Mapping('EnvironmentType', Mappings['EnvironmentType'])
21
+ Mapping('bastionAMI', bastionAMI)
22
+
23
+ Resource('Role') {
24
+ Type 'AWS::IAM::Role'
25
+ Property('AssumeRolePolicyDocument', {
26
+ Statement: [
27
+ Effect: 'Allow',
28
+ Principal: { Service: [ 'ec2.amazonaws.com' ] },
29
+ Action: [ 'sts:AssumeRole' ]
30
+ ]
31
+ })
32
+ Property('Path','/')
33
+ Property('Policies', [
34
+ {
35
+ PolicyName: 'associate-address',
36
+ PolicyDocument: {
37
+ Statement: [
38
+ {
39
+ Effect: 'Allow',
40
+ Action: ['ec2:AssociateAddress'],
41
+ Resource: '*'
42
+ }
43
+ ]
44
+ }
45
+ },
46
+ {
47
+ PolicyName: 'describe-ec2-autoscaling',
48
+ PolicyDocument: {
49
+ Statement: [
50
+ {
51
+ Effect:'Allow',
52
+ Action: ['ec2:Describe*', 'autoscaling:Describe*' ],
53
+ Resource: [ '*' ]
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ ])
59
+ }
60
+
61
+ InstanceProfile('InstanceProfile') {
62
+ Path '/'
63
+ Roles [ Ref('Role') ]
64
+ }
65
+
66
+ Resource('BastionIPAddress') {
67
+ Type 'AWS::EC2::EIP'
68
+ Property('Domain', 'vpc')
69
+ }
70
+
71
+ Resource('LaunchConfig') {
72
+ Type 'AWS::AutoScaling::LaunchConfiguration'
73
+ DependsOn ['BastionIPAddress']
74
+ Property('ImageId', FnFindInMap('bastionAMI', Ref('AWS::Region'), 'ami'))
75
+ Property('KeyName', FnFindInMap('EnvironmentType', 'ciinabox', 'KeyName'))
76
+ Property('AssociatePublicIpAddress',true)
77
+ Property('IamInstanceProfile', Ref('InstanceProfile'))
78
+ FnFindInMap('EnvironmentType', 'ciinabox', 'KeyName')
79
+ Property('SecurityGroups', [ Ref('SecurityGroupDev'),
80
+ Ref('SecurityGroupBackplane'),
81
+ Ref('SecurityGroupOps') ])
82
+ Property('InstanceType', bastionInstanceType)
83
+ Property('UserData', FnBase64(FnJoin('',[
84
+ "#!/bin/bash\n",
85
+ 'export NEW_HOSTNAME=', Ref('EnvironmentName'),"-bastion-xx-`/opt/aws/bin/ec2-metadata --instance-id|/usr/bin/awk '{print $2}'`", "\n",
86
+ "echo \"NEW_HOSTNAME=$NEW_HOSTNAME\" \n",
87
+ "hostname $NEW_HOSTNAME\n",
88
+ "sed -i \"s/^HOSTNAME=.*/HOSTNAME=$NEW_HOSTNAME/\" /etc/sysconfig/network\n",
89
+ 'aws --region ', Ref('AWS::Region'), ' ec2 associate-address --allocation-id ', FnGetAtt('BastionIPAddress','AllocationId') ," --instance-id $(curl http://169.254.169.254/2014-11-05/meta-data/instance-id -s)\n",
90
+ ])))
91
+ }
92
+
93
+ AutoScalingGroup('AutoScaleGroup') {
94
+ UpdatePolicy('AutoScalingRollingUpdate', {
95
+ 'MinInstancesInService' => '0',
96
+ 'MaxBatchSize' => '1',
97
+ })
98
+ LaunchConfigurationName Ref('LaunchConfig')
99
+ HealthCheckGracePeriod '500'
100
+ HealthCheckType 'EC2'
101
+ MinSize 1
102
+ MaxSize 1
103
+ VPCZoneIdentifier [ Ref('SubnetPublicA') ]
104
+ addTag('Name', FnJoin('-',[Ref('EnvironmentName'), 'bastion' , 'xx']), true)
105
+ addTag('Environment', Ref('EnvironmentName'), true)
106
+ addTag('EnvironmentType', Ref('EnvironmentType'), true)
107
+ addTag('Role', 'bastion', true)
108
+ }
109
+
110
+ Resource('BastionRecordSet') {
111
+ Type 'AWS::Route53::RecordSet'
112
+ DependsOn ['BastionIPAddress']
113
+ Property('HostedZoneName', FnJoin('', [ dns_domain, '.' ]))
114
+ Property('Comment', 'Bastion record set')
115
+ Property('Name', FnJoin('', [ 'bastion.', Ref('EnvironmentName') , '.', dns_domain, '.' ]))
116
+ Property('Type', 'A')
117
+ Property('TTL', '60')
118
+ Property('ResourceRecords', [ Ref('BastionIPAddress') ] )
119
+ }
120
+
121
+ end
@@ -0,0 +1,159 @@
1
+ require_relative '../ext/policies'
2
+
3
+ CloudFormation do
4
+
5
+ # Template metadata
6
+ AWSTemplateFormatVersion '2010-09-09'
7
+ Description "ciinabox ECS #{Gem.latest_spec_for('ciinabox-ecs').version.to_s} - v#{ciinabox_version}"
8
+
9
+ Resource(cluster_name) {
10
+ Type 'AWS::ECS::Cluster'
11
+ }
12
+
13
+ # VPC Stack
14
+ Resource('VPCStack') {
15
+ Type 'AWS::CloudFormation::Stack'
16
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/vpc.json")
17
+ Property('TimeoutInMinutes', 10)
18
+ }
19
+
20
+ # ECS Cluster Stack
21
+ Resource('ECSStack') {
22
+ Type 'AWS::CloudFormation::Stack'
23
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/ecs-cluster.json")
24
+ Property('TimeoutInMinutes', 10)
25
+ Property('Parameters',{
26
+ ECSCluster: Ref(cluster_name),
27
+ VPC: FnGetAtt('VPCStack', 'Outputs.VPCId'),
28
+ RouteTablePrivateA: FnGetAtt('VPCStack', 'Outputs.RouteTablePrivateA'),
29
+ RouteTablePrivateB: FnGetAtt('VPCStack', 'Outputs.RouteTablePrivateB'),
30
+ SubnetPublicA: FnGetAtt('VPCStack', 'Outputs.SubnetPublicA'),
31
+ SubnetPublicB: FnGetAtt('VPCStack', 'Outputs.SubnetPublicB'),
32
+ SecurityGroupBackplane: FnGetAtt('VPCStack', 'Outputs.SecurityGroupBackplane')
33
+ })
34
+ }
35
+
36
+ # ECS Services Stack
37
+ Resource('ECSServicesStack') {
38
+ Type 'AWS::CloudFormation::Stack'
39
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/ecs-services.json")
40
+ Property('TimeoutInMinutes', 15)
41
+ Property('Parameters',{
42
+ ECSCluster: Ref(cluster_name),
43
+ VPC: FnGetAtt('VPCStack', 'Outputs.VPCId'),
44
+ SubnetPublicA: FnGetAtt('VPCStack', 'Outputs.SubnetPublicA'),
45
+ SubnetPublicB: FnGetAtt('VPCStack', 'Outputs.SubnetPublicB'),
46
+ ECSSubnetPrivateA: FnGetAtt('ECSStack', 'Outputs.ECSSubnetPrivateA'),
47
+ ECSSubnetPrivateB: FnGetAtt('ECSStack', 'Outputs.ECSSubnetPrivateB'),
48
+ ECSENIPrivateIpAddress: FnGetAtt('ECSStack', 'Outputs.ECSENIPrivateIpAddress'),
49
+ SecurityGroupBackplane: FnGetAtt('VPCStack', 'Outputs.SecurityGroupBackplane'),
50
+ SecurityGroupOps: FnGetAtt('VPCStack', 'Outputs.SecurityGroupOps'),
51
+ SecurityGroupDev: FnGetAtt('VPCStack', 'Outputs.SecurityGroupDev'),
52
+ SecurityGroupNatGateway: FnGetAtt('VPCStack', 'Outputs.SecurityGroupNatGateway'),
53
+ CRAcmCertArn: FnGetAtt('LambdasStack','Outputs.LambdaCRIssueACMCertificateArn')
54
+ })
55
+ }
56
+
57
+ #These are the commona params for use below in "foreign templates
58
+ base_params = {
59
+ VPC: FnGetAtt('VPCStack', 'Outputs.VPCId'),
60
+ SecurityGroupBackplane: FnGetAtt('VPCStack', 'Outputs.SecurityGroupBackplane'),
61
+ SecurityGroupOps: FnGetAtt('VPCStack', 'Outputs.SecurityGroupOps'),
62
+ SecurityGroupDev: FnGetAtt('VPCStack', 'Outputs.SecurityGroupDev'),
63
+ EnvironmentType: 'ciinabox',
64
+ EnvironmentName: 'ciinabox'
65
+ }
66
+
67
+ availability_zones.each do |az|
68
+ base_params.merge!("SubnetPublic#{az}" => FnGetAtt('VPCStack', "Outputs.SubnetPublic#{az}"))
69
+ base_params.merge!("RouteTablePrivate#{az}" => FnGetAtt('VPCStack', "Outputs.RouteTablePrivate#{az}"))
70
+ end
71
+
72
+ # Lambda functions stack
73
+ Resource('LambdasStack') do
74
+ Type 'AWS::CloudFormation::Stack'
75
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/lambdas.json")
76
+ Property('Parameters', base_params)
77
+ end
78
+
79
+
80
+ # Bastion if required
81
+ Resource('BastionStack') do
82
+ Type 'AWS::CloudFormation::Stack'
83
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/bastion.json")
84
+ Property('Parameters', base_params)
85
+ end if include_bastion_stack
86
+
87
+
88
+
89
+ #Foreign templates
90
+ #e.g CIINABOXES_DIR/CIINABOX/templates/x.rb
91
+ #for f in foreign templates do:
92
+ # new stack
93
+
94
+ if defined? extra_stacks
95
+ extra_stacks.each do | stack, details |
96
+
97
+ #Note: each time we use base_params we need to clone
98
+ #assignment for hash is a shalow copy
99
+ #we could also use z = Hash[x] or Marshal.load(Marshal.dump(original_hash))
100
+ #also add any params from the config
101
+ params = base_params.clone
102
+ params.merge!details["parameters"] if details["parameters"]
103
+
104
+ #if file_name not applied assume stack name = file_name
105
+ file_name = details["file_name"] ? details["file_name"] : stack
106
+
107
+ Resource(stack) {
108
+ Type 'AWS::CloudFormation::Stack'
109
+ Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/ciinabox/#{ciinabox_version}/#{file_name}.json")
110
+ Property('Parameters', params)
111
+ }
112
+
113
+ if details['outputs']
114
+ details['outputs'].each do |output|
115
+ Output(output) {
116
+ Value(FnGetAtt(stack, "Outputs.#{output}"))
117
+ }
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ Output("Region") {
124
+ Value(Ref('AWS::Region'))
125
+ }
126
+
127
+ Output("VPCId") {
128
+ Value(FnGetAtt('VPCStack', 'Outputs.VPCId'))
129
+ }
130
+
131
+ availability_zones.each do |az|
132
+ Output("PublicSubnet#{az}") {
133
+ Value(FnGetAtt('VPCStack', "Outputs.SubnetPublic#{az}"))
134
+ }
135
+ end
136
+
137
+ availability_zones.each do |az|
138
+ Output("ECSPrivateSubnet#{az}") {
139
+ Value(FnGetAtt('ECSStack', "Outputs.ECSSubnetPrivate#{az}"))
140
+ }
141
+ end
142
+
143
+ Output("SecurityGroup") {
144
+ Value(FnGetAtt('VPCStack', 'Outputs.SecurityGroupBackplane'))
145
+ }
146
+
147
+ Output("ECSRole") {
148
+ Value(FnGetAtt('ECSStack', 'Outputs.ECSRole'))
149
+ }
150
+
151
+ Output("ECSInstanceProfile") {
152
+ Value(FnGetAtt('ECSStack', 'Outputs.ECSInstanceProfile'))
153
+ }
154
+
155
+ Output('DefaultSSLCertificate'){
156
+ Value(FnGetAtt('ECSServicesStack','Outputs.DefaultSSLCertificate'))
157
+ }
158
+
159
+ end
@@ -0,0 +1,252 @@
1
+ require 'cfndsl'
2
+
3
+ volume_name = "ECSDataVolume"
4
+ if defined? ecs_data_volume_name
5
+ volume_name = ecs_data_volume_name
6
+ end
7
+
8
+ volume_size = 100
9
+ if defined? ecs_data_volume_size
10
+ volume_size = ecs_data_volume_size
11
+ end
12
+
13
+ CloudFormation {
14
+
15
+ # Template metadata
16
+ AWSTemplateFormatVersion "2010-09-09"
17
+ Description "ciinabox - ECS Cluster v#{ciinabox_version}"
18
+
19
+ # Parameters
20
+ Parameter("ECSCluster") {Type 'String'}
21
+ Parameter("VPC") {Type 'String'}
22
+ Parameter("RouteTablePrivateA") {Type 'String'}
23
+ Parameter("RouteTablePrivateB") {Type 'String'}
24
+ Parameter("SubnetPublicA") {Type 'String'}
25
+ Parameter("SubnetPublicB") {Type 'String'}
26
+ Parameter("SecurityGroupBackplane") {Type 'String'}
27
+
28
+
29
+ # Global mappings
30
+ Mapping('EnvironmentType', Mappings['EnvironmentType'])
31
+ Mapping('ecsAMI', ecs_ami)
32
+
33
+ availability_zones.each do |az|
34
+ Resource("SubnetPrivate#{az}") {
35
+ Type 'AWS::EC2::Subnet'
36
+ Property('VpcId', Ref('VPC'))
37
+ Property('CidrBlock', FnJoin("", [FnFindInMap('EnvironmentType', 'ciinabox', 'NetworkPrefix'), ".", FnFindInMap('EnvironmentType', 'ciinabox', 'StackOctet'), ".", ecs["SubnetOctet#{az}"], ".0/", FnFindInMap('EnvironmentType', 'ciinabox', 'SubnetMask')]))
38
+ Property('AvailabilityZone', FnSelect(azId[az], FnGetAZs(Ref("AWS::Region"))))
39
+ }
40
+ end
41
+
42
+ availability_zones.each do |az|
43
+ Resource("SubnetRouteTableAssociationPrivate#{az}") {
44
+ Type 'AWS::EC2::SubnetRouteTableAssociation'
45
+ Property('SubnetId', Ref("SubnetPrivate#{az}"))
46
+ Property('RouteTableId', Ref("RouteTablePrivate#{az}"))
47
+ }
48
+ end
49
+
50
+ ecs_iam_role_permissions = ecs_iam_role_permissions_default
51
+ if defined? ecs_iam_role_permissions_extras
52
+ ecs_iam_role_permissions = ecs_iam_role_permissions + ecs_iam_role_permissions_extras
53
+ end
54
+
55
+ ecs_role_policies = ecs_iam_role_permissions.collect {|p|
56
+ {
57
+ PolicyName: p['name'],
58
+ PolicyDocument: {
59
+ Statement: [
60
+ {
61
+ Effect: 'Allow',
62
+ Action: p['actions'],
63
+ Resource: p['resource'] || '*'
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ }
69
+
70
+ has_ciinabox_role_predefined = defined? ciinabox_iam_role_name
71
+
72
+ Resource("Role") {
73
+ Type 'AWS::IAM::Role'
74
+ Property('AssumeRolePolicyDocument', {
75
+ Statement: [
76
+ Effect: 'Allow',
77
+ Principal: {Service: ['ec2.amazonaws.com']},
78
+ Action: ['sts:AssumeRole']
79
+ ]
80
+ })
81
+ Property('Path', '/')
82
+ Property('Policies', ecs_role_policies)
83
+ } unless has_ciinabox_role_predefined
84
+
85
+ InstanceProfile("InstanceProfile") {
86
+ Path '/'
87
+ Roles [Ref('Role')] unless has_ciinabox_role_predefined
88
+ Roles [ciinabox_iam_role_name] if has_ciinabox_role_predefined
89
+ }
90
+
91
+ EC2_Volume(volume_name) {
92
+ DeletionPolicy 'Snapshot'
93
+ Size volume_size
94
+ VolumeType 'gp2'
95
+ if defined? ecs_data_volume_snapshot
96
+ SnapshotId ecs_data_volume_snapshot
97
+ end
98
+ AvailabilityZone FnSelect(0, FnGetAZs(""))
99
+ addTag('Name', 'ciinabox-ecs-data-xx')
100
+ addTag('Environment', 'ciinabox')
101
+ addTag('EnvironmentType', 'ciinabox')
102
+ addTag('Role', 'ciinabox-data')
103
+ if data_volume_shelvery_backups
104
+ addTag('shelvery:create_backup','true')
105
+ addTag('shelvery:config:shelvery_keep_daily_backups', data_volume_retain_daily_backups)
106
+ addTag('shelvery:config:shelvery_keep_weekly_backups', data_volume_retain_weekly_backups)
107
+ addTag('shelvery:config:shelvery_keep_monthly_backups', data_volume_reatin_monthly_backups)
108
+ end
109
+
110
+ }
111
+
112
+ ecs_block_device_mapping = []
113
+ user_data_init_devices = ''
114
+ if defined? ecs_root_volume_size and ecs_root_volume_size > 8
115
+ ecs_block_device_mapping << {
116
+ "DeviceName" => "/dev/xvda",
117
+ "Ebs" => {
118
+ "VolumeSize" => ecs_root_volume_size
119
+ }
120
+ }
121
+ end
122
+
123
+ if defined? ecs_docker_volume_size and ecs_docker_volume_size > 22
124
+ ecs_block_device_mapping << {
125
+ "DeviceName" => "/dev/xvdcz",
126
+ "Ebs" => {
127
+ "VolumeSize" => ecs_docker_volume_size,
128
+ "VolumeType" => "gp2"
129
+ }
130
+ }
131
+ if (defined? 'ecs_docker_volume_volumemount') and (binding.eval('ecs_docker_volume_volumemount') == true)
132
+ user_data_init_devices = "mkfs.ext4 /dev/xvdcz && " +
133
+ "mount /dev/xvdcz /var/lib/docker\n"
134
+ end
135
+ end
136
+
137
+ ecs_allow_sg_ingress = [
138
+ { IpProtocol: 'tcp', FromPort: '32768', ToPort: '65535', CidrIp: FnJoin( "", [ FnFindInMap('EnvironmentType','ciinabox','NetworkPrefix'),".", FnFindInMap('EnvironmentType','ciinabox','StackOctet'), ".0.0/",FnFindInMap('EnvironmentType','ciinabox','StackMask') ] ) },
139
+ ]
140
+
141
+ Resource("SecurityGroupECS") {
142
+ Type 'AWS::EC2::SecurityGroup'
143
+ Property('VpcId', Ref('VPC'))
144
+ Property('GroupDescription', 'ECS SG')
145
+ Property('SecurityGroupIngress', ecs_allow_sg_ingress)
146
+ }
147
+
148
+ ecs_sgs = [Ref('SecurityGroupBackplane'), Ref('SecurityGroupECS')]
149
+
150
+ Resource("ECSENI") {
151
+ Type 'AWS::EC2::NetworkInterface'
152
+ Property('SubnetId', Ref('SubnetPrivateA'))
153
+ Property('GroupSet', ecs_sgs)
154
+ }
155
+
156
+ LaunchConfiguration(:LaunchConfig) {
157
+ ImageId FnFindInMap('ecsAMI', Ref('AWS::Region'), 'ami')
158
+ IamInstanceProfile Ref('InstanceProfile')
159
+ KeyName FnFindInMap('EnvironmentType', 'ciinabox', 'KeyName')
160
+ SecurityGroups ecs_sgs
161
+ InstanceType FnFindInMap('EnvironmentType', 'ciinabox', 'ECSInstanceType')
162
+ if not ecs_block_device_mapping.empty?
163
+ Property("BlockDeviceMappings", ecs_block_device_mapping)
164
+ end
165
+ UserData FnBase64(FnJoin("", [
166
+ "#!/bin/bash\n",
167
+ "echo ECS_CLUSTER=", Ref('ECSCluster'), " >> /etc/ecs/ecs.config\n",
168
+ "INSTANCE_ID=$(echo `/opt/aws/bin/ec2-metadata -i | cut -f2 -d:`)\n",
169
+ "PRIVATE_IP=`/opt/aws/bin/ec2-metadata -o | cut -f2 -d: | cut -f2 -d-`\n",
170
+ "yum install -y python-pip\n",
171
+ "python-pip install --upgrade awscli\n",
172
+ "/usr/local/bin/aws --region ", Ref("AWS::Region"), " ec2 attach-volume --volume-id ", Ref(volume_name), " --instance-id ${INSTANCE_ID} --device /dev/sdf\n",
173
+ "echo 'waiting for ECS Data volume to attach' && sleep 20\n",
174
+ "/usr/local/bin/aws --region ", Ref("AWS::Region"), " ec2 attach-network-interface --network-interface-id ", Ref('ECSENI'), " --instance-id ${INSTANCE_ID} --device-index 1\n",
175
+ "echo 'waiting for ECS ENI to attach' && sleep 20\n",
176
+ "echo '/dev/xvdf /data ext4 defaults,nofail 0 2' >> /etc/fstab\n",
177
+ "mkdir -p /data\n",
178
+ "mount /data && echo \"ECS Data volume already formatted\" || mkfs -t ext4 /dev/xvdf\n",
179
+ "mount -a && echo 'mounting ECS Data volume' || echo 'failed to mount ECS Data volume'\n",
180
+ "#{user_data_init_devices}",
181
+ "export BOOTSTRAP=/data/bootstrap \n",
182
+ "if [ ! -e \"$BOOTSTRAP\" ]; then echo \"boostrapping\"; chmod -R 777 /data; mkdir -p /data/jenkins; chown -R 1000:1000 /data/jenkins; touch $BOOTSTRAP; fi \n",
183
+ "ifconfig eth0 mtu 1500\n",
184
+ "curl https://amazon-ssm-", Ref("AWS::Region"), ".s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm -o /tmp/amazon-ssm-agent.rpm\n",
185
+ "yum install -y /tmp/amazon-ssm-agent.rpm\n",
186
+ "stop ecs\n",
187
+ "service docker stop\n",
188
+ "service docker start\n",
189
+ "start ecs\n",
190
+ "echo 'done!!!!'\n"
191
+ ]))
192
+ }
193
+
194
+ AutoScalingGroup("AutoScaleGroup") {
195
+ UpdatePolicy("AutoScalingRollingUpdate", {
196
+ "MinInstancesInService" => "0",
197
+ "MaxBatchSize" => "1",
198
+ })
199
+ LaunchConfigurationName Ref('LaunchConfig')
200
+ HealthCheckGracePeriod '500'
201
+ MinSize 1
202
+ MaxSize 1
203
+ DesiredCapacity 1
204
+ VPCZoneIdentifier [Ref('SubnetPrivateA')]
205
+ addTag("Name", FnJoin("", ["ciinabox-ecs-xx"]), true)
206
+ addTag("Environment", 'ciinabox', true)
207
+ addTag("EnvironmentType", 'ciinabox', true)
208
+ addTag("Role", "ciinabox-ecs", true)
209
+ }
210
+
211
+ if defined? scale_up_schedule
212
+ Resource("ScheduledActionUp") {
213
+ Type 'AWS::AutoScaling::ScheduledAction'
214
+ Property('AutoScalingGroupName', Ref('AutoScaleGroup'))
215
+ Property('MinSize', '1')
216
+ Property('MaxSize', '1')
217
+ Property('DesiredCapacity', '1')
218
+ Property('Recurrence', scale_up_schedule)
219
+ }
220
+ end
221
+
222
+ if defined? scale_down_schedule
223
+ Resource("ScheduledActionDown") {
224
+ Type 'AWS::AutoScaling::ScheduledAction'
225
+ Property('AutoScalingGroupName', Ref('AutoScaleGroup'))
226
+ Property('MinSize', '0')
227
+ Property('MaxSize', '0')
228
+ Property('DesiredCapacity', '0')
229
+ Property('Recurrence', scale_down_schedule)
230
+ }
231
+ end
232
+
233
+ availability_zones.each do |az|
234
+ Output("ECSSubnetPrivate#{az}") {
235
+ Value(Ref("SubnetPrivate#{az}"))
236
+ }
237
+ end
238
+
239
+ Output("ECSRole") {
240
+ Value(Ref('Role')) unless has_ciinabox_role_predefined
241
+ Value(ciinabox_iam_role_name) if has_ciinabox_role_predefined
242
+ }
243
+
244
+ Output("ECSENIPrivateIpAddress") {
245
+ Value(FnGetAtt('ECSENI', 'PrimaryPrivateIpAddress'))
246
+ }
247
+
248
+ Output("ECSInstanceProfile") {
249
+ Value(Ref('InstanceProfile'))
250
+ }
251
+
252
+ }