ciinabox-ecs 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +458 -0
- data/Rakefile +649 -0
- data/bin/Rakefile +1 -0
- data/bin/ciinabox-ecs +2 -0
- data/bin/ciinabox-ecs.rb +60 -0
- data/config/ciinabox_params.yml.erb +71 -0
- data/config/default_lambdas.yml +26 -0
- data/config/default_params.yml +303 -0
- data/config/default_params.yml.example +124 -0
- data/config/default_services.yml +62 -0
- data/ext/common_helper.rb +21 -0
- data/ext/config/managed_policies.yml +156 -0
- data/ext/helper.rb +29 -0
- data/ext/policies.rb +53 -0
- data/ext/zip_helper.rb +57 -0
- data/lambdas/acm_issuer_validator/lib/install.sh +20 -0
- data/templates/bastion.rb +121 -0
- data/templates/ciinabox.rb +159 -0
- data/templates/ecs-cluster.rb +252 -0
- data/templates/ecs-services.rb +340 -0
- data/templates/lambdas.rb +172 -0
- data/templates/services/bitbucket.rb +81 -0
- data/templates/services/drone.rb +394 -0
- data/templates/services/hawtio.rb +100 -0
- data/templates/services/icinga2.rb +79 -0
- data/templates/services/jenkins.rb +209 -0
- data/templates/services/nexus.rb +96 -0
- data/templates/vpc.rb +290 -0
- metadata +144 -0
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
|
+
}
|