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
@@ -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
@@ -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
 
@@ -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.
@@ -2,22 +2,36 @@ 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
20
- YAML.load(text).deep_symbolize_keys
23
+ text = RenderMePretty.result(found)
24
+ specific_data = YAML.load(text).deep_symbolize_keys
25
+
26
+ base = "#{Ufo.root}/.ufo/settings/#{@type}/base.yml"
27
+ base_data = if File.exist?(base)
28
+ text = RenderMePretty.result(base)
29
+ YAML.load(text).deep_symbolize_keys
30
+ else
31
+ {}
32
+ end
33
+
34
+ base_data.deep_merge(specific_data)
21
35
  end
22
36
  memoize :data
23
37
  end