ciinabox-ecs 0.1.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.
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
+ }