ufo 4.6.3 → 5.0.0
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.
- 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
|