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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/docs/_docs/extras/notification-arns.md +21 -0
  4. data/docs/_docs/helpers.md +6 -4
  5. data/docs/_docs/iam-roles.md +111 -0
  6. data/docs/_docs/secrets.md +112 -0
  7. data/docs/_docs/settings/cluster.md +7 -13
  8. data/docs/_includes/subnav.html +3 -0
  9. data/docs/_reference/ufo-deploy.md +1 -2
  10. data/docs/_reference/ufo-logs.md +1 -1
  11. data/docs/_reference/ufo-rollback.md +2 -0
  12. data/docs/_reference/ufo-ship.md +1 -2
  13. data/docs/_reference/ufo-ships.md +1 -2
  14. data/docs/_reference/ufo-tasks-build.md +1 -2
  15. data/lib/template/.secrets +3 -0
  16. data/lib/template/.ufo/settings.yml.tt +1 -0
  17. data/lib/template/.ufo/settings/cfn/default.yml.tt +27 -27
  18. data/lib/template/.ufo/settings/network/default.yml.tt +9 -0
  19. data/lib/template/.ufo/templates/fargate.json.erb +3 -0
  20. data/lib/template/.ufo/templates/main.json.erb +3 -0
  21. data/lib/template/.ufo/variables/base.rb.tt +1 -0
  22. data/lib/ufo.rb +2 -1
  23. data/lib/ufo/autoloader.rb +9 -0
  24. data/lib/ufo/cli.rb +3 -2
  25. data/lib/ufo/core.rb +1 -9
  26. data/lib/ufo/docker/cleaner.rb +1 -1
  27. data/lib/ufo/dsl.rb +6 -1
  28. data/lib/ufo/dsl/helper.rb +19 -37
  29. data/lib/ufo/dsl/helper/vars.rb +98 -0
  30. data/lib/ufo/dsl/outputter.rb +12 -9
  31. data/lib/ufo/log_group.rb +1 -0
  32. data/lib/ufo/role/builder.rb +66 -0
  33. data/lib/ufo/role/dsl.rb +21 -0
  34. data/lib/ufo/role/registry.rb +24 -0
  35. data/lib/ufo/rollback.rb +2 -1
  36. data/lib/ufo/setting/profile.rb +11 -7
  37. data/lib/ufo/setting/security_groups.rb +22 -0
  38. data/lib/ufo/settings.rb +20 -0
  39. data/lib/ufo/stack.rb +24 -24
  40. data/lib/ufo/stack/builder.rb +26 -0
  41. data/lib/ufo/stack/builder/base.rb +54 -0
  42. data/lib/ufo/stack/builder/conditions.rb +23 -0
  43. data/lib/ufo/stack/builder/outputs.rb +24 -0
  44. data/lib/ufo/stack/builder/parameters.rb +45 -0
  45. data/lib/ufo/stack/builder/resources.rb +20 -0
  46. data/lib/ufo/stack/builder/resources/base.rb +4 -0
  47. data/lib/ufo/stack/builder/resources/dns.rb +17 -0
  48. data/lib/ufo/stack/builder/resources/ecs.rb +63 -0
  49. data/lib/ufo/stack/builder/resources/elb.rb +45 -0
  50. data/lib/ufo/stack/builder/resources/listener.rb +42 -0
  51. data/lib/ufo/stack/builder/resources/listener_ssl.rb +16 -0
  52. data/lib/ufo/stack/builder/resources/roles/base.rb +22 -0
  53. data/lib/ufo/stack/builder/resources/roles/execution_role.rb +4 -0
  54. data/lib/ufo/stack/builder/resources/roles/task_role.rb +4 -0
  55. data/lib/ufo/stack/builder/resources/security_group/base.rb +4 -0
  56. data/lib/ufo/stack/builder/resources/security_group/ecs.rb +44 -0
  57. data/lib/ufo/stack/builder/resources/security_group/ecs_rule.rb +25 -0
  58. data/lib/ufo/stack/builder/resources/security_group/elb.rb +57 -0
  59. data/lib/ufo/stack/builder/resources/target_group.rb +39 -0
  60. data/lib/ufo/stack/builder/resources/task_definition.rb +24 -0
  61. data/lib/ufo/stack/builder/resources/task_definition/reconstructor.rb +49 -0
  62. data/lib/ufo/stack/context.rb +41 -48
  63. data/lib/ufo/stack/custom_properties.rb +59 -0
  64. data/lib/ufo/stack/helper.rb +2 -5
  65. data/lib/ufo/stack/template_body.rb +13 -0
  66. data/lib/ufo/task.rb +2 -7
  67. data/lib/ufo/tasks.rb +1 -1
  68. data/lib/ufo/tasks/builder.rb +0 -1
  69. data/lib/ufo/template_scope.rb +1 -66
  70. data/lib/ufo/utils/squeezer.rb +24 -0
  71. data/lib/ufo/version.rb +1 -1
  72. data/spec/fixtures/iam_roles/task_role.rb +17 -0
  73. data/spec/lib/role/builder_spec.rb +67 -0
  74. data/spec/lib/role/dsl_spec.rb +12 -0
  75. data/ufo.gemspec +1 -0
  76. metadata +57 -3
  77. data/lib/cfn/stack.yml +0 -283
@@ -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
@@ -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
- ship = Ship.new(@service, @options.merge(task_definition: task_definition))
6
+
7
+ ship = Ship.new(@service, @options.merge(task_definition: task_definition, rollback: true))
7
8
  ship.deploy
8
9
  end
9
10
 
@@ -2,21 +2,25 @@ class Ufo::Setting
2
2
  class Profile
3
3
  extend Memoist
4
4
 
5
- def initialize(type, profile='default')
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
- path = "#{Ufo.root}/.ufo/settings/#{@type}/#{@profile}.yml"
12
- unless File.exist?(path)
13
- puts "#{@type.camelize} profile #{path} not found. Please double check that it exists."
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(path)
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
@@ -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
@@ -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
- hash = {
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
- hash[:EcsSecurityGroups] = network[:ecs_security_groups].join(',') if network[:ecs_security_groups]
118
- hash[:ElbSecurityGroups] = network[:elb_security_groups].join(',') if network[:elb_security_groups]
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
- Context.new(@options.merge(
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 task_definition_arn
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 :task_definition_arn
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