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
data/lib/ufo/role/dsl.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ufo::Role
|
2
|
+
class DSL
|
3
|
+
def initialize(path)
|
4
|
+
@path = path # IE: .ufo/iam_roles/task_role.rb
|
5
|
+
end
|
6
|
+
|
7
|
+
def evaluate
|
8
|
+
instance_eval(IO.read(@path), @path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def iam_policy(policy_name, statements)
|
12
|
+
role_type = File.basename(@path).sub('.rb','') # task_role or execution_role
|
13
|
+
Registry.register_policy(role_type, policy_name, statements)
|
14
|
+
end
|
15
|
+
|
16
|
+
def managed_iam_policy(*policies)
|
17
|
+
role_type = File.basename(@path).sub('.rb','') # task_role or execution_role
|
18
|
+
Registry.register_managed_policy(role_type, policies)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Ufo::Role
|
4
|
+
class Registry
|
5
|
+
class_attribute :policies
|
6
|
+
self.policies = {}
|
7
|
+
class_attribute :managed_policies
|
8
|
+
self.managed_policies = {}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def register_policy(role_type, policy_name, *statements)
|
12
|
+
statements.flatten!
|
13
|
+
self.policies[role_type] ||= Set.new
|
14
|
+
self.policies[role_type].add([policy_name, statements]) # using set so DSL can safely be evaluated multiple times
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_managed_policy(role_type, *policies)
|
18
|
+
policies.flatten!
|
19
|
+
self.managed_policies[role_type] ||= Set.new
|
20
|
+
self.managed_policies[role_type].merge(policies) # using set so DSL can safely be evaluated multiple times
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ufo/rollback.rb
CHANGED
@@ -3,7 +3,8 @@ module Ufo
|
|
3
3
|
def deploy
|
4
4
|
task_definition = normalize_version(@options[:version])
|
5
5
|
puts "Rolling back ECS service to task definition #{task_definition}"
|
6
|
-
|
6
|
+
|
7
|
+
ship = Ship.new(@service, @options.merge(task_definition: task_definition, rollback: true))
|
7
8
|
ship.deploy
|
8
9
|
end
|
9
10
|
|
data/lib/ufo/setting/profile.rb
CHANGED
@@ -2,21 +2,25 @@ class Ufo::Setting
|
|
2
2
|
class Profile
|
3
3
|
extend Memoist
|
4
4
|
|
5
|
-
def initialize(type, profile=
|
5
|
+
def initialize(type, profile=nil)
|
6
6
|
@type = type.to_s # cfn or network
|
7
7
|
@profile = profile
|
8
8
|
end
|
9
9
|
|
10
10
|
def data
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
names = [
|
12
|
+
@profile, # user specified
|
13
|
+
Ufo.env, # conventional based on env
|
14
|
+
"default", # fallback to default
|
15
|
+
].compact.uniq
|
16
|
+
paths = names.map { |name| "#{Ufo.root}/.ufo/settings/#{@type}/#{name}.yml" }
|
17
|
+
found = paths.find { |p| File.exist?(p) }
|
18
|
+
unless found
|
19
|
+
puts "#{@type.camelize} profile not found. Please double check that it exists. Checked paths: #{paths}"
|
14
20
|
exit 1
|
15
21
|
end
|
16
22
|
|
17
|
-
text = RenderMePretty.result(
|
18
|
-
# puts "text:".color(:cyan)
|
19
|
-
# puts text
|
23
|
+
text = RenderMePretty.result(found)
|
20
24
|
YAML.load(text).deep_symbolize_keys
|
21
25
|
end
|
22
26
|
memoize :data
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Ufo::Setting
|
2
|
+
class SecurityGroups
|
3
|
+
include Ufo::Settings
|
4
|
+
extend Memoist
|
5
|
+
|
6
|
+
def initialize(service, type)
|
7
|
+
@service, @type = service, type
|
8
|
+
end
|
9
|
+
|
10
|
+
def load
|
11
|
+
groups = network[@type] # IE: network[:ecs_security_groups] or network[:elb_security_groups]
|
12
|
+
return [] unless groups
|
13
|
+
|
14
|
+
case groups
|
15
|
+
when Array # same security groups used for all services
|
16
|
+
groups
|
17
|
+
when Hash # service specific security groups
|
18
|
+
groups[@service.to_sym] || []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/ufo/settings.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ufo
|
2
|
+
module Settings
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def network
|
6
|
+
Ufo::Setting::Profile.new(:network, settings[:network_profile]).data
|
7
|
+
end
|
8
|
+
memoize :network
|
9
|
+
|
10
|
+
def cfn
|
11
|
+
Ufo::Setting::Profile.new(:cfn, settings[:cfn_profile]).data
|
12
|
+
end
|
13
|
+
memoize :cfn
|
14
|
+
|
15
|
+
def settings
|
16
|
+
Setting.new.data
|
17
|
+
end
|
18
|
+
memoize :settings
|
19
|
+
end
|
20
|
+
end
|
data/lib/ufo/stack.rb
CHANGED
@@ -25,10 +25,12 @@ module Ufo
|
|
25
25
|
class Stack
|
26
26
|
extend Memoist
|
27
27
|
include Helper
|
28
|
+
include Ufo::Settings
|
28
29
|
|
29
30
|
def initialize(options)
|
30
31
|
@options = options
|
31
32
|
@task_definition = options[:task_definition]
|
33
|
+
@rollback = options[:rollback]
|
32
34
|
@service = options[:service]
|
33
35
|
@cluster = @options[:cluster] || default_cluster(@service)
|
34
36
|
@stack_name = adjust_stack_name(@cluster, options[:service])
|
@@ -66,18 +68,6 @@ module Ufo
|
|
66
68
|
handle_stack_error(e)
|
67
69
|
end
|
68
70
|
|
69
|
-
# do not memoize template_body it can change for a rename retry
|
70
|
-
def template_body
|
71
|
-
custom_template = "#{Ufo.root}/.ufo/settings/cfn/stack.yml"
|
72
|
-
path = if File.exist?(custom_template)
|
73
|
-
custom_template
|
74
|
-
else
|
75
|
-
# built-in default
|
76
|
-
File.expand_path("../cfn/stack.yml", File.dirname(__FILE__))
|
77
|
-
end
|
78
|
-
RenderMePretty.result(path, context: context.scope)
|
79
|
-
end
|
80
|
-
|
81
71
|
def stack_options
|
82
72
|
save_template
|
83
73
|
if ENV['SAVE_TEMPLATE_EXIT']
|
@@ -85,22 +75,26 @@ module Ufo
|
|
85
75
|
exit 1
|
86
76
|
end
|
87
77
|
{
|
78
|
+
capabilities: ["CAPABILITY_IAM"],
|
79
|
+
notification_arns: notification_arns,
|
88
80
|
parameters: parameters,
|
89
81
|
stack_name: @stack_name,
|
90
82
|
template_body: template_body,
|
91
83
|
}
|
92
84
|
end
|
93
85
|
|
86
|
+
def notification_arns
|
87
|
+
settings[:notification_arns] || []
|
88
|
+
end
|
89
|
+
|
94
90
|
def parameters
|
95
91
|
create_elb, elb_target_group = context.elb_options
|
96
92
|
|
97
|
-
network = Setting::Profile.new(:network, settings[:network_profile]).data
|
98
|
-
# pp network
|
99
93
|
elb_subnets = network[:elb_subnets] && !network[:elb_subnets].empty? ?
|
100
94
|
network[:elb_subnets] :
|
101
95
|
network[:ecs_subnets]
|
102
96
|
|
103
|
-
|
97
|
+
params = {
|
104
98
|
Vpc: network[:vpc],
|
105
99
|
ElbSubnets: elb_subnets.join(','),
|
106
100
|
EcsSubnets: network[:ecs_subnets].join(','),
|
@@ -110,14 +104,11 @@ module Ufo
|
|
110
104
|
ElbEipIds: context.elb_eip_ids,
|
111
105
|
|
112
106
|
EcsDesiredCount: current_desired_count,
|
113
|
-
EcsTaskDefinition: task_definition_arn,
|
114
107
|
EcsSchedulingStrategy: scheduling_strategy,
|
115
108
|
}
|
116
109
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
hash.map do |k,v|
|
110
|
+
params = Ufo::Utils::Squeezer.new(params).squeeze
|
111
|
+
params.map do |k,v|
|
121
112
|
if v == :use_previous_value
|
122
113
|
{ parameter_key: k, use_previous_value: true }
|
123
114
|
else
|
@@ -127,12 +118,20 @@ module Ufo
|
|
127
118
|
end
|
128
119
|
memoize :parameters
|
129
120
|
|
121
|
+
# do not memoize template_body it can change for a rename retry
|
122
|
+
def template_body
|
123
|
+
TemplateBody.new(context).build
|
124
|
+
end
|
125
|
+
memoize :template_body
|
126
|
+
|
130
127
|
def context
|
131
|
-
|
128
|
+
o = @options.merge(
|
132
129
|
cluster: @cluster,
|
133
130
|
stack_name: @stack_name,
|
134
131
|
stack: @stack,
|
135
|
-
)
|
132
|
+
)
|
133
|
+
o[:rollback_definition_arn] = rollback_definition_arn if rollback_definition_arn
|
134
|
+
Context.new(o)
|
136
135
|
end
|
137
136
|
memoize :context
|
138
137
|
|
@@ -154,11 +153,12 @@ module Ufo
|
|
154
153
|
end
|
155
154
|
end
|
156
155
|
|
157
|
-
def
|
156
|
+
def rollback_definition_arn
|
157
|
+
return unless @rollback
|
158
158
|
resp = ecs.describe_task_definition(task_definition: @task_definition)
|
159
159
|
resp.task_definition.task_definition_arn
|
160
160
|
end
|
161
|
-
memoize :
|
161
|
+
memoize :rollback_definition_arn
|
162
162
|
|
163
163
|
# Store template in tmp in case for debugging
|
164
164
|
def save_template
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Ufo::Stack
|
2
|
+
class Builder
|
3
|
+
class_attribute :context
|
4
|
+
|
5
|
+
def initialize(context)
|
6
|
+
@context = context
|
7
|
+
# This builder class is really used as a singleton.
|
8
|
+
# To avoid having to pass context to all the builder classes.
|
9
|
+
self.class.context = @context
|
10
|
+
@template = {
|
11
|
+
Description: "Ufo ECS stack #{context.stack_name}",
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
@template[:Parameters] = Parameters.new.build
|
17
|
+
@template[:Conditions] = Conditions.new.build
|
18
|
+
@template[:Resources] = Resources.new.build
|
19
|
+
@template[:Outputs] = Outputs.new.build
|
20
|
+
@template.deep_stringify_keys!
|
21
|
+
@template = Ufo::Utils::Squeezer.new(@template).squeeze
|
22
|
+
@template = CustomProperties.new(@template, @context.stack_name).apply
|
23
|
+
YAML.dump(@template)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Ufo::Stack::Builder
|
2
|
+
class Base
|
3
|
+
include Ufo::Settings
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
copy_instance_variables
|
7
|
+
end
|
8
|
+
|
9
|
+
# Copy the instance variables from TemplateScope Stack Builder classes
|
10
|
+
def copy_instance_variables
|
11
|
+
context = Ufo::Stack::Builder.context
|
12
|
+
scope = context.scope
|
13
|
+
scope.instance_variables.each do |var|
|
14
|
+
val = scope.instance_variable_get(var)
|
15
|
+
instance_variable_set(var, val)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build
|
20
|
+
new.build
|
21
|
+
end
|
22
|
+
|
23
|
+
# type: elb or ecs
|
24
|
+
# NOTE: Application ELBs always seem to need a security group even though the docs say its not required
|
25
|
+
# However, there's a case where no ELB is created for a worker tier and if the settings are all blank
|
26
|
+
# CloudFormation fails to resolve and splits out this error:
|
27
|
+
#
|
28
|
+
# Template error: every Fn::Split object requires two parameters
|
29
|
+
#
|
30
|
+
# So we will not assign security groups at all for case of workers with no security groups at all.
|
31
|
+
#
|
32
|
+
def security_groups(type)
|
33
|
+
settings_key = "#{type}_security_groups".to_sym
|
34
|
+
group_ids = Ufo::Setting::SecurityGroups.new(@service, settings_key).load
|
35
|
+
# no security groups at all
|
36
|
+
return if !managed_security_groups_enabled? && group_ids.blank?
|
37
|
+
|
38
|
+
groups = []
|
39
|
+
groups += group_ids
|
40
|
+
groups += [managed_security_group(type.to_s.camelize)] if managed_security_groups_enabled?
|
41
|
+
groups
|
42
|
+
end
|
43
|
+
|
44
|
+
def managed_security_group(type)
|
45
|
+
logical_id = managed_security_groups_enabled? ? "#{type.camelize}SecurityGroup" : "AWS::NoValue"
|
46
|
+
{Ref: logical_id}
|
47
|
+
end
|
48
|
+
|
49
|
+
def managed_security_groups_enabled?
|
50
|
+
managed = settings[:managed_security_groups_enabled]
|
51
|
+
managed.nil? ? true : managed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Ufo::Stack::Builder
|
2
|
+
class Conditions < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
CreateElbIsTrue: {
|
6
|
+
"Fn::Equals": [{Ref: "CreateElb"}, true]
|
7
|
+
},
|
8
|
+
ElbTargetGroupIsBlank: {
|
9
|
+
"Fn::Equals": [{Ref: "ElbTargetGroup"}, ""]
|
10
|
+
},
|
11
|
+
CreateTargetGroupIsTrue: {
|
12
|
+
"Fn::And": [
|
13
|
+
{Condition: "CreateElbIsTrue"},
|
14
|
+
{Condition: "ElbTargetGroupIsBlank"},
|
15
|
+
]
|
16
|
+
},
|
17
|
+
EcsDesiredCountIsBlank: {
|
18
|
+
"Fn::Equals": [{Ref: "EcsDesiredCount"}, ""]
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Ufo::Stack::Builder
|
2
|
+
class Outputs < Base
|
3
|
+
def build
|
4
|
+
outputs = {
|
5
|
+
ElbDns: {
|
6
|
+
Description: "Elb Dns",
|
7
|
+
Condition: "CreateElbIsTrue",
|
8
|
+
Value: {
|
9
|
+
"Fn::GetAtt": "Elb.DNSName"
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
if @create_route53
|
15
|
+
outputs[:Route53Dns] = {
|
16
|
+
Description: "Route53 Dns",
|
17
|
+
Value: {Ref: "Dns"},
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
outputs
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Ufo::Stack::Builder
|
2
|
+
class Parameters < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
"Vpc": {
|
6
|
+
"Description": "Existing vpc id",
|
7
|
+
"Type": "AWS::EC2::VPC::Id"
|
8
|
+
},
|
9
|
+
"ElbSubnets": {
|
10
|
+
"Description": "Existing subnet ids for ELB",
|
11
|
+
"Type": "List<AWS::EC2::Subnet::Id>"
|
12
|
+
},
|
13
|
+
"EcsSubnets": {
|
14
|
+
"Description": "Existing subnet ids for ECS",
|
15
|
+
"Type": "List<AWS::EC2::Subnet::Id>"
|
16
|
+
},
|
17
|
+
"ElbTargetGroup": {
|
18
|
+
"Description": "Existing target group",
|
19
|
+
"Type": "String",
|
20
|
+
"Default": ""
|
21
|
+
},
|
22
|
+
"CreateElb": {
|
23
|
+
"Description": "Create elb",
|
24
|
+
"Type": "String",
|
25
|
+
"Default": true
|
26
|
+
},
|
27
|
+
"EcsDesiredCount": {
|
28
|
+
"Description": "Ecs desired count",
|
29
|
+
"Type": "String",
|
30
|
+
"Default": 1
|
31
|
+
},
|
32
|
+
"ElbEipIds": {
|
33
|
+
"Description": "ELB EIP Allocation ids to use for network load balancer",
|
34
|
+
"Type": "String",
|
35
|
+
"Default": ""
|
36
|
+
},
|
37
|
+
"EcsSchedulingStrategy": {
|
38
|
+
"Description": "The scheduling strategy to use for the service",
|
39
|
+
"Type": "String",
|
40
|
+
"Default": "REPLICA"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Ufo::Stack::Builder
|
2
|
+
class Resources < Base
|
3
|
+
def build
|
4
|
+
{
|
5
|
+
Dns: Dns.build,
|
6
|
+
Ecs: Ecs.build,
|
7
|
+
EcsSecurityGroup: SecurityGroup::Ecs.build,
|
8
|
+
EcsSecurityGroupRule: SecurityGroup::EcsRule.build,
|
9
|
+
Elb: Elb.build,
|
10
|
+
ElbSecurityGroup: SecurityGroup::Elb.build,
|
11
|
+
ExecutionRole: Roles::ExecutionRole.build,
|
12
|
+
Listener: Listener.build,
|
13
|
+
ListenerSsl: ListenerSsl.build,
|
14
|
+
TargetGroup: TargetGroup.build,
|
15
|
+
TaskDefinition: TaskDefinition.build,
|
16
|
+
TaskRole: Roles::TaskRole.build,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|