ufo 4.6.3 → 5.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/docs/_docs/conventions.md +1 -1
  4. data/docs/_docs/extras/codebuild-iam-role.md +1 -1
  5. data/docs/_docs/extras/dockerfile-erb.md +1 -1
  6. data/docs/_docs/extras/ecs-network-mode.md +1 -1
  7. data/docs/_docs/extras/load-balancer.md +1 -1
  8. data/docs/_docs/extras/minimal-deploy-iam.md +1 -1
  9. data/docs/_docs/extras/notification-arns.md +21 -0
  10. data/docs/_docs/extras/redirection-support.md +9 -9
  11. data/docs/_docs/extras/route53-support.md +4 -4
  12. data/docs/_docs/extras/security-groups.md +1 -1
  13. data/docs/_docs/extras/ssl-support.md +5 -5
  14. data/docs/_docs/faq.md +1 -1
  15. data/docs/_docs/helpers.md +7 -5
  16. data/docs/_docs/iam-roles.md +112 -0
  17. data/docs/_docs/install.md +0 -10
  18. data/docs/_docs/more/auto-completion.md +1 -1
  19. data/docs/_docs/more/automated-cleanup.md +1 -1
  20. data/docs/_docs/more/customize-cloudformation.md +1 -1
  21. data/docs/_docs/more/migrations.md +1 -1
  22. data/docs/_docs/more/run-in-pieces.md +1 -1
  23. data/docs/_docs/more/single-task.md +1 -1
  24. data/docs/_docs/more/stuck-cloudformation.md +1 -1
  25. data/docs/_docs/more/why-cloudformation.md +1 -1
  26. data/docs/_docs/next-steps.md +1 -1
  27. data/docs/_docs/secrets.md +135 -0
  28. data/docs/_docs/settings.md +10 -9
  29. data/docs/_docs/settings/cluster.md +7 -13
  30. data/docs/_docs/settings/manage-security-groups.md +24 -0
  31. data/docs/_docs/settings/network.md +11 -1
  32. data/docs/_docs/structure.md +10 -9
  33. data/docs/_docs/tutorial-ufo-init.md +1 -7
  34. data/docs/_docs/ufo-current.md +1 -1
  35. data/docs/_docs/ufo-env-extra.md +1 -1
  36. data/docs/_docs/ufo-env.md +3 -5
  37. data/docs/_docs/ufo-logs.md +1 -2
  38. data/docs/_docs/ufo-task-params.md +1 -1
  39. data/docs/_docs/upgrading.md +1 -1
  40. data/docs/_docs/upgrading/upgrade4.5.md +2 -2
  41. data/docs/_docs/upgrading/upgrade4.md +2 -2
  42. data/docs/_docs/upgrading/upgrade5.md +19 -0
  43. data/docs/_docs/variables.md +1 -1
  44. data/docs/_includes/cfn-customize.md +18 -4
  45. data/docs/_includes/footer.html +6 -5
  46. data/docs/_includes/subnav.html +3 -0
  47. data/docs/_reference/ufo-deploy.md +1 -2
  48. data/docs/_reference/ufo-init.md +14 -15
  49. data/docs/_reference/ufo-logs.md +1 -1
  50. data/docs/_reference/ufo-rollback.md +2 -0
  51. data/docs/_reference/ufo-ship.md +1 -2
  52. data/docs/_reference/ufo-ships.md +1 -2
  53. data/docs/_reference/ufo-tasks-build.md +1 -2
  54. data/docs/articles.md +1 -1
  55. data/lib/template/.secrets +5 -0
  56. data/lib/template/.ufo/iam_roles/execution_role.rb +7 -0
  57. data/lib/template/.ufo/iam_roles/task_role.rb +21 -0
  58. data/lib/template/.ufo/settings.yml.tt +1 -0
  59. data/lib/template/.ufo/settings/cfn/default.yml.tt +27 -27
  60. data/lib/template/.ufo/settings/network/default.yml.tt +9 -0
  61. data/lib/template/.ufo/templates/fargate.json.erb +3 -1
  62. data/lib/template/.ufo/templates/main.json.erb +3 -0
  63. data/lib/template/.ufo/variables/base.rb.tt +1 -0
  64. data/lib/ufo.rb +2 -1
  65. data/lib/ufo/autoloader.rb +9 -0
  66. data/lib/ufo/cli.rb +3 -2
  67. data/lib/ufo/core.rb +1 -9
  68. data/lib/ufo/docker/cleaner.rb +1 -1
  69. data/lib/ufo/dsl.rb +6 -1
  70. data/lib/ufo/dsl/helper.rb +19 -37
  71. data/lib/ufo/dsl/helper/vars.rb +97 -0
  72. data/lib/ufo/dsl/outputter.rb +12 -9
  73. data/lib/ufo/ecr/auth.rb +10 -21
  74. data/lib/ufo/init.rb +0 -2
  75. data/lib/ufo/log_group.rb +1 -0
  76. data/lib/ufo/role/builder.rb +66 -0
  77. data/lib/ufo/role/dsl.rb +21 -0
  78. data/lib/ufo/role/registry.rb +24 -0
  79. data/lib/ufo/rollback.rb +2 -1
  80. data/lib/ufo/sequence.rb +0 -16
  81. data/lib/ufo/setting/profile.rb +22 -8
  82. data/lib/ufo/setting/security_groups.rb +22 -0
  83. data/lib/ufo/settings.rb +20 -0
  84. data/lib/ufo/stack.rb +24 -24
  85. data/lib/ufo/stack/builder.rb +26 -0
  86. data/lib/ufo/stack/builder/base.rb +54 -0
  87. data/lib/ufo/stack/builder/conditions.rb +23 -0
  88. data/lib/ufo/stack/builder/outputs.rb +24 -0
  89. data/lib/ufo/stack/builder/parameters.rb +45 -0
  90. data/lib/ufo/stack/builder/resources.rb +20 -0
  91. data/lib/ufo/stack/builder/resources/base.rb +4 -0
  92. data/lib/ufo/stack/builder/resources/dns.rb +17 -0
  93. data/lib/ufo/stack/builder/resources/ecs.rb +71 -0
  94. data/lib/ufo/stack/builder/resources/elb.rb +45 -0
  95. data/lib/ufo/stack/builder/resources/listener.rb +42 -0
  96. data/lib/ufo/stack/builder/resources/listener_ssl.rb +16 -0
  97. data/lib/ufo/stack/builder/resources/roles/base.rb +22 -0
  98. data/lib/ufo/stack/builder/resources/roles/execution_role.rb +4 -0
  99. data/lib/ufo/stack/builder/resources/roles/task_role.rb +4 -0
  100. data/lib/ufo/stack/builder/resources/security_group/base.rb +4 -0
  101. data/lib/ufo/stack/builder/resources/security_group/ecs.rb +44 -0
  102. data/lib/ufo/stack/builder/resources/security_group/ecs_rule.rb +25 -0
  103. data/lib/ufo/stack/builder/resources/security_group/elb.rb +57 -0
  104. data/lib/ufo/stack/builder/resources/target_group.rb +39 -0
  105. data/lib/ufo/stack/builder/resources/task_definition.rb +24 -0
  106. data/lib/ufo/stack/builder/resources/task_definition/reconstructor.rb +49 -0
  107. data/lib/ufo/stack/context.rb +41 -48
  108. data/lib/ufo/stack/custom_properties.rb +59 -0
  109. data/lib/ufo/stack/helper.rb +2 -5
  110. data/lib/ufo/stack/template_body.rb +13 -0
  111. data/lib/ufo/task.rb +2 -7
  112. data/lib/ufo/tasks.rb +1 -1
  113. data/lib/ufo/tasks/builder.rb +0 -1
  114. data/lib/ufo/template_scope.rb +1 -66
  115. data/lib/ufo/utils/squeezer.rb +24 -0
  116. data/lib/ufo/version.rb +1 -1
  117. data/spec/fixtures/iam_roles/task_role.rb +17 -0
  118. data/spec/lib/ecr_auth_spec.rb +32 -20
  119. data/spec/lib/role/builder_spec.rb +67 -0
  120. data/spec/lib/role/dsl_spec.rb +12 -0
  121. data/ufo.gemspec +2 -1
  122. metadata +66 -8
  123. data/lib/cfn/stack.yml +0 -283
@@ -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? && group_ids.blank?
37
+
38
+ groups = []
39
+ groups += group_ids
40
+ groups += [managed_security_group(type.to_s.camelize)] if managed_security_groups?
41
+ groups
42
+ end
43
+
44
+ def managed_security_group(type)
45
+ logical_id = managed_security_groups? ? "#{type.camelize}SecurityGroup" : "AWS::NoValue"
46
+ {Ref: logical_id}
47
+ end
48
+
49
+ def managed_security_groups?
50
+ managed = settings[:managed_security_groups]
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
@@ -0,0 +1,4 @@
1
+ class Ufo::Stack::Builder::Resources
2
+ class Base < Ufo::Stack::Builder::Base
3
+ end
4
+ end
@@ -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,71 @@
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
+ LoadBalancers: {
25
+ "Fn::If": [
26
+ "CreateTargetGroupIsTrue",
27
+ [
28
+ {
29
+ ContainerName: @container[:name],
30
+ ContainerPort: @container[:port],
31
+ TargetGroupArn: {Ref: "TargetGroup"}
32
+ }
33
+ ],
34
+ {
35
+ "Fn::If": [
36
+ "ElbTargetGroupIsBlank",
37
+ [],
38
+ [
39
+ {
40
+ ContainerName: @container[:name],
41
+ ContainerPort: @container[:port],
42
+ TargetGroupArn: {Ref: "ElbTargetGroup"}
43
+ }
44
+ ]
45
+ ]
46
+ }
47
+ ]
48
+ },
49
+ SchedulingStrategy: {Ref: "EcsSchedulingStrategy"}
50
+ }
51
+
52
+ props[:TaskDefinition] = @rollback_definition_arn ? @rollback_definition_arn : {Ref: "TaskDefinition"}
53
+
54
+ if @container[:network_mode].to_s == 'awsvpc'
55
+ props[:NetworkConfiguration] = {
56
+ AwsvpcConfiguration: {
57
+ Subnets: {Ref: "EcsSubnets"},
58
+ SecurityGroups: security_groups(:ecs)
59
+ }
60
+ }
61
+
62
+ if @container[:fargate]
63
+ props[:LaunchType] = "FARGATE"
64
+ props[:NetworkConfiguration][:AwsvpcConfiguration][:AssignPublicIp] = "ENABLED" # Works with fargate but doesnt seem to work with non-fargate
65
+ end
66
+ end
67
+
68
+ props
69
+ end
70
+ end
71
+ end