rubycfn 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/Gemfile.lock +2 -2
- data/README.md +5 -6
- data/lib/rubycfn/version.rb +1 -1
- data/templates/.gitignore +4 -0
- data/templates/.rubocop.yml +3 -0
- data/templates/README.md +1 -1
- data/templates/Rakefile +3 -4
- data/templates/config.yaml +3 -0
- data/templates/lib/aws_helper/compiler.rb +1 -1
- data/templates/lib/aws_helper/dependencies.rb +11 -3
- data/templates/lib/aws_helper/upload_stack.rb +3 -10
- data/templates/lib/core/applications.rb +117 -28
- data/templates/lib/core/dependencies.rb +3 -3
- data/templates/lib/core/deploy.rb +2 -2
- data/templates/lib/core/init.rb +60 -12
- data/templates/lib/main.rb +1 -1
- data/templates/lib/shared_concerns/global_variables.rb +1 -1
- data/templates/lib/shared_concerns/shared_methods.rb +2 -2
- data/templates/lib/stacks/ecs_stack/ecs_cluster.rb +276 -276
- data/templates/lib/stacks/ecs_stack/lifecycle_hook.rb +162 -160
- data/templates/lib/stacks/ecs_stack/load_balancer.rb +52 -50
- data/templates/lib/stacks/ecs_stack/main.rb +2 -0
- data/templates/lib/stacks/ecs_stack/rollback.rb +77 -0
- metadata +4 -3
- data/templates/bootstrap/dependency_stack.rb +0 -49
@@ -3,185 +3,187 @@ module EcsStack
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
## Drains instances on termination
|
5
5
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
"Endpoint": :lifecycle_handler_function.ref(:arn),
|
13
|
-
"Protocol": "lambda"
|
14
|
-
}
|
15
|
-
]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
resource :instance_terminating_hook,
|
20
|
-
depends_on: :ecs_lifecycle_notification_topic,
|
21
|
-
type: "AWS::AutoScaling::LifecycleHook" do |r|
|
22
|
-
r.property(:auto_scaling_group_name) { :ecs_auto_scaling_group.ref }
|
23
|
-
r.property(:default_result) { "ABANDON" }
|
24
|
-
r.property(:heartbeat_timeout) { 900 }
|
25
|
-
r.property(:lifecycle_transition) { "autoscaling:EC2_INSTANCE_TERMINATING" }
|
26
|
-
r.property(:notification_target_arn) { :ecs_lifecycle_notification_topic.ref }
|
27
|
-
r.property(:role_arn) { :autoscaling_notification_role.ref(:arn) }
|
28
|
-
end
|
29
|
-
|
30
|
-
resource :autoscaling_notification_role,
|
31
|
-
type: "AWS::IAM::Role" do |r|
|
32
|
-
r.property(:assume_role_policy_document) do
|
33
|
-
{
|
34
|
-
"Version": "2012-10-17",
|
35
|
-
"Statement": [
|
6
|
+
unless infra_config["environments"][environment]["cluster_size"].nil? || infra_config["environments"][environment]["cluster_size"].to_i.zero?
|
7
|
+
resource :ecs_lifecycle_notification_topic,
|
8
|
+
depends_on: :lifecycle_handler_function,
|
9
|
+
type: "AWS::SNS::Topic" do |r|
|
10
|
+
r.property(:subscription) do
|
11
|
+
[
|
36
12
|
{
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"Service": [
|
40
|
-
"autoscaling.amazonaws.com"
|
41
|
-
]
|
42
|
-
},
|
43
|
-
"Action": [
|
44
|
-
"sts:AssumeRole"
|
45
|
-
]
|
13
|
+
"Endpoint": :lifecycle_handler_function.ref(:arn),
|
14
|
+
"Protocol": "lambda"
|
46
15
|
}
|
47
16
|
]
|
48
|
-
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
resource :instance_terminating_hook,
|
21
|
+
depends_on: :ecs_lifecycle_notification_topic,
|
22
|
+
type: "AWS::AutoScaling::LifecycleHook" do |r|
|
23
|
+
r.property(:auto_scaling_group_name) { :ecs_auto_scaling_group.ref }
|
24
|
+
r.property(:default_result) { "ABANDON" }
|
25
|
+
r.property(:heartbeat_timeout) { 900 }
|
26
|
+
r.property(:lifecycle_transition) { "autoscaling:EC2_INSTANCE_TERMINATING" }
|
27
|
+
r.property(:notification_target_arn) { :ecs_lifecycle_notification_topic.ref }
|
28
|
+
r.property(:role_arn) { :autoscaling_notification_role.ref(:arn) }
|
49
29
|
end
|
50
|
-
r.property(:managed_policy_arns) { ["arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole"] }
|
51
|
-
end
|
52
30
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
[
|
31
|
+
resource :autoscaling_notification_role,
|
32
|
+
type: "AWS::IAM::Role" do |r|
|
33
|
+
r.property(:assume_role_policy_document) do
|
57
34
|
{
|
58
|
-
"
|
59
|
-
"
|
60
|
-
|
61
|
-
|
62
|
-
{
|
63
|
-
"
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
"ec2:DescribeHosts",
|
73
|
-
"ecs:ListContainerInstances",
|
74
|
-
"ecs:SubmitContainerStateChange",
|
75
|
-
"ecs:SubmitTaskStateChange",
|
76
|
-
"ecs:DescribeContainerInstances",
|
77
|
-
"ecs:UpdateContainerInstancesState",
|
78
|
-
"ecs:ListTasks",
|
79
|
-
"ecs:DescribeTasks",
|
80
|
-
"sns:Publish",
|
81
|
-
"sns:ListSubscriptions"
|
82
|
-
],
|
83
|
-
"Resource": "*"
|
84
|
-
}
|
85
|
-
]
|
86
|
-
}
|
35
|
+
"Version": "2012-10-17",
|
36
|
+
"Statement": [
|
37
|
+
{
|
38
|
+
"Effect": "Allow",
|
39
|
+
"Principal": {
|
40
|
+
"Service": [
|
41
|
+
"autoscaling.amazonaws.com"
|
42
|
+
]
|
43
|
+
},
|
44
|
+
"Action": [
|
45
|
+
"sts:AssumeRole"
|
46
|
+
]
|
47
|
+
}
|
48
|
+
]
|
87
49
|
}
|
88
|
-
|
50
|
+
end
|
51
|
+
r.property(:managed_policy_arns) { ["arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole"] }
|
89
52
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
53
|
+
|
54
|
+
resource :lambda_execution_role,
|
55
|
+
type: "AWS::IAM::Role" do |r|
|
56
|
+
r.property(:policies) do
|
57
|
+
[
|
94
58
|
{
|
95
|
-
"
|
96
|
-
"
|
97
|
-
"
|
98
|
-
|
59
|
+
"PolicyName": "lambda-inline",
|
60
|
+
"PolicyDocument": {
|
61
|
+
"Version": "2012-10-17",
|
62
|
+
"Statement": [
|
63
|
+
{
|
64
|
+
"Effect": "Allow",
|
65
|
+
"Action": [
|
66
|
+
"autoscaling:CompleteLifecycleAction",
|
67
|
+
"logs:CreateLogGroup",
|
68
|
+
"logs:CreateLogStream",
|
69
|
+
"logs:PutLogEvents",
|
70
|
+
"ec2:DescribeInstances",
|
71
|
+
"ec2:DescribeInstanceAttribute",
|
72
|
+
"ec2:DescribeInstanceStatus",
|
73
|
+
"ec2:DescribeHosts",
|
74
|
+
"ecs:ListContainerInstances",
|
75
|
+
"ecs:SubmitContainerStateChange",
|
76
|
+
"ecs:SubmitTaskStateChange",
|
77
|
+
"ecs:DescribeContainerInstances",
|
78
|
+
"ecs:UpdateContainerInstancesState",
|
79
|
+
"ecs:ListTasks",
|
80
|
+
"ecs:DescribeTasks",
|
81
|
+
"sns:Publish",
|
82
|
+
"sns:ListSubscriptions"
|
83
|
+
],
|
84
|
+
"Resource": "*"
|
85
|
+
}
|
99
86
|
]
|
100
|
-
}
|
101
|
-
"Action": [
|
102
|
-
"sts:AssumeRole"
|
103
|
-
]
|
87
|
+
}
|
104
88
|
}
|
105
89
|
]
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
90
|
+
end
|
91
|
+
r.property(:assume_role_policy_document) do
|
92
|
+
{
|
93
|
+
"Version": "2012-10-17",
|
94
|
+
"Statement": [
|
95
|
+
{
|
96
|
+
"Effect": "Allow",
|
97
|
+
"Principal": {
|
98
|
+
"Service": [
|
99
|
+
"lambda.amazonaws.com"
|
100
|
+
]
|
101
|
+
},
|
102
|
+
"Action": [
|
103
|
+
"sts:AssumeRole"
|
104
|
+
]
|
105
|
+
}
|
106
|
+
]
|
107
|
+
}
|
108
|
+
end
|
109
|
+
r.property(:managed_policy_arns) do
|
110
|
+
[
|
111
|
+
"arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole"
|
112
|
+
]
|
113
|
+
end
|
112
114
|
end
|
113
|
-
end
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
resource :lambda_invoke_permission,
|
117
|
+
type: "AWS::Lambda::Permission" do |r|
|
118
|
+
r.property(:function_name) { :lifecycle_handler_function.ref }
|
119
|
+
r.property(:action) { "lambda:InvokeFunction" }
|
120
|
+
r.property(:principal) { "sns.amazonaws.com" }
|
121
|
+
r.property(:source_arn) { :ecs_lifecycle_notification_topic.ref }
|
122
|
+
end
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
resource :lifecycle_handler_function,
|
125
|
+
type: "AWS::Lambda::Function" do |r|
|
126
|
+
r.property(:environment) do
|
127
|
+
{
|
128
|
+
"Variables": {
|
129
|
+
"CLUSTER": :ecs_cluster.ref
|
130
|
+
}
|
129
131
|
}
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
132
|
+
end
|
133
|
+
r.property(:code) do
|
134
|
+
{
|
135
|
+
"ZipFile": {
|
136
|
+
"Fn::Join": [
|
137
|
+
"\n",
|
138
|
+
[
|
139
|
+
"import boto3,json,os,time",
|
140
|
+
"ec2Client = boto3.client('ec2')",
|
141
|
+
"ecsClient = boto3.client('ecs')",
|
142
|
+
"autoscalingClient = boto3.client('autoscaling')",
|
143
|
+
"snsClient = boto3.client('sns')",
|
144
|
+
"lambdaClient = boto3.client('lambda')",
|
145
|
+
"def publishSNSMessage(snsMessage,snsTopicArn):",
|
146
|
+
" response = snsClient.publish(TopicArn=snsTopicArn,Message=json.dumps(snsMessage),Subject='reinvoking')",
|
147
|
+
"def setContainerInstanceStatusToDraining(ecsClusterName,containerInstanceArn):",
|
148
|
+
" response = ecsClient.update_container_instances_state(cluster=ecsClusterName,containerInstances=[containerInstanceArn],status='DRAINING')",
|
149
|
+
"def tasksRunning(ecsClusterName,ec2InstanceId):",
|
150
|
+
" ecsContainerInstances = ecsClient.describe_container_instances(cluster=ecsClusterName,containerInstances=ecsClient.list_container_instances(cluster=ecsClusterName)['containerInstanceArns'])['containerInstances']",
|
151
|
+
" for i in ecsContainerInstances:",
|
152
|
+
" if i['ec2InstanceId'] == ec2InstanceId:",
|
153
|
+
" if i['status'] == 'ACTIVE':",
|
154
|
+
" setContainerInstanceStatusToDraining(ecsClusterName,i['containerInstanceArn'])",
|
155
|
+
" return 1",
|
156
|
+
" if (i['runningTasksCount']>0) or (i['pendingTasksCount']>0):",
|
157
|
+
" return 1",
|
158
|
+
" return 0",
|
159
|
+
" return 2",
|
160
|
+
"def lambda_handler(event, context):",
|
161
|
+
" ecsClusterName=os.environ['CLUSTER']",
|
162
|
+
" snsTopicArn=event['Records'][0]['Sns']['TopicArn']",
|
163
|
+
" snsMessage=json.loads(event['Records'][0]['Sns']['Message'])",
|
164
|
+
" lifecycleHookName=snsMessage['LifecycleHookName']",
|
165
|
+
" lifecycleActionToken=snsMessage['LifecycleActionToken']",
|
166
|
+
" asgName=snsMessage['AutoScalingGroupName']",
|
167
|
+
" ec2InstanceId=snsMessage['EC2InstanceId']",
|
168
|
+
" checkTasks=tasksRunning(ecsClusterName,ec2InstanceId)",
|
169
|
+
" if checkTasks==0:",
|
170
|
+
" try:",
|
171
|
+
" response = autoscalingClient.complete_lifecycle_action(LifecycleHookName=lifecycleHookName,AutoScalingGroupName=asgName,LifecycleActionToken=lifecycleActionToken,LifecycleActionResult='CONTINUE')",
|
172
|
+
" except BaseException as e:",
|
173
|
+
" print(str(e))",
|
174
|
+
" elif checkTasks==1:",
|
175
|
+
" time.sleep(5)",
|
176
|
+
" publishSNSMessage(snsMessage,snsTopicArn)"
|
177
|
+
]
|
176
178
|
]
|
177
|
-
|
179
|
+
}
|
178
180
|
}
|
179
|
-
|
181
|
+
end
|
182
|
+
r.property(:handler) { "index.lambda_handler" }
|
183
|
+
r.property(:role) { :lambda_execution_role.ref(:arn) }
|
184
|
+
r.property(:runtime) { "python3.6" }
|
185
|
+
r.property(:timeout) { 10 }
|
180
186
|
end
|
181
|
-
r.property(:handler) { "index.lambda_handler" }
|
182
|
-
r.property(:role) { :lambda_execution_role.ref(:arn) }
|
183
|
-
r.property(:runtime) { "python3.6" }
|
184
|
-
r.property(:timeout) { 10 }
|
185
187
|
end
|
186
188
|
end
|
187
189
|
end
|
@@ -5,64 +5,66 @@ module EcsStack
|
|
5
5
|
parameter :public_subnets,
|
6
6
|
description: "List of Public EC2 Subnets for the ALB"
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
unless infra_config["environments"][environment]["cluster_size"].nil? || infra_config["environments"][environment]["cluster_size"].to_i.zero?
|
9
|
+
resource :ecs_load_balancer,
|
10
|
+
type: "AWS::ElasticLoadBalancingV2::LoadBalancer" do |r|
|
11
|
+
r.property(:name) { environment }
|
12
|
+
r.property(:subnets) { :public_subnets.ref.fnsplit(",") }
|
13
|
+
r.property(:security_groups) do
|
14
|
+
[
|
15
|
+
:ecs_host_security_group.ref,
|
16
|
+
:load_balancer_security_group.ref # Not sure if necessary
|
17
|
+
]
|
18
|
+
end
|
19
|
+
r.property(:tags) do
|
20
|
+
[
|
21
|
+
{
|
22
|
+
"Key": "Name",
|
23
|
+
"Value": "#{environment}_ecs_loadbalancer"
|
24
|
+
}
|
25
|
+
]
|
26
|
+
end
|
17
27
|
end
|
18
|
-
r.property(:tags) do
|
19
|
-
[
|
20
|
-
{
|
21
|
-
"Key": "Name",
|
22
|
-
"Value": "#{environment}_ecs_loadbalancer"
|
23
|
-
}
|
24
|
-
]
|
25
|
-
end
|
26
|
-
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
resource :load_balancer_listener,
|
30
|
+
type: "AWS::ElasticLoadBalancingV2::Listener" do |r|
|
31
|
+
r.property(:load_balancer_arn) { :ecs_load_balancer.ref }
|
32
|
+
r.property(:port) { 80 }
|
33
|
+
r.property(:protocol) { "HTTP" }
|
34
|
+
r.property(:default_actions) do
|
35
|
+
[
|
36
|
+
{
|
37
|
+
"Type": "forward",
|
38
|
+
"TargetGroupArn": :default_target_group.ref
|
39
|
+
}
|
40
|
+
]
|
41
|
+
end
|
40
42
|
end
|
41
|
-
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
resource :default_target_group,
|
45
|
+
type: "AWS::ElasticLoadBalancingV2::TargetGroup" do |r|
|
46
|
+
r.property(:name) { "#{environment}-default" }
|
47
|
+
r.property(:vpc_id) { :vpc.ref }
|
48
|
+
r.property(:port) { 80 }
|
49
|
+
r.property(:protocol) { "HTTP" }
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
output :ecs_load_balancer,
|
53
|
+
description: "ECS Application Load Balancer",
|
54
|
+
value: :ecs_load_balancer.ref
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
output :ecs_load_balancer_url,
|
57
|
+
description: "URL of the ECS ALB",
|
58
|
+
value: :ecs_load_balancer.ref("DNSName")
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
output :ecs_load_balancer_listener,
|
61
|
+
description: "ECS Port 80 listener",
|
62
|
+
value: :load_balancer_listener.ref
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
output :ecs_load_balancer_hosted_zone_id,
|
65
|
+
description: "Canonical Hosted Zone ID of the ALB",
|
66
|
+
value: :ecs_load_balancer.ref("CanonicalHostedZoneID")
|
67
|
+
end
|
66
68
|
end
|
67
69
|
end
|
68
70
|
end
|