ufo 4.6.3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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