ufo 4.6.1 → 5.0.2

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 +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/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 +3 -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 +19 -37
  73. data/lib/ufo/dsl/helper/vars.rb +97 -0
  74. data/lib/ufo/dsl/outputter.rb +12 -9
  75. data/lib/ufo/ecr/auth.rb +10 -21
  76. data/lib/ufo/help/init.md +1 -1
  77. data/lib/ufo/init.rb +0 -2
  78. data/lib/ufo/log_group.rb +1 -0
  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 +71 -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/template_scope.rb +1 -66
  118. data/lib/ufo/utils/squeezer.rb +24 -0
  119. data/lib/ufo/version.rb +1 -1
  120. data/spec/fixtures/iam_roles/task_role.rb +17 -0
  121. data/spec/lib/ecr_auth_spec.rb +32 -20
  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
@@ -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,48 +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 do |x|
34
- remove_surrounding_quotes(x.strip)
35
- end
36
- value = value.join('=')
37
- {
38
- name: key,
39
- value: value,
40
- }
41
- end
39
+ def env(text)
40
+ Vars.new(text: text).env
42
41
  end
42
+ alias_method :env_vars, :env
43
43
 
44
- def remove_surrounding_quotes(s)
45
- if s =~ /^"/ && s =~ /"$/
46
- s.sub(/^["]/, '').gsub(/["]$/,'') # remove surrounding double quotes
47
- elsif s =~ /^'/ && s =~ /'$/
48
- s.sub(/^[']/, '').gsub(/[']$/,'') # remove surrounding single quotes
49
- else
50
- s
51
- end
44
+ def env_file(path)
45
+ Vars.new(file: path).env
52
46
  end
53
47
 
54
- def filtered_lines(text)
55
- lines = text.split("\n")
56
- # remove comment at the end of the line
57
- lines.map! { |l| l.sub(/\s+#.*/,'').strip }
58
- # filter out commented lines
59
- lines = lines.reject { |l| l =~ /(^|\s)#/i }
60
- # filter out empty lines
61
- lines = lines.reject { |l| l.strip.empty? }
48
+ def secrets(text)
49
+ Vars.new(text: text).secrets
62
50
  end
63
51
 
64
- def env_file(path)
65
- full_path = "#{Ufo.root}/#{path}"
66
- unless File.exist?(full_path)
67
- puts "The #{full_path} env file could not be found. Are you sure it exists?"
68
- exit 1
69
- end
70
- text = IO.read(full_path)
71
- env_vars(text)
52
+ def secrets_file(path)
53
+ Vars.new(file: path).secrets
72
54
  end
73
55
 
74
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
@@ -1,3 +1,5 @@
1
+ require 'open3'
2
+
1
3
  =begin
2
4
  Normally, you must authorized to AWS ECR to push to their registry with:
3
5
 
@@ -27,19 +29,15 @@ module Ufo
27
29
  return unless ecr_image?
28
30
 
29
31
  auth_token = fetch_auth_token
30
- if File.exist?(docker_config)
31
- data = JSON.load(IO.read(docker_config))
32
- data["auths"][@repo_domain] = {auth: auth_token}
33
- else
34
- data = {"auths" => {@repo_domain => {auth: auth_token}}}
32
+ username, password = Base64.decode64(auth_token).split(':')
33
+
34
+ command = "docker login -u #{username} --password-stdin #{@repo_domain}"
35
+ puts "=> #{command}".color(:green)
36
+ *, status = Open3.capture3(command, stdin_data: password)
37
+ unless status.success?
38
+ puts "ERROR: The docker failed to login.".color(:red)
39
+ exit 1
35
40
  end
36
-
37
- # Handle legacy docker clients that still have old format with https://
38
- legacy_entry = "https://#{@repo_domain}"
39
- data["auths"][legacy_entry] = {auth: auth_token}
40
-
41
- ensure_dotdocker_exists
42
- IO.write(docker_config, JSON.pretty_generate(data))
43
41
  end
44
42
 
45
43
  def ecr_image?
@@ -50,14 +48,5 @@ module Ufo
50
48
  ecr.get_authorization_token.authorization_data.first.authorization_token
51
49
  end
52
50
 
53
- def docker_config
54
- "#{ENV['HOME']}/.docker/config.json"
55
- end
56
-
57
- def ensure_dotdocker_exists
58
- dirname = File.dirname(docker_config)
59
- FileUtils.mkdir_p(dirname) unless File.exist?(dirname)
60
- end
61
-
62
51
  end
63
52
  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))
@@ -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