lono 3.5.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +15 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +15 -1
- data/Gemfile +3 -3
- data/Guardfile +17 -8
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.md +20 -12
- data/Rakefile +1 -2
- data/{bin → exe}/lono +1 -0
- data/lib/lono.rb +12 -9
- data/lib/lono/cfn.rb +7 -9
- data/lib/lono/cfn/{aws_services.rb → aws_service.rb} +1 -1
- data/lib/lono/cfn/base.rb +41 -38
- data/lib/lono/cfn/create.rb +6 -2
- data/lib/lono/cfn/delete.rb +2 -2
- data/lib/lono/cfn/diff.rb +1 -1
- data/lib/lono/cfn/preview.rb +26 -15
- data/lib/lono/cfn/update.rb +11 -9
- data/lib/lono/cfn/util.rb +3 -3
- data/lib/lono/clean.rb +1 -1
- data/lib/lono/cli.rb +71 -39
- data/lib/lono/command.rb +42 -18
- data/lib/lono/completer.rb +162 -0
- data/lib/lono/completer/script.rb +6 -0
- data/lib/lono/completer/script.sh +10 -0
- data/lib/lono/completion.rb +15 -0
- data/lib/lono/core.rb +23 -9
- data/lib/lono/core/config.rb +20 -0
- data/lib/lono/default/settings.yml +33 -13
- data/lib/lono/help.rb +6 -79
- data/lib/lono/help/cfn.md +6 -0
- data/lib/lono/help/cfn/create.md +22 -0
- data/lib/lono/help/cfn/delete.md +5 -0
- data/lib/lono/help/cfn/diff.md +5 -0
- data/lib/lono/help/cfn/download.md +5 -0
- data/lib/lono/help/cfn/preview.md +11 -0
- data/lib/lono/help/cfn/update.md +21 -0
- data/lib/lono/help/completion.md +22 -0
- data/lib/lono/help/completion_script.md +3 -0
- data/lib/lono/help/generate.md +7 -0
- data/lib/lono/help/hello.md +5 -0
- data/lib/lono/help/import.md +7 -0
- data/lib/lono/help/inspect.md +4 -0
- data/lib/lono/help/inspect/depends.md +3 -0
- data/lib/lono/help/inspect/summary.md +3 -0
- data/lib/lono/help/new.md +8 -0
- data/lib/lono/help/param.md +3 -0
- data/lib/lono/{param/help.rb → help/param/generate.md} +1 -9
- data/lib/lono/help/script/build.md +5 -0
- data/lib/lono/help/script/upload.md +8 -0
- data/lib/lono/help/template.md +4 -0
- data/lib/lono/help/template/bashify.md +4 -0
- data/lib/lono/help/template/generate.md +7 -0
- data/lib/lono/help/user_data.md +3 -0
- data/lib/lono/importer.rb +43 -20
- data/lib/lono/inspector.rb +2 -19
- data/lib/lono/inspector/base.rb +2 -2
- data/lib/lono/inspector/{depends.rb → graph.rb} +3 -3
- data/lib/lono/inspector/summary.rb +1 -1
- data/lib/lono/new.rb +79 -26
- data/lib/lono/new/helper.rb +16 -0
- data/lib/lono/new/message.rb +35 -0
- data/lib/lono/param.rb +1 -2
- data/lib/lono/param/generator.rb +34 -86
- data/lib/lono/project_checker.rb +35 -40
- data/lib/lono/script.rb +19 -0
- data/lib/lono/script/base.rb +9 -0
- data/lib/lono/script/build.rb +73 -0
- data/lib/lono/script/upload.rb +81 -0
- data/lib/lono/sequence.rb +33 -0
- data/lib/lono/setting.rb +83 -0
- data/lib/lono/template.rb +8 -9
- data/lib/lono/template/{aws_services.rb → aws_service.rb} +1 -1
- data/lib/lono/template/context.rb +73 -0
- data/lib/lono/template/dsl.rb +63 -64
- data/lib/lono/template/helper.rb +201 -0
- data/lib/lono/template/template.rb +29 -221
- data/lib/lono/template/upload.rb +41 -33
- data/lib/lono/upgrade4.rb +175 -0
- data/lib/lono/user_data.rb +31 -0
- data/lib/lono/version.rb +1 -1
- data/lib/starter_projects/autoscaling/.gitignore +1 -0
- data/lib/starter_projects/{json_project → autoscaling}/Gemfile +0 -0
- data/lib/starter_projects/{yaml_project → autoscaling}/Guardfile +0 -0
- data/lib/starter_projects/autoscaling/README.md +118 -0
- data/lib/starter_projects/autoscaling/app/definitions/base.rb +2 -0
- data/lib/starter_projects/autoscaling/app/templates/autoscaling.yml +682 -0
- data/lib/starter_projects/autoscaling/config/params/base/autoscaling.txt +6 -0
- data/lib/starter_projects/autoscaling/config/settings.yml +33 -0
- data/lib/starter_projects/ec2/.gitignore +1 -0
- data/lib/starter_projects/{yaml_project → ec2}/Gemfile +0 -0
- data/lib/starter_projects/{json_project → ec2}/Guardfile +1 -1
- data/lib/starter_projects/ec2/README.md +86 -0
- data/lib/starter_projects/ec2/app/definitions/base.rb +2 -0
- data/lib/starter_projects/ec2/app/definitions/development.rb +1 -0
- data/lib/starter_projects/ec2/app/definitions/production.rb +1 -0
- data/lib/starter_projects/{yaml_project → ec2/app}/helpers/my_custom_helper.rb +0 -0
- data/lib/starter_projects/{json_project/templates/user_data/app.sh → ec2/app/partials/user_data/bootstrap.sh} +1 -2
- data/lib/starter_projects/{yaml_project → ec2/app}/templates/example.yml +0 -0
- data/lib/starter_projects/{json_project/params/base/api-web.txt → ec2/config/params/base/example.txt} +0 -0
- data/lib/starter_projects/ec2/config/params/development/example.txt +3 -0
- data/lib/starter_projects/ec2/config/params/production/example.txt +2 -0
- data/lib/starter_projects/ec2/config/settings.yml +33 -0
- data/lib/starter_projects/ec2/config/variables/base.rb +3 -0
- data/lib/starter_projects/ec2/config/variables/development.rb +2 -0
- data/lib/starter_projects/ec2/config/variables/production.rb +2 -0
- data/lib/starter_projects/ec2/welcome.txt +8 -0
- data/lib/starter_projects/skeleton/.gitignore +1 -0
- data/lib/starter_projects/skeleton/Gemfile +3 -0
- data/lib/starter_projects/skeleton/Guardfile +12 -0
- data/lib/starter_projects/skeleton/README.md +53 -0
- data/{spec/fixtures/my_project/templates/.gitkeep → lib/starter_projects/skeleton/app/definitions/base.rb} +0 -0
- data/lib/starter_projects/skeleton/config/settings.yml +33 -0
- data/lib/starter_projects/skeleton/welcome.txt +7 -0
- data/lono.gemspec +12 -10
- data/spec/fixtures/lono_project/.gitignore +1 -0
- data/spec/fixtures/lono_project/Gemfile +3 -0
- data/spec/fixtures/lono_project/Guardfile +12 -0
- data/spec/fixtures/lono_project/app/definitions/base.rb +10 -0
- data/spec/fixtures/lono_project/app/definitions/base/more.rb +7 -0
- data/spec/fixtures/lono_project/app/definitions/development.rb +1 -0
- data/spec/fixtures/lono_project/app/definitions/production.rb +1 -0
- data/spec/fixtures/lono_project/app/helpers/custom_helper.rb +5 -0
- data/spec/fixtures/lono_project/app/partials/security_group.yml +10 -0
- data/{lib/starter_projects/yaml_project/templates/partial → spec/fixtures/lono_project/app/partials}/user_data/bootstrap.sh +8 -2
- data/spec/fixtures/lono_project/app/templates/example.yml +50 -0
- data/{lib/starter_projects/yaml_project/params/base/api-web-prod.txt → spec/fixtures/lono_project/config/params/base/example.txt} +1 -0
- data/spec/fixtures/lono_project/config/params/development/example.txt +1 -0
- data/spec/fixtures/lono_project/config/params/production/example.txt +1 -0
- data/spec/fixtures/lono_project/config/settings.yml +31 -0
- data/spec/fixtures/lono_project/config/variables/base.rb +3 -0
- data/spec/fixtures/lono_project/config/variables/development.rb +1 -0
- data/spec/fixtures/lono_project/config/variables/production.rb +1 -0
- data/spec/fixtures/params/envonly/params/{prod → development}/network.txt +0 -0
- data/spec/fixtures/params/overlay/params/{prod → development}/network.txt +0 -0
- data/spec/fixtures/raw_templates/aws-waf-security-automations.template +2 -2
- data/spec/lib/lono/cfn_spec.rb +6 -9
- data/spec/lib/lono/cli_spec.rb +44 -0
- data/spec/lib/lono/completion_spec.rb +17 -0
- data/spec/lib/lono/inspect_spec.rb +6 -15
- data/spec/lib/lono/param/generator_spec.rb +45 -26
- data/spec/lib/lono/param_spec.rb +1 -3
- data/spec/lib/lono/setting_spec.rb +47 -0
- data/spec/lib/lono/template/dsl_spec.rb +33 -157
- data/spec/lib/lono/template_spec.rb +4 -16
- data/spec/spec_helper.rb +45 -14
- metadata +168 -82
- data/.coveralls.yml +0 -1
- data/lib/lono/cfn/help.rb +0 -103
- data/lib/lono/current_region.rb +0 -42
- data/lib/lono/inspector/help.rb +0 -21
- data/lib/lono/settings.rb +0 -45
- data/lib/lono/template/help.rb +0 -25
- data/lib/lono/template/helpers.rb +0 -136
- data/lib/starter_projects/json_project/.gitignore +0 -1
- data/lib/starter_projects/json_project/config/templates/base/blog.rb +0 -20
- data/lib/starter_projects/json_project/config/templates/base/stacks.rb +0 -58
- data/lib/starter_projects/json_project/templates/db.json +0 -212
- data/lib/starter_projects/json_project/templates/partial/host_record.json +0 -28
- data/lib/starter_projects/json_project/templates/partial/server.json +0 -45
- data/lib/starter_projects/json_project/templates/user_data/db.sh +0 -39
- data/lib/starter_projects/json_project/templates/user_data/db2.sh +0 -2
- data/lib/starter_projects/json_project/templates/user_data/ruby_script.rb +0 -5
- data/lib/starter_projects/json_project/templates/web.json +0 -386
- data/lib/starter_projects/yaml_project/.gitignore +0 -1
- data/lib/starter_projects/yaml_project/config/templates/base/blog.rb +0 -20
- data/lib/starter_projects/yaml_project/config/templates/base/stacks.rb +0 -56
- data/lib/starter_projects/yaml_project/config/templates/prod/stacks.rb +0 -1
- data/lib/starter_projects/yaml_project/config/templates/stag/stacks.rb +0 -1
- data/lib/starter_projects/yaml_project/config/variables/base/variables.rb +0 -4
- data/lib/starter_projects/yaml_project/config/variables/prod/variables.rb +0 -1
- data/lib/starter_projects/yaml_project/config/variables/stag/variables.rb +0 -1
- data/lib/starter_projects/yaml_project/params/base/example.txt +0 -2
- data/lib/starter_projects/yaml_project/params/prod/example.txt +0 -1
- data/lib/starter_projects/yaml_project/params/stag/example.txt +0 -1
- data/lib/starter_projects/yaml_project/templates/db.yml +0 -148
- data/lib/starter_projects/yaml_project/templates/partial/host_record.yml +0 -14
- data/lib/starter_projects/yaml_project/templates/partial/server.yml +0 -59
- data/lib/starter_projects/yaml_project/templates/web.yml +0 -206
- data/spec/fixtures/my_project/config/templates/base/stacks.rb +0 -3
- data/spec/fixtures/my_project/params/my-stack.txt +0 -3
- data/spec/fixtures/my_project/templates/my-stack.yml +0 -0
- data/spec/lib/lono/new_spec.rb +0 -59
- data/spec/lib/lono/template/template_spec.rb +0 -104
- data/spec/lib/lono_spec.rb +0 -27
data/lib/lono/template/dsl.rb
CHANGED
|
@@ -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
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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.
|
|
90
|
+
output_path = "#{Lono.config.output_path}/templates"
|
|
91
91
|
FileUtils.rm_rf(output_path) if @options[:clean]
|
|
92
|
-
FileUtils.
|
|
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 += "
|
|
96
|
+
path += ".yml"
|
|
97
97
|
puts " #{path}" unless @options[:quiet]
|
|
98
98
|
ensure_parent_dir(path)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
105
|
+
def validate(path)
|
|
106
|
+
text = IO.read(path)
|
|
115
107
|
begin
|
|
116
|
-
YAML.load(
|
|
108
|
+
YAML.load(text)
|
|
117
109
|
rescue Psych::SyntaxError => e
|
|
118
|
-
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
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
|
-
@
|
|
15
|
-
@
|
|
16
|
-
@
|
|
17
|
-
@
|
|
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
|
-
@
|
|
75
|
-
@
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
arr.join(',')
|
|
119
|
-
end
|
|
43
|
+
def build
|
|
44
|
+
instance_eval(&@block) if @block
|
|
120
45
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
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
|