ufo 4.6.3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/docs/_docs/extras/notification-arns.md +21 -0
- data/docs/_docs/helpers.md +6 -4
- data/docs/_docs/iam-roles.md +111 -0
- data/docs/_docs/secrets.md +112 -0
- data/docs/_docs/settings/cluster.md +7 -13
- data/docs/_includes/subnav.html +3 -0
- data/docs/_reference/ufo-deploy.md +1 -2
- data/docs/_reference/ufo-logs.md +1 -1
- data/docs/_reference/ufo-rollback.md +2 -0
- data/docs/_reference/ufo-ship.md +1 -2
- data/docs/_reference/ufo-ships.md +1 -2
- data/docs/_reference/ufo-tasks-build.md +1 -2
- data/lib/template/.secrets +3 -0
- data/lib/template/.ufo/settings.yml.tt +1 -0
- data/lib/template/.ufo/settings/cfn/default.yml.tt +27 -27
- data/lib/template/.ufo/settings/network/default.yml.tt +9 -0
- data/lib/template/.ufo/templates/fargate.json.erb +3 -0
- data/lib/template/.ufo/templates/main.json.erb +3 -0
- data/lib/template/.ufo/variables/base.rb.tt +1 -0
- data/lib/ufo.rb +2 -1
- data/lib/ufo/autoloader.rb +9 -0
- data/lib/ufo/cli.rb +3 -2
- data/lib/ufo/core.rb +1 -9
- data/lib/ufo/docker/cleaner.rb +1 -1
- data/lib/ufo/dsl.rb +6 -1
- data/lib/ufo/dsl/helper.rb +19 -37
- data/lib/ufo/dsl/helper/vars.rb +98 -0
- data/lib/ufo/dsl/outputter.rb +12 -9
- data/lib/ufo/log_group.rb +1 -0
- data/lib/ufo/role/builder.rb +66 -0
- data/lib/ufo/role/dsl.rb +21 -0
- data/lib/ufo/role/registry.rb +24 -0
- data/lib/ufo/rollback.rb +2 -1
- data/lib/ufo/setting/profile.rb +11 -7
- data/lib/ufo/setting/security_groups.rb +22 -0
- data/lib/ufo/settings.rb +20 -0
- data/lib/ufo/stack.rb +24 -24
- data/lib/ufo/stack/builder.rb +26 -0
- data/lib/ufo/stack/builder/base.rb +54 -0
- data/lib/ufo/stack/builder/conditions.rb +23 -0
- data/lib/ufo/stack/builder/outputs.rb +24 -0
- data/lib/ufo/stack/builder/parameters.rb +45 -0
- data/lib/ufo/stack/builder/resources.rb +20 -0
- data/lib/ufo/stack/builder/resources/base.rb +4 -0
- data/lib/ufo/stack/builder/resources/dns.rb +17 -0
- data/lib/ufo/stack/builder/resources/ecs.rb +63 -0
- data/lib/ufo/stack/builder/resources/elb.rb +45 -0
- data/lib/ufo/stack/builder/resources/listener.rb +42 -0
- data/lib/ufo/stack/builder/resources/listener_ssl.rb +16 -0
- data/lib/ufo/stack/builder/resources/roles/base.rb +22 -0
- data/lib/ufo/stack/builder/resources/roles/execution_role.rb +4 -0
- data/lib/ufo/stack/builder/resources/roles/task_role.rb +4 -0
- data/lib/ufo/stack/builder/resources/security_group/base.rb +4 -0
- data/lib/ufo/stack/builder/resources/security_group/ecs.rb +44 -0
- data/lib/ufo/stack/builder/resources/security_group/ecs_rule.rb +25 -0
- data/lib/ufo/stack/builder/resources/security_group/elb.rb +57 -0
- data/lib/ufo/stack/builder/resources/target_group.rb +39 -0
- data/lib/ufo/stack/builder/resources/task_definition.rb +24 -0
- data/lib/ufo/stack/builder/resources/task_definition/reconstructor.rb +49 -0
- data/lib/ufo/stack/context.rb +41 -48
- data/lib/ufo/stack/custom_properties.rb +59 -0
- data/lib/ufo/stack/helper.rb +2 -5
- data/lib/ufo/stack/template_body.rb +13 -0
- data/lib/ufo/task.rb +2 -7
- data/lib/ufo/tasks.rb +1 -1
- data/lib/ufo/tasks/builder.rb +0 -1
- data/lib/ufo/template_scope.rb +1 -66
- data/lib/ufo/utils/squeezer.rb +24 -0
- data/lib/ufo/version.rb +1 -1
- data/spec/fixtures/iam_roles/task_role.rb +17 -0
- data/spec/lib/role/builder_spec.rb +67 -0
- data/spec/lib/role/dsl_spec.rb +12 -0
- data/ufo.gemspec +1 -0
- metadata +57 -3
- data/lib/cfn/stack.yml +0 -283
@@ -0,0 +1,17 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class Dns < Base
|
3
|
+
def build
|
4
|
+
return unless @create_route53
|
5
|
+
|
6
|
+
{
|
7
|
+
Type: "AWS::Route53::RecordSet",
|
8
|
+
Properties: {
|
9
|
+
Comment: "cname to load balancer",
|
10
|
+
Type: "CNAME",
|
11
|
+
TTL: 60, # ttl has special casing
|
12
|
+
ResourceRecords: [{"Fn::GetAtt": "Elb.DNSName"}]
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class Ecs < Base
|
3
|
+
def build
|
4
|
+
attrs = {
|
5
|
+
Type: "AWS::ECS::Service",
|
6
|
+
Properties: properties
|
7
|
+
}
|
8
|
+
|
9
|
+
attrs[:DependsOn] = "Listener" if @create_elb
|
10
|
+
|
11
|
+
attrs
|
12
|
+
end
|
13
|
+
|
14
|
+
def properties
|
15
|
+
props = {
|
16
|
+
Cluster: @cluster,
|
17
|
+
DesiredCount: {
|
18
|
+
"Fn::If": [
|
19
|
+
"EcsDesiredCountIsBlank",
|
20
|
+
{Ref: "AWS::NoValue"},
|
21
|
+
{Ref: "EcsDesiredCount"}
|
22
|
+
]
|
23
|
+
},
|
24
|
+
NetworkConfiguration: {
|
25
|
+
AwsvpcConfiguration: {
|
26
|
+
Subnets: {Ref: "EcsSubnets"},
|
27
|
+
SecurityGroups: security_groups(:ecs)
|
28
|
+
}
|
29
|
+
},
|
30
|
+
LoadBalancers: {
|
31
|
+
"Fn::If": [
|
32
|
+
"CreateTargetGroupIsTrue",
|
33
|
+
[
|
34
|
+
{
|
35
|
+
ContainerName: "web",
|
36
|
+
ContainerPort: @container[:port],
|
37
|
+
TargetGroupArn: {Ref: "TargetGroup"}
|
38
|
+
}
|
39
|
+
],
|
40
|
+
{
|
41
|
+
"Fn::If": [
|
42
|
+
"ElbTargetGroupIsBlank",
|
43
|
+
[],
|
44
|
+
[
|
45
|
+
{
|
46
|
+
ContainerName: "web",
|
47
|
+
ContainerPort: @container[:port],
|
48
|
+
TargetGroupArn: {Ref: "ElbTargetGroup"}
|
49
|
+
}
|
50
|
+
]
|
51
|
+
]
|
52
|
+
}
|
53
|
+
]
|
54
|
+
},
|
55
|
+
SchedulingStrategy: {Ref: "EcsSchedulingStrategy"}
|
56
|
+
}
|
57
|
+
|
58
|
+
props[:TaskDefinition] = @rollback_definition_arn ? @rollback_definition_arn : {Ref: "TaskDefinition"}
|
59
|
+
|
60
|
+
props
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class Elb < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer",
|
6
|
+
Condition: "CreateElbIsTrue",
|
7
|
+
Properties: properties,
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def properties
|
12
|
+
props = {
|
13
|
+
Type: @elb_type,
|
14
|
+
Tags: [
|
15
|
+
{Key: "Name", Value: @stack_name}
|
16
|
+
],
|
17
|
+
Subnets: {Ref: "ElbSubnets"},
|
18
|
+
Scheme: "internet-facing"
|
19
|
+
}
|
20
|
+
|
21
|
+
props[:SecurityGroups] = security_groups(:elb) if @elb_type == "application"
|
22
|
+
subnets(props)
|
23
|
+
|
24
|
+
props
|
25
|
+
end
|
26
|
+
|
27
|
+
def subnets(props)
|
28
|
+
mappings = @elb_type == "network" && @subnet_mappings && !@subnet_mappings.empty?
|
29
|
+
if mappings
|
30
|
+
props[:SubnetMappings] = subnet_mappings
|
31
|
+
else
|
32
|
+
props[:Subnets] = {Ref: "ElbSubnets"}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def subnet_mappings
|
37
|
+
@subnet_mappings.map do |allocation_id, subnet_id|
|
38
|
+
{
|
39
|
+
AllocationId: allocation_id,
|
40
|
+
SubnetId: subnet_id,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class Listener < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
Type: "AWS::ElasticLoadBalancingV2::Listener",
|
6
|
+
Condition: "CreateElbIsTrue",
|
7
|
+
Properties: properties,
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def properties
|
12
|
+
props = {
|
13
|
+
DefaultActions: [
|
14
|
+
{
|
15
|
+
Type: "forward",
|
16
|
+
TargetGroupArn: {
|
17
|
+
"Fn::If": [
|
18
|
+
"ElbTargetGroupIsBlank",
|
19
|
+
{Ref: "TargetGroup"},
|
20
|
+
{Ref: "ElbTargetGroup"}
|
21
|
+
]
|
22
|
+
}
|
23
|
+
}
|
24
|
+
],
|
25
|
+
LoadBalancerArn: {Ref: "Elb"},
|
26
|
+
Protocol: protocol,
|
27
|
+
}
|
28
|
+
|
29
|
+
props[:Port] = port if port
|
30
|
+
|
31
|
+
props
|
32
|
+
end
|
33
|
+
|
34
|
+
def protocol
|
35
|
+
@default_listener_protocol
|
36
|
+
end
|
37
|
+
|
38
|
+
def port
|
39
|
+
80
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ufo::Stack::Builder::Resources::Roles
|
2
|
+
class Base < Ufo::Stack::Builder::Base
|
3
|
+
def build
|
4
|
+
return unless self.class.build? # important because it runs DSL#evaluate
|
5
|
+
Ufo::Role::Builder.new(self.class.role_type).build
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def role_type
|
11
|
+
self.name.to_s.split("::").last.underscore
|
12
|
+
end
|
13
|
+
|
14
|
+
def build?
|
15
|
+
path = "#{Ufo.root}/.ufo/iam_roles/#{role_type}.rb"
|
16
|
+
return unless File.exist?(path)
|
17
|
+
Ufo::Role::DSL.new(path).evaluate # runs the role.rb and registers items
|
18
|
+
Ufo::Role::Builder.new(role_type).build?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Ufo::Stack::Builder::Resources::SecurityGroup
|
2
|
+
class Ecs < Base
|
3
|
+
def build
|
4
|
+
return unless managed_security_groups_enabled?
|
5
|
+
|
6
|
+
{
|
7
|
+
Type: "AWS::EC2::SecurityGroup",
|
8
|
+
Properties: properties
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def properties
|
13
|
+
props = {
|
14
|
+
GroupDescription: "Allow http to client host",
|
15
|
+
VpcId: {Ref: "Vpc"},
|
16
|
+
SecurityGroupEgress: [
|
17
|
+
{
|
18
|
+
IpProtocol: "-1",
|
19
|
+
CidrIp: "0.0.0.0/0",
|
20
|
+
Description: "outbound traffic"
|
21
|
+
}
|
22
|
+
],
|
23
|
+
Tags: [
|
24
|
+
{
|
25
|
+
Key: "Name",
|
26
|
+
Value: @stack_name,
|
27
|
+
}
|
28
|
+
]
|
29
|
+
}
|
30
|
+
|
31
|
+
if @elb_type == "network"
|
32
|
+
props[:SecurityGroupIngress] = {
|
33
|
+
IpProtocol: "tcp",
|
34
|
+
FromPort: @container[:port],
|
35
|
+
ToPort: @container[:port],
|
36
|
+
CidrIp: "0.0.0.0/0",
|
37
|
+
Description: "docker ephemeral port range for network elb",
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
props
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Ufo::Stack::Builder::Resources::SecurityGroup
|
2
|
+
class EcsRule < Base
|
3
|
+
def build
|
4
|
+
return unless managed_security_groups_enabled?
|
5
|
+
return unless @elb_type == "application"
|
6
|
+
|
7
|
+
{
|
8
|
+
Type: "AWS::EC2::SecurityGroupIngress",
|
9
|
+
Condition: "CreateElbIsTrue",
|
10
|
+
Properties: {
|
11
|
+
IpProtocol: "tcp",
|
12
|
+
FromPort: "0",
|
13
|
+
ToPort: "65535",
|
14
|
+
SourceSecurityGroupId: {
|
15
|
+
"Fn::GetAtt": "ElbSecurityGroup.GroupId"
|
16
|
+
},
|
17
|
+
GroupId: {
|
18
|
+
"Fn::GetAtt": "EcsSecurityGroup.GroupId"
|
19
|
+
},
|
20
|
+
Description: "application elb access to ecs"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Ufo::Stack::Builder::Resources::SecurityGroup
|
2
|
+
class Elb < Base
|
3
|
+
def build
|
4
|
+
return unless managed_security_groups_enabled?
|
5
|
+
return unless @elb_type == "application"
|
6
|
+
|
7
|
+
{
|
8
|
+
Type: "AWS::EC2::SecurityGroup",
|
9
|
+
Condition: "CreateElbIsTrue",
|
10
|
+
Properties: properties
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def properties
|
15
|
+
port = cfn.dig(:Listener, :Port) || cfn.dig(:listener, :port) # backwards compatiblity
|
16
|
+
|
17
|
+
props = {
|
18
|
+
GroupDescription: "Allow http to client host",
|
19
|
+
VpcId: {Ref: "Vpc"},
|
20
|
+
SecurityGroupIngress: [
|
21
|
+
{
|
22
|
+
IpProtocol: "tcp",
|
23
|
+
FromPort: port,
|
24
|
+
ToPort: port,
|
25
|
+
CidrIp: "0.0.0.0/0"
|
26
|
+
}
|
27
|
+
],
|
28
|
+
SecurityGroupEgress: [
|
29
|
+
{
|
30
|
+
IpProtocol: "tcp",
|
31
|
+
FromPort: "0",
|
32
|
+
ToPort: "65535",
|
33
|
+
CidrIp: "0.0.0.0/0"
|
34
|
+
}
|
35
|
+
],
|
36
|
+
Tags: [
|
37
|
+
{
|
38
|
+
Key: "Name",
|
39
|
+
Value: "#{@stack_name}-elb"
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
|
44
|
+
if @create_listener_ssl
|
45
|
+
ssl_port = cfn.dig(:ListenerSsl, :Port) || cfn.dig(:listener_ssl, :port) # backwards compatiblity
|
46
|
+
props[:SecurityGroupIngress] << {
|
47
|
+
IpProtocol: "tcp",
|
48
|
+
FromPort: ssl_port,
|
49
|
+
ToPort: ssl_port,
|
50
|
+
CidrIp: "0.0.0.0/0"
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
props
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class TargetGroup < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
Type: "AWS::ElasticLoadBalancingV2::TargetGroup",
|
6
|
+
Condition: "CreateTargetGroupIsTrue",
|
7
|
+
Properties: properties,
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def properties
|
12
|
+
props = {
|
13
|
+
VpcId: {Ref: "Vpc"},
|
14
|
+
Tags: [
|
15
|
+
{
|
16
|
+
Key: "Name",
|
17
|
+
Value: @stack_name,
|
18
|
+
}
|
19
|
+
],
|
20
|
+
Protocol: @default_target_group_protocol,
|
21
|
+
Port: 80,
|
22
|
+
HealthCheckIntervalSeconds: 10,
|
23
|
+
HealthyThresholdCount: 2,
|
24
|
+
UnhealthyThresholdCount: 2,
|
25
|
+
TargetGroupAttributes: [
|
26
|
+
{
|
27
|
+
Key: "deregistration_delay.timeout_seconds",
|
28
|
+
Value: 10
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
|
33
|
+
props[:TargetType] = "ip" if @container[:network_mode] == "awsvpc"
|
34
|
+
props[:HealthCheckPort] = @container[:port] if @elb_type == "network" && @network_mode == "awsvpc"
|
35
|
+
|
36
|
+
props
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources
|
2
|
+
class TaskDefinition < Base
|
3
|
+
def build
|
4
|
+
return if @rollback_definition_arn
|
5
|
+
|
6
|
+
{
|
7
|
+
Type: "AWS::ECS::TaskDefinition",
|
8
|
+
Properties: properties,
|
9
|
+
DeletionPolicy: "Retain",
|
10
|
+
UpdateReplacePolicy: "Retain",
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def properties
|
15
|
+
props = Reconstructor.new(@task_definition).reconstruct
|
16
|
+
|
17
|
+
# Decorate with iam roles if needed
|
18
|
+
props[:TaskRoleArn] = {"Fn::GetAtt": "TaskRole.Arn"} if Roles::TaskRole.build?
|
19
|
+
props[:ExecutionRoleArn] = {"Fn::GetAtt": "ExecutionRole.Arn"} if Roles::ExecutionRole.build?
|
20
|
+
|
21
|
+
props
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Ufo::Stack::Builder::Resources::TaskDefinition
|
2
|
+
class Reconstructor
|
3
|
+
include Ufo::AwsService
|
4
|
+
|
5
|
+
def initialize(task_definition, rollback=false)
|
6
|
+
@task_definition, @rollback = task_definition, rollback
|
7
|
+
end
|
8
|
+
|
9
|
+
def reconstruct
|
10
|
+
camelize(data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
if @rollback
|
15
|
+
resp = ecs.describe_task_definition(task_definition: @task_definition)
|
16
|
+
resp.task_definition.to_h
|
17
|
+
else
|
18
|
+
path = "#{Ufo.root}/.ufo/output/#{@task_definition}.json"
|
19
|
+
JSON.load(IO.read(path))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# non-destructive
|
24
|
+
def camelize(value, parent_keys=[])
|
25
|
+
case value
|
26
|
+
when Array
|
27
|
+
value.map { |v| camelize(v, parent_keys) }
|
28
|
+
when Hash
|
29
|
+
initializer = value.map do |k, v|
|
30
|
+
new_key = camelize_key(k, parent_keys)
|
31
|
+
[new_key, camelize(v, parent_keys+[new_key])]
|
32
|
+
end
|
33
|
+
Hash[initializer]
|
34
|
+
else
|
35
|
+
value # do not camelize values
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def camelize_key(k, parent_keys=[])
|
40
|
+
k = k.to_s
|
41
|
+
special = %w[Options] & parent_keys.map(&:to_s)
|
42
|
+
if special.empty?
|
43
|
+
k.camelize
|
44
|
+
else
|
45
|
+
k # pass through untouch
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|