lono 3.5.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -4
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +15 -1
  5. data/Gemfile +3 -3
  6. data/Guardfile +17 -8
  7. data/{LICENSE → LICENSE.txt} +1 -1
  8. data/README.md +20 -12
  9. data/Rakefile +1 -2
  10. data/{bin → exe}/lono +1 -0
  11. data/lib/lono.rb +12 -9
  12. data/lib/lono/cfn.rb +7 -9
  13. data/lib/lono/cfn/{aws_services.rb → aws_service.rb} +1 -1
  14. data/lib/lono/cfn/base.rb +41 -38
  15. data/lib/lono/cfn/create.rb +6 -2
  16. data/lib/lono/cfn/delete.rb +2 -2
  17. data/lib/lono/cfn/diff.rb +1 -1
  18. data/lib/lono/cfn/preview.rb +26 -15
  19. data/lib/lono/cfn/update.rb +11 -9
  20. data/lib/lono/cfn/util.rb +3 -3
  21. data/lib/lono/clean.rb +1 -1
  22. data/lib/lono/cli.rb +71 -39
  23. data/lib/lono/command.rb +42 -18
  24. data/lib/lono/completer.rb +162 -0
  25. data/lib/lono/completer/script.rb +6 -0
  26. data/lib/lono/completer/script.sh +10 -0
  27. data/lib/lono/completion.rb +15 -0
  28. data/lib/lono/core.rb +23 -9
  29. data/lib/lono/core/config.rb +20 -0
  30. data/lib/lono/default/settings.yml +33 -13
  31. data/lib/lono/help.rb +6 -79
  32. data/lib/lono/help/cfn.md +6 -0
  33. data/lib/lono/help/cfn/create.md +22 -0
  34. data/lib/lono/help/cfn/delete.md +5 -0
  35. data/lib/lono/help/cfn/diff.md +5 -0
  36. data/lib/lono/help/cfn/download.md +5 -0
  37. data/lib/lono/help/cfn/preview.md +11 -0
  38. data/lib/lono/help/cfn/update.md +21 -0
  39. data/lib/lono/help/completion.md +22 -0
  40. data/lib/lono/help/completion_script.md +3 -0
  41. data/lib/lono/help/generate.md +7 -0
  42. data/lib/lono/help/hello.md +5 -0
  43. data/lib/lono/help/import.md +7 -0
  44. data/lib/lono/help/inspect.md +4 -0
  45. data/lib/lono/help/inspect/depends.md +3 -0
  46. data/lib/lono/help/inspect/summary.md +3 -0
  47. data/lib/lono/help/new.md +8 -0
  48. data/lib/lono/help/param.md +3 -0
  49. data/lib/lono/{param/help.rb → help/param/generate.md} +1 -9
  50. data/lib/lono/help/script/build.md +5 -0
  51. data/lib/lono/help/script/upload.md +8 -0
  52. data/lib/lono/help/template.md +4 -0
  53. data/lib/lono/help/template/bashify.md +4 -0
  54. data/lib/lono/help/template/generate.md +7 -0
  55. data/lib/lono/help/user_data.md +3 -0
  56. data/lib/lono/importer.rb +43 -20
  57. data/lib/lono/inspector.rb +2 -19
  58. data/lib/lono/inspector/base.rb +2 -2
  59. data/lib/lono/inspector/{depends.rb → graph.rb} +3 -3
  60. data/lib/lono/inspector/summary.rb +1 -1
  61. data/lib/lono/new.rb +79 -26
  62. data/lib/lono/new/helper.rb +16 -0
  63. data/lib/lono/new/message.rb +35 -0
  64. data/lib/lono/param.rb +1 -2
  65. data/lib/lono/param/generator.rb +34 -86
  66. data/lib/lono/project_checker.rb +35 -40
  67. data/lib/lono/script.rb +19 -0
  68. data/lib/lono/script/base.rb +9 -0
  69. data/lib/lono/script/build.rb +73 -0
  70. data/lib/lono/script/upload.rb +81 -0
  71. data/lib/lono/sequence.rb +33 -0
  72. data/lib/lono/setting.rb +83 -0
  73. data/lib/lono/template.rb +8 -9
  74. data/lib/lono/template/{aws_services.rb → aws_service.rb} +1 -1
  75. data/lib/lono/template/context.rb +73 -0
  76. data/lib/lono/template/dsl.rb +63 -64
  77. data/lib/lono/template/helper.rb +201 -0
  78. data/lib/lono/template/template.rb +29 -221
  79. data/lib/lono/template/upload.rb +41 -33
  80. data/lib/lono/upgrade4.rb +175 -0
  81. data/lib/lono/user_data.rb +31 -0
  82. data/lib/lono/version.rb +1 -1
  83. data/lib/starter_projects/autoscaling/.gitignore +1 -0
  84. data/lib/starter_projects/{json_project → autoscaling}/Gemfile +0 -0
  85. data/lib/starter_projects/{yaml_project → autoscaling}/Guardfile +0 -0
  86. data/lib/starter_projects/autoscaling/README.md +118 -0
  87. data/lib/starter_projects/autoscaling/app/definitions/base.rb +2 -0
  88. data/lib/starter_projects/autoscaling/app/templates/autoscaling.yml +682 -0
  89. data/lib/starter_projects/autoscaling/config/params/base/autoscaling.txt +6 -0
  90. data/lib/starter_projects/autoscaling/config/settings.yml +33 -0
  91. data/lib/starter_projects/ec2/.gitignore +1 -0
  92. data/lib/starter_projects/{yaml_project → ec2}/Gemfile +0 -0
  93. data/lib/starter_projects/{json_project → ec2}/Guardfile +1 -1
  94. data/lib/starter_projects/ec2/README.md +86 -0
  95. data/lib/starter_projects/ec2/app/definitions/base.rb +2 -0
  96. data/lib/starter_projects/ec2/app/definitions/development.rb +1 -0
  97. data/lib/starter_projects/ec2/app/definitions/production.rb +1 -0
  98. data/lib/starter_projects/{yaml_project → ec2/app}/helpers/my_custom_helper.rb +0 -0
  99. data/lib/starter_projects/{json_project/templates/user_data/app.sh → ec2/app/partials/user_data/bootstrap.sh} +1 -2
  100. data/lib/starter_projects/{yaml_project → ec2/app}/templates/example.yml +0 -0
  101. data/lib/starter_projects/{json_project/params/base/api-web.txt → ec2/config/params/base/example.txt} +0 -0
  102. data/lib/starter_projects/ec2/config/params/development/example.txt +3 -0
  103. data/lib/starter_projects/ec2/config/params/production/example.txt +2 -0
  104. data/lib/starter_projects/ec2/config/settings.yml +33 -0
  105. data/lib/starter_projects/ec2/config/variables/base.rb +3 -0
  106. data/lib/starter_projects/ec2/config/variables/development.rb +2 -0
  107. data/lib/starter_projects/ec2/config/variables/production.rb +2 -0
  108. data/lib/starter_projects/ec2/welcome.txt +8 -0
  109. data/lib/starter_projects/skeleton/.gitignore +1 -0
  110. data/lib/starter_projects/skeleton/Gemfile +3 -0
  111. data/lib/starter_projects/skeleton/Guardfile +12 -0
  112. data/lib/starter_projects/skeleton/README.md +53 -0
  113. data/{spec/fixtures/my_project/templates/.gitkeep → lib/starter_projects/skeleton/app/definitions/base.rb} +0 -0
  114. data/lib/starter_projects/skeleton/config/settings.yml +33 -0
  115. data/lib/starter_projects/skeleton/welcome.txt +7 -0
  116. data/lono.gemspec +12 -10
  117. data/spec/fixtures/lono_project/.gitignore +1 -0
  118. data/spec/fixtures/lono_project/Gemfile +3 -0
  119. data/spec/fixtures/lono_project/Guardfile +12 -0
  120. data/spec/fixtures/lono_project/app/definitions/base.rb +10 -0
  121. data/spec/fixtures/lono_project/app/definitions/base/more.rb +7 -0
  122. data/spec/fixtures/lono_project/app/definitions/development.rb +1 -0
  123. data/spec/fixtures/lono_project/app/definitions/production.rb +1 -0
  124. data/spec/fixtures/lono_project/app/helpers/custom_helper.rb +5 -0
  125. data/spec/fixtures/lono_project/app/partials/security_group.yml +10 -0
  126. data/{lib/starter_projects/yaml_project/templates/partial → spec/fixtures/lono_project/app/partials}/user_data/bootstrap.sh +8 -2
  127. data/spec/fixtures/lono_project/app/templates/example.yml +50 -0
  128. data/{lib/starter_projects/yaml_project/params/base/api-web-prod.txt → spec/fixtures/lono_project/config/params/base/example.txt} +1 -0
  129. data/spec/fixtures/lono_project/config/params/development/example.txt +1 -0
  130. data/spec/fixtures/lono_project/config/params/production/example.txt +1 -0
  131. data/spec/fixtures/lono_project/config/settings.yml +31 -0
  132. data/spec/fixtures/lono_project/config/variables/base.rb +3 -0
  133. data/spec/fixtures/lono_project/config/variables/development.rb +1 -0
  134. data/spec/fixtures/lono_project/config/variables/production.rb +1 -0
  135. data/spec/fixtures/params/envonly/params/{prod → development}/network.txt +0 -0
  136. data/spec/fixtures/params/overlay/params/{prod → development}/network.txt +0 -0
  137. data/spec/fixtures/raw_templates/aws-waf-security-automations.template +2 -2
  138. data/spec/lib/lono/cfn_spec.rb +6 -9
  139. data/spec/lib/lono/cli_spec.rb +44 -0
  140. data/spec/lib/lono/completion_spec.rb +17 -0
  141. data/spec/lib/lono/inspect_spec.rb +6 -15
  142. data/spec/lib/lono/param/generator_spec.rb +45 -26
  143. data/spec/lib/lono/param_spec.rb +1 -3
  144. data/spec/lib/lono/setting_spec.rb +47 -0
  145. data/spec/lib/lono/template/dsl_spec.rb +33 -157
  146. data/spec/lib/lono/template_spec.rb +4 -16
  147. data/spec/spec_helper.rb +45 -14
  148. metadata +168 -82
  149. data/.coveralls.yml +0 -1
  150. data/lib/lono/cfn/help.rb +0 -103
  151. data/lib/lono/current_region.rb +0 -42
  152. data/lib/lono/inspector/help.rb +0 -21
  153. data/lib/lono/settings.rb +0 -45
  154. data/lib/lono/template/help.rb +0 -25
  155. data/lib/lono/template/helpers.rb +0 -136
  156. data/lib/starter_projects/json_project/.gitignore +0 -1
  157. data/lib/starter_projects/json_project/config/templates/base/blog.rb +0 -20
  158. data/lib/starter_projects/json_project/config/templates/base/stacks.rb +0 -58
  159. data/lib/starter_projects/json_project/templates/db.json +0 -212
  160. data/lib/starter_projects/json_project/templates/partial/host_record.json +0 -28
  161. data/lib/starter_projects/json_project/templates/partial/server.json +0 -45
  162. data/lib/starter_projects/json_project/templates/user_data/db.sh +0 -39
  163. data/lib/starter_projects/json_project/templates/user_data/db2.sh +0 -2
  164. data/lib/starter_projects/json_project/templates/user_data/ruby_script.rb +0 -5
  165. data/lib/starter_projects/json_project/templates/web.json +0 -386
  166. data/lib/starter_projects/yaml_project/.gitignore +0 -1
  167. data/lib/starter_projects/yaml_project/config/templates/base/blog.rb +0 -20
  168. data/lib/starter_projects/yaml_project/config/templates/base/stacks.rb +0 -56
  169. data/lib/starter_projects/yaml_project/config/templates/prod/stacks.rb +0 -1
  170. data/lib/starter_projects/yaml_project/config/templates/stag/stacks.rb +0 -1
  171. data/lib/starter_projects/yaml_project/config/variables/base/variables.rb +0 -4
  172. data/lib/starter_projects/yaml_project/config/variables/prod/variables.rb +0 -1
  173. data/lib/starter_projects/yaml_project/config/variables/stag/variables.rb +0 -1
  174. data/lib/starter_projects/yaml_project/params/base/example.txt +0 -2
  175. data/lib/starter_projects/yaml_project/params/prod/example.txt +0 -1
  176. data/lib/starter_projects/yaml_project/params/stag/example.txt +0 -1
  177. data/lib/starter_projects/yaml_project/templates/db.yml +0 -148
  178. data/lib/starter_projects/yaml_project/templates/partial/host_record.yml +0 -14
  179. data/lib/starter_projects/yaml_project/templates/partial/server.yml +0 -59
  180. data/lib/starter_projects/yaml_project/templates/web.yml +0 -206
  181. data/spec/fixtures/my_project/config/templates/base/stacks.rb +0 -3
  182. data/spec/fixtures/my_project/params/my-stack.txt +0 -3
  183. data/spec/fixtures/my_project/templates/my-stack.yml +0 -0
  184. data/spec/lib/lono/new_spec.rb +0 -59
  185. data/spec/lib/lono/template/template_spec.rb +0 -104
  186. data/spec/lib/lono_spec.rb +0 -27
@@ -1,11 +1,10 @@
1
1
  class Lono::Template::DSL
2
2
  def initialize(options={})
3
3
  @options = options
4
- @config_path = "#{Lono.root}/config"
5
4
  Lono::ProjectChecker.check
5
+ Lono::ProjectChecker.empty_templates
6
6
  @templates = []
7
7
  @results = {}
8
- @detected_format = nil
9
8
  end
10
9
 
11
10
  def run(options={})
@@ -14,24 +13,29 @@ class Lono::Template::DSL
14
13
  write_output
15
14
  end
16
15
 
17
- # Instance eval's all the files within each folder under
18
- # config/lono/base and config/lono/[Lono.env]
19
- # Base gets base first and then the Lono.env configs get evaluate second.
20
- # This means the env specific configs override the base configs.
16
+ # Instance eval's the template declarations in app/definitions in this order:
17
+ #
18
+ # app/definitions/base.rb
19
+ # app/definitions/base - all files in folder
20
+ # app/definitions/[Lono.env].rb
21
+ # app/definitions/[Lono.env] - all files in folder
22
+ #
23
+ # So Lono.env specific template declarations override base template declarations.
21
24
  def evaluate_templates
25
+ evaluate_template("base")
22
26
  evaluate_folder("base")
27
+ evaluate_template(Lono.env)
23
28
  evaluate_folder(Lono.env)
24
- @detected_format = detect_format
25
29
  end
26
30
 
27
- def evaluate_folder(folder)
28
- paths = Dir.glob("#{@config_path}/templates/#{folder}/**/*")
29
- paths.select{ |e| File.file?(e) }.each do |path|
30
- evaluate_template(path)
31
- end
31
+ def evaluate_template(name)
32
+ path = "#{Lono.config.definitions_path}/#{name}.rb"
33
+ evaluate_template_path(path)
32
34
  end
33
35
 
34
- def evaluate_template(path)
36
+ def evaluate_template_path(path)
37
+ return unless File.exist?(path)
38
+
35
39
  begin
36
40
  instance_eval(File.read(path), path)
37
41
  rescue Exception => e
@@ -41,6 +45,13 @@ class Lono::Template::DSL
41
45
  end
42
46
  end
43
47
 
48
+ def evaluate_folder(folder)
49
+ paths = Dir.glob("#{Lono.config.definitions_path}/#{folder}/**/*")
50
+ paths.select{ |e| File.file?(e) }.each do |path|
51
+ evaluate_template_path(path)
52
+ end
53
+ end
54
+
44
55
  # Prints out a user friendly task_definition error message
45
56
  def template_evaluation_error(e)
46
57
  error_info = e.backtrace.first
@@ -65,85 +76,73 @@ class Lono::Template::DSL
65
76
  end
66
77
  end
67
78
 
68
- # Detects the format of the templates. Checks the extension of all the
69
- # templates files.
70
- # All the templates must be of the same format, either all json or all yaml.
71
- def detect_format
72
- extensions = Dir.glob("#{Lono.root}/templates/**/*").map do |path|
73
- File.extname(path).sub(/^\./,'')
74
- end.reject(&:empty?).uniq
75
- extensions.include?('yml') ? 'yml' : 'json' # defaults to yml - falls back to json
76
- end
77
-
78
79
  def template(name, &block)
79
80
  @templates << {name: name, block: block}
80
81
  end
81
82
 
82
83
  def build_templates
83
- options = @options.merge(detected_format: @detected_format)
84
84
  @templates.each do |t|
85
- @results[t[:name]] = Lono::Template::Template.new(t[:name], t[:block], options).build
85
+ @results[t[:name]] = Lono::Template::Template.new(t[:name], t[:block], @options).build
86
86
  end
87
87
  end
88
88
 
89
89
  def write_output
90
- output_path = "#{Lono.root}/output"
90
+ output_path = "#{Lono.config.output_path}/templates"
91
91
  FileUtils.rm_rf(output_path) if @options[:clean]
92
- FileUtils.mkdir(output_path) unless File.exist?(output_path)
92
+ FileUtils.mkdir_p(output_path)
93
93
  puts "Generating CloudFormation templates:" unless @options[:quiet]
94
94
  @results.each do |name,text|
95
95
  path = "#{output_path}/#{name}".sub(/^\.\//,'') # strip leading '.'
96
- path += ".#{@detected_format}"
96
+ path += ".yml"
97
97
  puts " #{path}" unless @options[:quiet]
98
98
  ensure_parent_dir(path)
99
- validate(text, path)
100
- File.open(path, 'w') do |f|
101
- f.write(output_format(text))
102
- end
103
- end
104
- end
105
-
106
- def validate(text, path)
107
- if @detected_format == "json"
108
- validate_json(text, path)
109
- else
110
- validate_yaml(text, path)
99
+ text = commented(text)
100
+ IO.write(path, text) # write file first so validate method is simpler
101
+ validate(path)
111
102
  end
112
103
  end
113
104
 
114
- def validate_yaml(yaml, path)
105
+ def validate(path)
106
+ text = IO.read(path)
115
107
  begin
116
- YAML.load(yaml)
108
+ YAML.load(text)
117
109
  rescue Psych::SyntaxError => e
118
- puts "Invalid yaml. Output written to #{path} for debugging".colorize(:red)
119
- puts "ERROR: #{e.message}".colorize(:red)
120
- File.open(path, 'w') {|f| f.write(yaml) }
121
- exit 1
110
+ handle_yaml_syntax_error(e, path)
122
111
  end
123
112
  end
124
113
 
125
- def validate_json(json, path)
126
- begin
127
- JSON.parse(json)
128
- rescue JSON::ParserError => e
129
- puts "Invalid json. Output written to #{path} for debugging".colorize(:red)
130
- puts "ERROR: #{e.message}".colorize(:red)
131
- File.open(path, 'w') {|f| f.write(json) }
132
- exit 1
133
- end
134
- end
114
+ def handle_yaml_syntax_error(e, path)
115
+ io = StringIO.new
116
+ io.puts "Invalid yaml. Output written to debugging: #{path}".colorize(:red)
117
+ io.puts "ERROR: #{e.message}".colorize(:red)
135
118
 
136
- def output_format(text)
137
- @options[:pretty] ? prettify(text) : text
138
- end
119
+ # Grab line info. Example error:
120
+ # ERROR: (<unknown>): could not find expected ':' while scanning a simple key at line 2 column 1
121
+ md = e.message.match(/at line (\d+) column (\d+)/)
122
+ line = md[1].to_i
139
123
 
140
- # Input text is either yaml or json.
141
- # Do not prettify yaml format because it removes the !Ref like CloudFormation notation
142
- def prettify(text)
143
- @detected_format == "json" ? JSON.pretty_generate(JSON.parse(text)) : yaml_format(text)
124
+ lines = IO.read(path).split("\n")
125
+ context = 5 # lines of context
126
+ top, bottom = [line-context-1, 0].max, line+context-1
127
+ spacing = lines.size.to_s.size
128
+ lines[top..bottom].each_with_index do |line_content, index|
129
+ line_number = top+index+1
130
+ if line_number == line
131
+ io.printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
132
+ else
133
+ io.printf("%#{spacing}d %s\n", line_number, line_content)
134
+ end
135
+ end
136
+
137
+ if ENV['TEST']
138
+ io.string
139
+ else
140
+ puts io.string
141
+ exit 1
142
+ end
144
143
  end
145
144
 
146
- def yaml_format(text)
145
+ def commented(text)
147
146
  comment =<<~EOS
148
147
  # This file was generated with lono. Do not edit directly, the changes will be lost.
149
148
  # More info: http://lono.cloud
@@ -0,0 +1,201 @@
1
+ # This is included into Lono::Template::Context.
2
+ # It has access to the original thor CLI options via @options.
3
+ #
4
+ # @options gets passed into:
5
+ #
6
+ # Lono::Template::Context.new(@options)
7
+ module Lono::Template::Helper
8
+ # Bash code that is meant to included in user-data
9
+ def extract_scripts(options={})
10
+ check_s3_folder_settings!
11
+
12
+ settings = setting.data["extract_scripts"] || {}
13
+ options = settings.merge(options)
14
+ # defaults also here in case they are removed from settings
15
+ to = options[:to] || "/opt"
16
+ user = options[:as] || "ec2-user"
17
+
18
+ if Dir.glob("#{Lono.config.scripts_path}/*").empty?
19
+ puts "WARN: you are using the extract_scripts helper method but you do not have any app/scripts.".colorize(:yellow)
20
+ calling_line = caller[0].split(':')[0..1].join(':')
21
+ puts "Called from: #{calling_line}"
22
+ return ""
23
+ end
24
+
25
+ <<-BASH_CODE
26
+ # Generated from the lono extract_scripts helper.
27
+ # Downloads scripts from s3, extract them, and setup.
28
+ mkdir -p #{to}
29
+ aws s3 cp #{scripts_s3_path} #{to}/
30
+ cd #{to}
31
+ tar zxf #{to}/#{scripts_name}
32
+ chmod -R a+x #{to}/scripts
33
+ chown -R #{user}:#{user} #{to}/scripts
34
+ BASH_CODE
35
+ end
36
+
37
+ def check_s3_folder_settings!
38
+ return if setting.s3_folder
39
+
40
+ puts "Helper method called that requires the s3_folder to be set at:"
41
+ lines = caller.reject { |l| l =~ %r{lib/lono} } # hide internal lono trace
42
+ puts " #{lines[0]}"
43
+
44
+ puts "Please configure your settings.yml with an s3_folder.".colorize(:red)
45
+ puts "Detected AWS_PROFILE #{ENV['AWS_PROFILE'].inspect}"
46
+ exit 1
47
+ end
48
+
49
+ def scripts_name
50
+ File.basename(scripts_s3_path)
51
+ end
52
+
53
+ def scripts_s3_path
54
+ upload = Lono::Script::Upload.new
55
+ upload.s3_dest
56
+ end
57
+
58
+ def template_s3_path(template_name)
59
+ check_s3_folder_settings!
60
+ # high jacking Upload for useful s3_https_url method
61
+ template_path = "#{template_name}.yml"
62
+ upload = Lono::Template::Upload.new(@options)
63
+ upload.s3_https_url(template_path)
64
+ end
65
+
66
+ def template_params(param_name)
67
+ generator_options = {
68
+ allow_no_file: true
69
+ }.merge(@options)
70
+ generator = Lono::Param::Generator.new(param_name, generator_options)
71
+ # do not generate because lono cfn calling logic already generated it we only need the values
72
+ generator.params # Returns Array in underscore keys format
73
+ end
74
+
75
+ # Adjust the partial path so that it will use app/user_data
76
+ def user_data(path,vars={}, options={})
77
+ options.merge!(user_data: true)
78
+ partial(path,vars, options)
79
+ end
80
+
81
+ # The partial's path is a relative path.
82
+ #
83
+ # Example:
84
+ # Given file in app/partials/iam/docker.yml
85
+ #
86
+ # <%= partial("iam/docker", {}, indent: 10) %>
87
+ # <%= partial("iam/docker.yml", {}, indent: 10) %>
88
+ #
89
+ # If the user specifies the extension then use that instead of auto-adding
90
+ # the detected format.
91
+ def partial(path,vars={}, options={})
92
+ path = options[:user_data] ?
93
+ user_data_path_for(path) :
94
+ partial_path_for(path)
95
+ path = auto_add_format(path)
96
+
97
+ instance_variables!(vars)
98
+ result = render_path(path)
99
+
100
+ result = indent(result, options[:indent]) if options[:indent]
101
+ result + "\n"
102
+ end
103
+
104
+ # add indentation
105
+ def indent(text, indentation_amount)
106
+ text.split("\n").map do |line|
107
+ " " * indentation_amount + line
108
+ end.join("\n")
109
+ end
110
+
111
+ def partial_exist?(path)
112
+ path = partial_path_for(path)
113
+ path = auto_add_format(path)
114
+ path && File.exist?(path)
115
+ end
116
+
117
+ def current_region
118
+ region = Aws.config[:region]
119
+ region ||= ENV['AWS_REGION']
120
+ return region if region
121
+
122
+ default_region = 'us-east-1' # fallback if default not found in ~/.aws/config
123
+ if ENV['AWS_PROFILE']
124
+ path = "#{ENV['HOME']}/.aws/config"
125
+ if File.exist?(path)
126
+ lines = IO.readlines(path)
127
+ capture_default, capture_current = false, false
128
+ lines.each do | line|
129
+ if line.include?('[default]')
130
+ capture_default = true # next line
131
+ next
132
+ end
133
+ if capture_default && line.match(/region = /)
134
+ # over default from above
135
+ default_region = line.split(' = ').last.strip
136
+ capture_default = false
137
+ end
138
+
139
+ md = line.match(/\[profile (.*)\]/)
140
+ if md && md[1] == ENV['AWS_PROFILE']
141
+ capture_current = true
142
+ next
143
+ end
144
+ if capture_current && line.match(/region = /)
145
+ region = line.split(' = ').last.strip
146
+ capture_current = false
147
+ end
148
+ end
149
+ end
150
+
151
+ region ||= default_region
152
+ return region if region
153
+ end
154
+
155
+ 'us-east-1' # default
156
+ end
157
+
158
+ private
159
+ def render_path(path)
160
+ RenderMePretty.result(path, context: self)
161
+ end
162
+
163
+ def user_data_path_for(path)
164
+ "#{Lono.config.user_data_path}/#{path}"
165
+ end
166
+
167
+ def partial_path_for(path)
168
+ "#{Lono.config.partials_path}/#{path}"
169
+ end
170
+
171
+ def auto_add_format(path)
172
+ # Return immediately if user provided explicit extension
173
+ extension = File.extname(path) # current extension
174
+ return path if !extension.empty?
175
+
176
+ # Else let's auto detect
177
+ paths = Dir.glob("#{path}.*")
178
+
179
+ if paths.size == 1 # non-ambiguous match
180
+ return paths.first
181
+ end
182
+
183
+ if paths.size > 1 # ambiguous match
184
+ puts "ERROR: Multiple possible partials found:".colorize(:red)
185
+ paths.each do |path|
186
+ puts " #{path}"
187
+ end
188
+ puts "Please specify an extension in the name to remove the ambiguity.".colorize(:green)
189
+ exit 1
190
+ end
191
+
192
+ # Account for case when user wants to include a file with no extension at all
193
+ return path if File.exist?(path) && !File.directory?(path)
194
+
195
+ path # original path if this point is reached
196
+ end
197
+
198
+ def setting
199
+ @setting ||= Lono::Setting.new
200
+ end
201
+ end
@@ -3,249 +3,57 @@ require 'json'
3
3
  require 'base64'
4
4
 
5
5
  class Lono::Template::Template
6
- include Lono::Template::Helpers
7
- include Lono::CurrentRegion
8
6
  include ERB::Util
9
7
 
8
+ # Main template DSL methods are: source and variables
9
+ #
10
+ # template "example-2" do
11
+ # source "example"
12
+ # variables(test: 1)
13
+ # end
14
+ #
15
+ attr_reader :name
10
16
  def initialize(name, block=nil, options={})
11
17
  # Taking care to name instance variables with _ in front because we load the
12
18
  # variables from config/variables and those instance variables can clobber these
13
19
  # instance variables
14
- @_name = name
15
- @_options = options
16
- @_detected_format = options[:detected_format]
17
- @_block = block
18
- @_config_path = "#{Lono.root}/config"
19
- @_source = default_source(name)
20
- end
21
-
22
- def default_source(name)
23
- "#{Lono.root}/templates/#{name}.#{@_detected_format}" # defaults to name, source method overrides
24
- end
25
-
26
- def build
27
- load_variables
28
- load_custom_helpers
29
- instance_eval(&@_block) if @_block
30
- template = IO.read(@_source)
31
- erb_result(@_source, template)
32
- end
33
-
34
- def load_variables
35
- load_variables_folder("base")
36
- load_variables_folder(Lono.env)
37
- end
38
-
39
- # Load the variables defined in config/variables/* to make available in the
40
- # template blocks in config/templates/*.
41
- #
42
- # Example:
43
- #
44
- # `config/variables/base/variables.rb`:
45
- # @foo = 123
46
- #
47
- # `config/templates/base/resources.rb`:
48
- # template "mytemplate.yml" do
49
- # source "mytemplate.yml.erb"
50
- # variables(foo: @foo)
51
- # end
52
- #
53
- # NOTE: Only able to make instance variables avaialble with instance_eval
54
- # Wasnt able to make local variables available.
55
- def load_variables_folder(folder)
56
- paths = Dir.glob("#{@_config_path}/variables/#{folder}/**/*")
57
- paths.select{ |e| File.file? e }.each do |path|
58
- instance_eval(IO.read(path))
59
- end
60
- end
61
-
62
- # Load custom helper methods from the user's infra repo
63
- def load_custom_helpers
64
- Dir.glob("#{Lono.root}/helpers/**/*_helper.rb").each do |path|
65
- filename = path.sub(%r{.*/},'').sub('.rb','')
66
- module_name = filename.classify
67
-
68
- require path
69
- self.class.send :include, module_name.constantize
70
- end
20
+ @name = name
21
+ @block = block
22
+ @options = options
23
+ @source_path = default_source_path(name)
71
24
  end
72
25
 
26
+ # Returns path, example: ./app/templates/example.yml
73
27
  def source(path)
74
- @_source = path[0..0] == '/' ? path : "#{Lono.root}/templates/#{path}"
75
- @_source += ".#{@_detected_format}"
28
+ @source_path = path[0..0] == '/' ? path : "#{Lono.config.templates_path}/#{path}"
29
+ @source_path += ".yml"
76
30
  end
77
31
 
78
32
  def variables(vars={})
79
33
  vars.each do |var,value|
80
- instance_variable_set("@#{var}", value)
34
+ context.instance_variable_set("@#{var}", value)
81
35
  end
82
36
  end
83
37
 
84
- def erb_result(path, template)
85
- begin
86
- ERB.new(template, nil, "-").result(binding)
87
- rescue Exception => e
88
- puts e
89
- puts e.backtrace if ENV['DEBUG']
90
-
91
- # how to know where ERB stopped? - https://www.ruby-forum.com/topic/182051
92
- # syntax errors have the (erb):xxx info in e.message
93
- # undefined variables have (erb):xxx info in e.backtrac
94
- error_info = e.message.split("\n").grep(/\(erb\)/)[0]
95
- error_info ||= e.backtrace.grep(/\(erb\)/)[0]
96
- raise unless error_info # unable to find the (erb):xxx: error line
97
- line = error_info.split(':')[1].to_i
98
- puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '').colorize(:green)}"
99
-
100
- template_lines = template.split("\n")
101
- context = 5 # lines of context
102
- top, bottom = [line-context-1, 0].max, line+context-1
103
- spacing = template_lines.size.to_s.size
104
- template_lines[top..bottom].each_with_index do |line_content, index|
105
- line_number = top+index+1
106
- if line_number == line
107
- printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
108
- else
109
- printf("%#{spacing}d %s\n", line_number, line_content)
110
- end
111
- end
112
- exit 1 unless ENV['TEST']
113
- end
38
+ # internal methods
39
+ def default_source_path(name)
40
+ "#{Lono.config.templates_path}/#{name}.yml" # defaults to name, source method overrides
114
41
  end
115
42
 
116
- def transform_array(arr)
117
- arr.map! {|x| x =~ /=>/ ? x : x.inspect }
118
- arr.join(',')
119
- end
43
+ def build
44
+ instance_eval(&@block) if @block
120
45
 
121
- # transform each line of bash script to array with cloudformation template objects
122
- def transform(data)
123
- data = evaluate(data)
124
- if data[-1].is_a?(String)
125
- data[0..-2] + ["#{data[-1]}\n"]
46
+ if File.exist?(@source_path)
47
+ RenderMePretty.result(@source_path, context: context)
126
48
  else
127
- data + ["\n"]
49
+ puts "ERROR: #{@source_path} does not exist, but it was used as a template source.".colorize(:red)
50
+ exit 1
128
51
  end
129
52
  end
130
53
 
131
- # Input:
132
- # String
133
- # Output:
134
- # Array of parse positions
135
- #
136
- # The positions of tokens taking into account when brackets start and close,
137
- # handles nested brackets.
138
- def bracket_positions(line)
139
- positions,pair,count = [],[],0
140
-
141
- line.split('').each_with_index do |char,i|
142
- pair << i if pair.empty?
143
-
144
- first_pair_char = line[pair[0]]
145
- if first_pair_char == '{' # object logic
146
- if char == '{'
147
- count += 1
148
- end
149
-
150
- if char == '}'
151
- count -= 1
152
- if count == 0
153
- pair << i
154
- positions << pair
155
- pair = []
156
- end
157
- end
158
- else # string logic
159
- lookahead = line[i+1]
160
- if lookahead == '{'
161
- pair << i
162
- positions << pair
163
- pair = []
164
- end
165
- end
166
- end # end of loop
167
-
168
- # for string logic when lookahead does not contain a object token
169
- # need to clear out what's left to match the final pair
170
- if !pair.empty?
171
- pair << line.size - 1
172
- positions << pair
173
- end
174
-
175
- positions
176
- end
177
-
178
- # Input:
179
- # Array - bracket_positions
180
- # Ouput:
181
- # Array - positions that can be use to determine what to parse
182
- def parse_positions(line)
183
- positions = bracket_positions(line)
184
- positions.flatten
185
- end
186
-
187
- # Input
188
- # String line of code to decompose into chunks, some can be transformed into objects
189
- # Output
190
- # Array of strings, some can be transformed into objects
191
- #
192
- # Example:
193
- # line = 'a{b}c{d{d}d}e' # nested brackets
194
- # template.decompose(line).should == ['a','{b}','c','{d{d}d}','e']
195
- def decompose(line)
196
- positions = parse_positions(line)
197
- return [line] if positions.empty?
198
-
199
- result = []
200
- str = ''
201
- until positions.empty?
202
- left = positions.shift
203
- right = positions.shift
204
- token = line[left..right]
205
- # if cfn object, add to the result set but after clearing out
206
- # the temp str that is being built up when the token is just a string
207
- if cfn_object?(token)
208
- unless str.empty? # first token might be a object
209
- result << str
210
- str = ''
211
- end
212
- result << token
213
- else
214
- str << token # keeps building up the string
215
- end
216
- end
217
-
218
- # at the of the loop there's a leftover string, unless the last token
219
- # is an object
220
- result << str unless str.empty?
221
-
222
- result
223
- end
224
-
225
- def cfn_object?(s)
226
- exact = %w[Ref]
227
- pattern = %w[Fn::]
228
- exact_match = !!exact.detect {|word| s.include?(word)}
229
- pattern_match = !!pattern.detect {|p| s =~ Regexp.new(p)}
230
- (exact_match || pattern_match) && s =~ /^{/ && s =~ /=>/
231
- end
232
-
233
- def recompose(decomposition)
234
- decomposition.map { |s| cfn_object?(s) ? eval(s) : s }
235
- end
236
-
237
- def evaluate(line)
238
- recompose(decompose(line))
239
- end
240
-
241
- # For simple just parameters files that can also be generated with lono, the CFN
242
- # Fn::Base64 function is not available and as lono is not being used in the context
243
- # of CloudFormation. So this can be used in it's place.
244
- def encode_base64(text)
245
- Base64.strict_encode64(text).strip
246
- end
247
-
248
- def name
249
- @_name
54
+ # Context for ERB rendering.
55
+ # This is where we control what references get passed to the ERB rendering.
56
+ def context
57
+ @context ||= Lono::Template::Context.new(@options)
250
58
  end
251
59
  end