ufo 4.6.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) 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/quick-start-ec2.md +1 -0
  28. data/docs/_docs/secrets.md +135 -0
  29. data/docs/_docs/settings.md +10 -9
  30. data/docs/_docs/settings/cluster.md +7 -13
  31. data/docs/_docs/settings/manage-security-groups.md +24 -0
  32. data/docs/_docs/settings/network.md +11 -1
  33. data/docs/_docs/structure.md +10 -9
  34. data/docs/_docs/tutorial-ufo-init.md +1 -7
  35. data/docs/_docs/ufo-current.md +1 -1
  36. data/docs/_docs/ufo-env-extra.md +1 -1
  37. data/docs/_docs/ufo-env.md +3 -5
  38. data/docs/_docs/ufo-logs.md +1 -2
  39. data/docs/_docs/ufo-task-params.md +1 -1
  40. data/docs/_docs/upgrading.md +1 -1
  41. data/docs/_docs/upgrading/upgrade4.5.md +2 -2
  42. data/docs/_docs/upgrading/upgrade4.md +2 -2
  43. data/docs/_docs/upgrading/upgrade5.md +19 -0
  44. data/docs/_docs/variables.md +1 -1
  45. data/docs/_includes/cfn-customize.md +4 -4
  46. data/docs/_includes/subnav.html +3 -0
  47. data/docs/_reference/ufo-deploy.md +1 -2
  48. data/docs/_reference/ufo-init.md +15 -16
  49. data/docs/_reference/ufo-logs.md +10 -9
  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/docs/quick-start.md +1 -0
  56. data/lib/template/.secrets +5 -0
  57. data/lib/template/.ufo/iam_roles/execution_role.rb +7 -0
  58. data/lib/template/.ufo/iam_roles/task_role.rb +21 -0
  59. data/lib/template/.ufo/settings.yml.tt +1 -0
  60. data/lib/template/.ufo/settings/cfn/default.yml.tt +27 -27
  61. data/lib/template/.ufo/settings/network/default.yml.tt +9 -0
  62. data/lib/template/.ufo/templates/fargate.json.erb +3 -1
  63. data/lib/template/.ufo/templates/main.json.erb +3 -0
  64. data/lib/template/.ufo/variables/base.rb.tt +1 -0
  65. data/lib/ufo.rb +2 -1
  66. data/lib/ufo/autoloader.rb +9 -0
  67. data/lib/ufo/cli.rb +4 -2
  68. data/lib/ufo/command.rb +7 -0
  69. data/lib/ufo/core.rb +1 -9
  70. data/lib/ufo/docker/cleaner.rb +1 -1
  71. data/lib/ufo/dsl.rb +6 -1
  72. data/lib/ufo/dsl/helper.rb +21 -27
  73. data/lib/ufo/dsl/helper/vars.rb +97 -0
  74. data/lib/ufo/dsl/outputter.rb +12 -9
  75. data/lib/ufo/help/init.md +1 -1
  76. data/lib/ufo/init.rb +0 -2
  77. data/lib/ufo/log_group.rb +1 -0
  78. data/lib/ufo/logs.rb +5 -4
  79. data/lib/ufo/role/builder.rb +66 -0
  80. data/lib/ufo/role/dsl.rb +21 -0
  81. data/lib/ufo/role/registry.rb +24 -0
  82. data/lib/ufo/rollback.rb +2 -1
  83. data/lib/ufo/sequence.rb +0 -16
  84. data/lib/ufo/setting/profile.rb +11 -7
  85. data/lib/ufo/setting/security_groups.rb +22 -0
  86. data/lib/ufo/settings.rb +20 -0
  87. data/lib/ufo/stack.rb +24 -24
  88. data/lib/ufo/stack/builder.rb +26 -0
  89. data/lib/ufo/stack/builder/base.rb +54 -0
  90. data/lib/ufo/stack/builder/conditions.rb +23 -0
  91. data/lib/ufo/stack/builder/outputs.rb +24 -0
  92. data/lib/ufo/stack/builder/parameters.rb +45 -0
  93. data/lib/ufo/stack/builder/resources.rb +20 -0
  94. data/lib/ufo/stack/builder/resources/base.rb +4 -0
  95. data/lib/ufo/stack/builder/resources/dns.rb +17 -0
  96. data/lib/ufo/stack/builder/resources/ecs.rb +67 -0
  97. data/lib/ufo/stack/builder/resources/elb.rb +45 -0
  98. data/lib/ufo/stack/builder/resources/listener.rb +42 -0
  99. data/lib/ufo/stack/builder/resources/listener_ssl.rb +16 -0
  100. data/lib/ufo/stack/builder/resources/roles/base.rb +22 -0
  101. data/lib/ufo/stack/builder/resources/roles/execution_role.rb +4 -0
  102. data/lib/ufo/stack/builder/resources/roles/task_role.rb +4 -0
  103. data/lib/ufo/stack/builder/resources/security_group/base.rb +4 -0
  104. data/lib/ufo/stack/builder/resources/security_group/ecs.rb +44 -0
  105. data/lib/ufo/stack/builder/resources/security_group/ecs_rule.rb +25 -0
  106. data/lib/ufo/stack/builder/resources/security_group/elb.rb +57 -0
  107. data/lib/ufo/stack/builder/resources/target_group.rb +39 -0
  108. data/lib/ufo/stack/builder/resources/task_definition.rb +24 -0
  109. data/lib/ufo/stack/builder/resources/task_definition/reconstructor.rb +49 -0
  110. data/lib/ufo/stack/context.rb +41 -48
  111. data/lib/ufo/stack/custom_properties.rb +59 -0
  112. data/lib/ufo/stack/helper.rb +2 -5
  113. data/lib/ufo/stack/template_body.rb +13 -0
  114. data/lib/ufo/task.rb +2 -7
  115. data/lib/ufo/tasks.rb +1 -1
  116. data/lib/ufo/tasks/builder.rb +0 -1
  117. data/lib/ufo/tasks/register.rb +11 -8
  118. data/lib/ufo/template_scope.rb +1 -66
  119. data/lib/ufo/utils/squeezer.rb +24 -0
  120. data/lib/ufo/version.rb +1 -1
  121. data/spec/fixtures/iam_roles/task_role.rb +17 -0
  122. data/spec/lib/role/builder_spec.rb +67 -0
  123. data/spec/lib/role/dsl_spec.rb +12 -0
  124. data/ufo.gemspec +1 -0
  125. metadata +61 -3
  126. data/lib/cfn/stack.yml +0 -283
@@ -21,7 +21,7 @@ module Ufo
21
21
  end
22
22
 
23
23
  def delete_list
24
- return [] if ENV['TEST']
24
+ return [] if ENV['TEST'] || @options[:noop]
25
25
  return @delete_list if @delete_list
26
26
 
27
27
  out = execute("docker images") # live to override the noop cli options
@@ -2,6 +2,8 @@ require 'ostruct'
2
2
 
3
3
  module Ufo
4
4
  class DSL
5
+ extend Memoist
6
+
5
7
  def initialize(template_definitions_path, options={})
6
8
  @template_definitions_path = template_definitions_path
7
9
  @options = options
@@ -85,7 +87,10 @@ module Ufo
85
87
  end
86
88
 
87
89
  def helper
88
- Helper.new
90
+ helper = Helper.new
91
+ helper.add_project_helpers
92
+ helper
89
93
  end
94
+ memoize :helper
90
95
  end
91
96
  end
@@ -6,11 +6,20 @@
6
6
  # Simply aggregates a bunch of variables that is useful for the task_definition.
7
7
  module Ufo
8
8
  class DSL
9
- # provides some helperally context variables
10
9
  class Helper
11
10
  include Ufo::Util
12
11
  extend Memoist
13
12
 
13
+ # Add helpers from .ufo/helpers folder
14
+ def add_project_helpers
15
+ helpers_dir = "#{Ufo.root}/.ufo/helpers"
16
+ Dir.glob("#{helpers_dir}/**/*").each do |path|
17
+ next unless File.file?(path)
18
+ klass = path.gsub(%r{.*\.ufo/helpers/},'').sub(".rb",'').camelize
19
+ self.class.send(:include, klass.constantize)
20
+ end
21
+ end
22
+
14
23
  ##############
15
24
  # helper variables
16
25
  def dockerfile_port
@@ -27,36 +36,21 @@ module Ufo
27
36
 
28
37
  #############
29
38
  # helper methods
30
- def env_vars(text)
31
- lines = filtered_lines(text)
32
- lines.map do |line|
33
- key,*value = line.strip.split("=").map {|x| x.strip}
34
- value = value.join('=')
35
- {
36
- name: key,
37
- value: value,
38
- }
39
- end
39
+ def env(text)
40
+ Vars.new(text: text).env
40
41
  end
42
+ alias_method :env_vars, :env
41
43
 
42
- def filtered_lines(text)
43
- lines = text.split("\n")
44
- # remove comment at the end of the line
45
- lines.map! { |l| l.sub(/\s+#.*/,'').strip }
46
- # filter out commented lines
47
- lines = lines.reject { |l| l =~ /(^|\s)#/i }
48
- # filter out empty lines
49
- lines = lines.reject { |l| l.strip.empty? }
44
+ def env_file(path)
45
+ Vars.new(file: path).env
50
46
  end
51
47
 
52
- def env_file(path)
53
- full_path = "#{Ufo.root}/#{path}"
54
- unless File.exist?(full_path)
55
- puts "The #{full_path} env file could not be found. Are you sure it exists?"
56
- exit 1
57
- end
58
- text = IO.read(full_path)
59
- env_vars(text)
48
+ def secrets(text)
49
+ Vars.new(text: text).secrets
50
+ end
51
+
52
+ def secrets_file(path)
53
+ Vars.new(file: path).secrets
60
54
  end
61
55
 
62
56
  def current_region
@@ -0,0 +1,97 @@
1
+ require "aws_data"
2
+
3
+ class Ufo::DSL::Helper
4
+ class Vars
5
+ extend Memoist
6
+
7
+ def initialize(options={})
8
+ # use either file or text. text takes higher precedence
9
+ @file = options[:file]
10
+ @text = options[:text]
11
+ end
12
+
13
+ def content
14
+ @text || read(@file)
15
+ end
16
+
17
+ def read(path)
18
+ full_path = "#{Ufo.root}/#{path}"
19
+ unless File.exist?(full_path)
20
+ puts "The #{full_path} env file could not be found. Are you sure it exists?"
21
+ exit 1
22
+ end
23
+ IO.read(full_path)
24
+ end
25
+
26
+ def env
27
+ lines = filtered_lines(content)
28
+ lines.map do |line|
29
+ key,*value = line.strip.split("=").map do |x|
30
+ remove_surrounding_quotes(x.strip)
31
+ end
32
+ value = value.join('=')
33
+ {
34
+ name: key,
35
+ value: value,
36
+ }
37
+ end
38
+ end
39
+
40
+ def secrets
41
+ secrets = env
42
+ secrets.map do |item|
43
+ value = item.delete(:value)
44
+ item[:valueFrom] = substitute(expand_secret(value))
45
+ end
46
+ secrets
47
+ end
48
+
49
+ def expand_secret(value)
50
+ case value
51
+ when /^ssm:/i
52
+ value.sub(/^ssm:/i, "arn:aws:ssm:#{region}:#{account}:parameter/")
53
+ when /^secretsmanager:/i
54
+ value.sub(/^secretsmanager:/i, "arn:aws:secretsmanager:#{region}:#{account}:secret:")
55
+ else
56
+ value # assume full arn has been passed
57
+ end
58
+ end
59
+
60
+ def substitute(value)
61
+ value.gsub(":UFO_ENV", Ufo.env)
62
+ end
63
+
64
+ def remove_surrounding_quotes(s)
65
+ if s =~ /^"/ && s =~ /"$/
66
+ s.sub(/^["]/, '').gsub(/["]$/,'') # remove surrounding double quotes
67
+ elsif s =~ /^'/ && s =~ /'$/
68
+ s.sub(/^[']/, '').gsub(/[']$/,'') # remove surrounding single quotes
69
+ else
70
+ s
71
+ end
72
+ end
73
+
74
+ def filtered_lines(content)
75
+ lines = content.split("\n")
76
+ # remove comment at the end of the line
77
+ lines.map! { |l| l.sub(/\s+#.*/,'').strip }
78
+ # filter out commented lines
79
+ lines = lines.reject { |l| l =~ /(^|\s)#/i }
80
+ # filter out empty lines
81
+ lines = lines.reject { |l| l.strip.empty? }
82
+ end
83
+
84
+ def aws_data
85
+ AwsData.new
86
+ end
87
+ memoize :aws_data
88
+
89
+ def region
90
+ aws_data.region
91
+ end
92
+
93
+ def account
94
+ aws_data.account
95
+ end
96
+ end
97
+ end
@@ -5,7 +5,6 @@ module Ufo
5
5
  @name = name
6
6
  @erb_result = erb_result
7
7
  @options = options
8
- @pretty = options[:pretty].nil? ? true : options[:pretty]
9
8
  end
10
9
 
11
10
  def write
@@ -16,25 +15,29 @@ module Ufo
16
15
  path = "#{output_path}/#{@name}.json".sub(/^\.\//,'')
17
16
  puts " #{path}" unless @options[:quiet]
18
17
  validate(@erb_result, path)
19
- json = @pretty ?
20
- JSON.pretty_generate(JSON.parse(@erb_result)) :
21
- @erb_result
22
- File.open(path, 'w') {|f| f.write(output_json(json)) }
18
+ data = JSON.parse(@erb_result)
19
+ override_image(data)
20
+ json = JSON.pretty_generate(data)
21
+ File.open(path, 'w') {|f| f.write(json) }
22
+ end
23
+
24
+ def override_image(data)
25
+ return data unless @options[:image_override]
26
+ data["containerDefinitions"].each do |container_definition|
27
+ container_definition["image"] = @options[:image_override]
28
+ end
23
29
  end
24
30
 
25
31
  def validate(json, path)
26
32
  begin
27
33
  JSON.parse(json)
28
34
  rescue JSON::ParserError => e
35
+ puts "#{e.class}: #{e.message}"
29
36
  puts "Invalid json. Output written to #{path} for debugging".color(:red)
30
37
  File.open(path, 'w') {|f| f.write(json) }
31
38
  exit 1
32
39
  end
33
40
  end
34
-
35
- def output_json(json)
36
- @options[:pretty] ? JSON.pretty_generate(JSON.parse(json)) : json
37
- end
38
41
  end
39
42
  end
40
43
  end
@@ -39,7 +39,7 @@ The `image` is the base portion of image name that will be pushed to the docker
39
39
 
40
40
  The generated `tongueroo/demo-ufo:ufo-2018-02-08T21-04-02-3c86158` image name gets pushed to the docker registry.
41
41
 
42
- The `--vpc-id` option is optional but very useful. If not specified then ufo will use the default vpc for the network settings like subnets and security groups, which might not be what you want.
42
+ The `--vpc-id`, `--ecs-subnets`, and `--elb-subnets` options are optional but very useful. If not specified then ufo will use the default vpc for the network settings like subnets and security groups, which might not be what you want.
43
43
 
44
44
  ## Directory Structure
45
45
 
@@ -9,7 +9,6 @@ module Ufo
9
9
  [:image, required: true, desc: "Docker image name without the tag. Example: tongueroo/demo-ufo. Configures ufo/settings.yml"],
10
10
  [:app, desc: "App name. Preferably one word. Used in the generated ufo/task_definitions.rb. If not specified then the app name is inferred as the folder name."],
11
11
  [:launch_type, default: "ec2", desc: "ec2 or fargate."],
12
- [:execution_role_arn, desc: "execution role arn used by tasks, required for fargate."],
13
12
  [:template, desc: "Custom template to use."],
14
13
  [:template_mode, desc: "Template mode: replace or additive."],
15
14
  [:vpc_id, desc: "Vpc id. For settings/network/default.yml."],
@@ -56,7 +55,6 @@ module Ufo
56
55
  # map variables
57
56
  @app = options[:app] || inferred_app
58
57
  @image = options[:image]
59
- @execution_role_arn_input = get_execution_role_arn_input
60
58
  # copy the files
61
59
  puts "Setting up ufo project..."
62
60
  exclude_pattern = File.exist?("#{Ufo.root}/Dockerfile") ?
@@ -11,6 +11,7 @@ module Ufo
11
11
  def create
12
12
  puts "Ensuring log group for #{@task_definition.color(:green)} task definition exists"
13
13
  return if @options[:noop]
14
+ return if @options[:rollback] # dont need to create log group because previously deployed
14
15
 
15
16
  Ufo.check_task_definition!(@task_definition)
16
17
  task_def = JSON.load(IO.read(task_def_path))
@@ -41,14 +41,15 @@ module Ufo
41
41
  end
42
42
 
43
43
  def cloudwatch_tail(log={})
44
- since = @options[:since] || "10m" # by default, search only 10 mins in the past
45
- cw_tail = AwsLogs::Tail.new(
44
+ o = {
46
45
  log_group_name: log["awslogs-group"],
47
46
  log_stream_name_prefix: log["awslogs-stream-prefix"],
48
- since: since,
47
+ since: @options[:since] || "10m", # by default, search only 10 mins in the past
49
48
  follow: @options[:follow],
50
49
  format: @options[:format],
51
- )
50
+ }
51
+ o[:filter_pattern] = @options[:filter_pattern] if @options[:filter_pattern]
52
+ cw_tail = AwsLogs::Tail.new(o)
52
53
  cw_tail.run
53
54
  end
54
55
  end
@@ -0,0 +1,66 @@
1
+ module Ufo::Role
2
+ class Builder
3
+ def initialize(role_type)
4
+ @role_type = role_type
5
+ end
6
+
7
+ def build
8
+ resource(policies, managed_policy_arns)
9
+ end
10
+
11
+ def build?
12
+ !!(policies || managed_policy_arns)
13
+ end
14
+
15
+ def policies
16
+ items = Registry.policies[@role_type] # Array of Arrays
17
+ return unless items && !items.empty?
18
+
19
+ items.map do |item|
20
+ policy_name, statements = item # first element has policy name, second element has statements
21
+ {
22
+ PolicyName: policy_name,
23
+ PolicyDocument: {
24
+ Version: "2012-10-17",
25
+ Statement: statements
26
+ }
27
+ }
28
+ end
29
+ end
30
+
31
+ def managed_policy_arns
32
+ items = Registry.managed_policies[@role_type] # Array of Arrays
33
+ return unless items && !items.empty?
34
+
35
+ items.map do |item|
36
+ item.include?('iam::aws:policy') ? item : "arn:aws:iam::aws:policy/#{item}"
37
+ end
38
+ end
39
+
40
+ def resource(policies, managed_policy_arns)
41
+ properties = {
42
+ AssumeRolePolicyDocument: {
43
+ Version: "2012-10-17",
44
+ Statement: [
45
+ {
46
+ Effect: "Allow",
47
+ Principal: {
48
+ Service: "ecs-tasks.amazonaws.com"
49
+ },
50
+ Action: "sts:AssumeRole"
51
+ }
52
+ ]
53
+ },
54
+ }
55
+ properties[:Policies] = policies if policies
56
+ properties[:ManagedPolicyArns] = managed_policy_arns if managed_policy_arns
57
+
58
+ attrs = {
59
+ Type: "AWS::IAM::Role",
60
+ Properties: properties
61
+ }
62
+
63
+ attrs.deep_stringify_keys
64
+ end
65
+ end
66
+ end
@@ -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
 
@@ -17,22 +17,6 @@ module Ufo
17
17
  File.basename(Dir.pwd)
18
18
  end
19
19
 
20
- def get_execution_role_arn_input
21
- return @execution_role_arn if @execution_role_arn
22
-
23
- if @options[:execution_role_arn]
24
- @execution_role_arn = @options[:execution_role_arn]
25
- return @execution_role_arn
26
- end
27
-
28
- return unless @options[:launch_type] == "fargate"
29
- # execution role arn required for fargate
30
- puts "For fargate ECS tasks an ECS Task Execution IAM Role is required. "
31
- puts "More details here: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html"
32
- print "Please provide a execution role arn role for the ecs task: "
33
- @execution_role_arn = $stdin.gets.strip
34
- end
35
-
36
20
  def override_source_paths(*paths)
37
21
  # Using string with instance_eval because block doesnt have access to
38
22
  # path at runtime.