lono 1.1.3 → 2.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +150 -39
  5. data/bin/lono +2 -2
  6. data/circle.yml +4 -0
  7. data/lib/lono.rb +16 -7
  8. data/lib/lono/cfn.rb +64 -0
  9. data/lib/lono/cfn/aws_services.rb +37 -0
  10. data/lib/lono/cfn/base.rb +144 -0
  11. data/lib/lono/cfn/create.rb +34 -0
  12. data/lib/lono/cfn/delete.rb +26 -0
  13. data/lib/lono/cfn/diff.rb +43 -0
  14. data/lib/lono/cfn/help.rb +93 -0
  15. data/lib/lono/cfn/preview.rb +133 -0
  16. data/lib/lono/cfn/update.rb +62 -0
  17. data/lib/lono/cfn/util.rb +21 -0
  18. data/lib/lono/cli.rb +19 -10
  19. data/lib/lono/command.rb +25 -0
  20. data/lib/lono/help.rb +59 -0
  21. data/lib/lono/new.rb +3 -2
  22. data/lib/lono/param.rb +20 -0
  23. data/lib/lono/param/generator.rb +90 -0
  24. data/lib/lono/param/help.rb +15 -0
  25. data/lib/lono/project_checker.rb +44 -0
  26. data/lib/lono/template.rb +22 -248
  27. data/lib/lono/template/bashify.rb +39 -0
  28. data/lib/lono/template/dsl.rb +139 -0
  29. data/lib/lono/template/help.rb +25 -0
  30. data/lib/lono/template/template.rb +251 -0
  31. data/lib/lono/version.rb +1 -1
  32. data/lib/{starter_project_yaml → starter_projects/json_project}/Gemfile +0 -1
  33. data/lib/{starter_project_json → starter_projects/json_project}/Guardfile +0 -0
  34. data/lib/{starter_project_json → starter_projects/json_project}/config/lono.rb +0 -0
  35. data/lib/{starter_project_json → starter_projects/json_project}/config/lono/api.rb +0 -0
  36. data/lib/starter_projects/json_project/params/api-web-prod.txt +20 -0
  37. data/lib/{starter_project_json → starter_projects/json_project}/templates/db.json.erb +0 -0
  38. data/lib/{starter_project_json → starter_projects/json_project}/templates/partial/host_record.json.erb +0 -0
  39. data/lib/{starter_project_json → starter_projects/json_project}/templates/partial/server.json.erb +0 -0
  40. data/lib/{starter_project_json → starter_projects/json_project}/templates/user_data/app.sh.erb +0 -0
  41. data/lib/{starter_project_json → starter_projects/json_project}/templates/user_data/db.sh.erb +0 -0
  42. data/lib/{starter_project_json → starter_projects/json_project}/templates/user_data/db2.sh.erb +0 -0
  43. data/lib/{starter_project_json → starter_projects/json_project}/templates/user_data/ruby_script.rb.erb +0 -0
  44. data/lib/{starter_project_json → starter_projects/json_project}/templates/web.json.erb +0 -0
  45. data/lib/{starter_project_json → starter_projects/yaml_project}/Gemfile +0 -1
  46. data/lib/{starter_project_yaml → starter_projects/yaml_project}/Guardfile +0 -0
  47. data/lib/{starter_project_yaml → starter_projects/yaml_project}/config/lono.rb +0 -0
  48. data/lib/{starter_project_yaml → starter_projects/yaml_project}/config/lono/api.rb +0 -0
  49. data/lib/starter_projects/yaml_project/params/api-web-prod.txt +20 -0
  50. data/lib/{starter_project_yaml → starter_projects/yaml_project}/templates/db.yml.erb +0 -0
  51. data/lib/{starter_project_yaml → starter_projects/yaml_project}/templates/partial/host_record.yml.erb +0 -0
  52. data/lib/{starter_project_yaml → starter_projects/yaml_project}/templates/partial/server.yml.erb +0 -0
  53. data/lib/{starter_project_yaml → starter_projects/yaml_project}/templates/partial/user_data/bootstrap.sh.erb +0 -0
  54. data/lib/{starter_project_yaml → starter_projects/yaml_project}/templates/web.yml.erb +0 -0
  55. data/lono.gemspec +15 -10
  56. data/spec/fixtures/my_project/config/lono.rb +1 -0
  57. data/spec/fixtures/my_project/params/my-stack.txt +3 -0
  58. data/spec/fixtures/my_project/templates/.gitkeep +0 -0
  59. data/spec/fixtures/my_project/templates/my-stack.yml.erb +0 -0
  60. data/spec/lib/lono/cfn_spec.rb +35 -0
  61. data/spec/lib/lono/new_spec.rb +3 -3
  62. data/spec/lib/lono/param_spec.rb +15 -0
  63. data/spec/lib/lono/{dsl_spec.rb → template/dsl_spec.rb} +9 -9
  64. data/spec/lib/lono/template/template_spec.rb +104 -0
  65. data/spec/lib/lono/template_spec.rb +22 -37
  66. data/spec/lib/lono_spec.rb +6 -83
  67. data/vendor/plissken/Gemfile +14 -0
  68. data/vendor/plissken/LICENSE.txt +20 -0
  69. data/vendor/plissken/README.md +46 -0
  70. data/vendor/plissken/Rakefile +56 -0
  71. data/vendor/plissken/VERSION +1 -0
  72. data/vendor/plissken/lib/plissken.rb +1 -0
  73. data/vendor/plissken/lib/plissken/ext/hash/to_snake_keys.rb +45 -0
  74. data/vendor/plissken/plissken.gemspec +61 -0
  75. data/vendor/plissken/spec/lib/to_snake_keys_spec.rb +177 -0
  76. data/vendor/plissken/spec/spec_helper.rb +90 -0
  77. data/vendor/plissken/test/helper.rb +20 -0
  78. data/vendor/plissken/test/plissken/ext/hash/to_snake_keys_test.rb +184 -0
  79. data/vendor/plissken/test/test_plissken.rb +2 -0
  80. metadata +115 -39
  81. data/lib/lono/bashify.rb +0 -41
  82. data/lib/lono/cli/help.rb +0 -37
  83. data/lib/lono/dsl.rb +0 -132
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+
3
+ module Lono
4
+ class Command < Thor
5
+ class << self
6
+ def dispatch(m, args, options, config)
7
+ # Allow calling for help via:
8
+ # lono generate help
9
+ # lono generate -h
10
+ # lono generate --help
11
+ # lono generate -D
12
+ #
13
+ # as well thor's nomral setting as
14
+ #
15
+ # lono help generate
16
+ help_flags = Thor::HELP_MAPPINGS + ["help"]
17
+ if args.length > 1 && !(args & help_flags).empty?
18
+ args -= help_flags
19
+ args.insert(-2, "help")
20
+ end
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ module Lono::Help
2
+ def new_long_desc
3
+ <<-EOL
4
+ Examples:
5
+
6
+ $ lono new project
7
+
8
+ $ lono new lono
9
+ EOL
10
+ end
11
+
12
+ def generate
13
+ <<-EOL
14
+ Examples:
15
+
16
+ $ lono generate
17
+
18
+ $ lono g -c # shortcut
19
+
20
+ Builds both CloudFormation template and parameter files based on lono project and writes them to the output folder on the filesystem.
21
+ EOL
22
+
23
+ end
24
+
25
+ def template
26
+ <<-EOL
27
+ Examples:
28
+
29
+ $ lono template generate --help
30
+
31
+ $ lono template bashify --help
32
+ EOL
33
+
34
+ end
35
+
36
+ def cfn
37
+ <<-EOL
38
+ Examples:
39
+
40
+ $ lono cfn create my-stack
41
+
42
+ $ lono cfn preview my-stack
43
+
44
+ $ lono cfn update my-stack
45
+
46
+ $ lono cfn delete my-stack
47
+ EOL
48
+ end
49
+
50
+ def param
51
+ <<-EOL
52
+ Examples:
53
+
54
+ $ lono param generate
55
+ EOL
56
+ end
57
+
58
+ extend self
59
+ end
@@ -9,11 +9,12 @@ module Lono
9
9
 
10
10
  def run
11
11
  puts "Setting up lono project" unless options[:quiet]
12
- source_root = File.expand_path("../../starter_project_#{@format}", __FILE__)
12
+ source_root = File.expand_path("../../starter_projects/#{@format}_project", __FILE__)
13
13
  paths = Dir.glob("#{source_root}/**/*").
14
14
  select {|p| File.file?(p) }
15
15
  paths.each do |src|
16
- regexp = Regexp.new(".*starter_project_#{@format}/")
16
+ # starter_projects/yaml_project/ ->
17
+ regexp = Regexp.new(".*starter_projects/#{@format}_project/")
17
18
  dest = src.gsub(regexp,'')
18
19
  dest = "#{@project_root}/#{dest}"
19
20
 
@@ -0,0 +1,20 @@
1
+ require "thor"
2
+
3
+ module Lono
4
+ class Param < Command
5
+ autoload :Help, 'lono/param/help'
6
+ autoload :Generator, 'lono/param/generator'
7
+
8
+ class_option :verbose, type: :boolean
9
+ class_option :noop, type: :boolean
10
+ class_option :mute, type: :boolean
11
+ class_option :project_root, desc: "project root to use", default: '.'
12
+
13
+ desc "generate NAME", "generate parameter json file for NAME"
14
+ long_desc Help.generate
15
+ option :path, desc: "Name of the source that maps to the params txt file. name -> params/NAME.txt. Use this to override the params/NAME.txt convention"
16
+ def generate
17
+ Generator.generate_all(options)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,90 @@
1
+ class Lono::Param::Generator
2
+ def self.generate_all(options)
3
+ puts "Generating params files"
4
+ project_root = options[:project_root] || '.'
5
+ Dir.glob("#{project_root}/params/**/*.txt").each do |path|
6
+ next if File.directory?(path)
7
+ name = path.sub(/.*params\//, '').sub('.txt', '')
8
+ param = Lono::Param::Generator.new(name, options)
9
+ param.generate
10
+ end
11
+ end
12
+
13
+ def initialize(name, options)
14
+ @name = name
15
+ @options = options
16
+ @project_root = options[:project_root] || '.'
17
+ @source_path = options[:path] || "#{@project_root}/params/#{@name}.txt"
18
+ end
19
+
20
+ def generate
21
+ # useful option for lono cfn
22
+ return if @options[:allow_no_file] && !File.exist?(@source_path)
23
+
24
+ if File.exist?(@source_path)
25
+ contents = IO.read(@source_path)
26
+ data = convert_to_cfn_format(contents)
27
+ json = JSON.pretty_generate(data)
28
+ write_output(json)
29
+ puts "Params file generated for #{@name} at #{output_path}" unless @options[:mute]
30
+ else
31
+ puts "#{@source_path} could not be found? Are you sure it exist?"
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ # useful for when calling CloudFormation via the aws-sdk gem
37
+ def params
38
+ # useful option for lono cfn
39
+ return {} if @options[:allow_no_file] && !File.exist?(@source_path)
40
+
41
+ contents = IO.read(@source_path)
42
+ convert_to_cfn_format(contents, :underscore)
43
+ end
44
+
45
+ def parse_contents(contents)
46
+ lines = contents.split("\n")
47
+ # remove comment at the end of the line
48
+ lines.map! { |l| l.sub(/#.*/,'').strip }
49
+ # filter out commented lines
50
+ lines = lines.reject { |l| l =~ /(^|\s)#/i }
51
+ # filter out empty lines
52
+ lines = lines.reject { |l| l.strip.empty? }
53
+ lines
54
+ end
55
+
56
+ def convert_to_cfn_format(contents, casing=:camel)
57
+ lines = parse_contents(contents)
58
+ params = []
59
+ lines.each do |line|
60
+ key,value = line.strip.split("=").map {|x| x.strip}
61
+ param = if value == "use_previous_value"
62
+ {
63
+ ParameterKey: key,
64
+ UsePreviousValue: true
65
+ }
66
+ elsif value
67
+ {
68
+ ParameterKey: key,
69
+ ParameterValue: value
70
+ }
71
+ end
72
+ if param
73
+ param = param.to_snake_keys if casing == :underscore
74
+ params << param
75
+ end
76
+ end
77
+ params
78
+ end
79
+
80
+ def output_path
81
+ "#{@project_root}/output/params/#{@name}.json".sub(/\.\//,'')
82
+ end
83
+
84
+ def write_output(json)
85
+ dir = File.dirname(output_path)
86
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
87
+ IO.write(output_path, json)
88
+ end
89
+
90
+ end
@@ -0,0 +1,15 @@
1
+ class Lono::Param::Help
2
+ class << self
3
+ def generate
4
+ <<-EOL
5
+ Example:
6
+
7
+ To generate a CloudFormation json parameter files in the params folder to the output/params folder.
8
+
9
+ $ lono-params generate
10
+
11
+ If you have params/my-stack.txt. It will generate a CloudFormation json file in output/params/my-stack.json.
12
+ EOL
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ module Lono
2
+ class ProjectChecker
3
+ # Checks to see command is running in a lono project.
4
+ # If not, provide a friendly message and exit.
5
+ def self.check(project_root)
6
+ new(project_root).check
7
+ end
8
+
9
+ def initialize(project_root)
10
+ @project_root = project_root
11
+ end
12
+
13
+ def check
14
+ config_folder_exist
15
+ templates_folder_exist
16
+ empty_folders
17
+ end
18
+
19
+ def config_folder_exist
20
+ unless File.exist?("#{@project_root}/config")
21
+ puts "The config folder does not exist in this project. Are you sure this is a lono project?"
22
+ exit 1
23
+ end
24
+ end
25
+
26
+ def templates_folder_exist
27
+ unless File.exist?("#{@project_root}/templates")
28
+ puts "The templates folder does not exist in this project. Are you sure this is a lono project?"
29
+ exit 1
30
+ end
31
+ end
32
+
33
+ def empty_folders
34
+ if Dir["#{@project_root}/config/**/*.rb"].empty?
35
+ puts "The config folder does not contain any lono template definitions."
36
+ exit 1
37
+ end
38
+ if Dir["#{@project_root}/templates/**/*"].empty?
39
+ puts "The templates folder does not contain any lono template definitions."
40
+ exit 1
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,253 +1,27 @@
1
- require 'erb'
2
- require 'json'
3
- require 'base64'
1
+ require "thor"
2
+ require_relative "command"
4
3
 
5
4
  module Lono
6
- class Template
7
- include ERB::Util
8
-
9
- attr_reader :name
10
- def initialize(name, block, options={})
11
- @name = name
12
- @block = block
13
- @options = options
14
- end
15
-
16
- def build
17
- instance_eval(&@block)
18
- template = IO.read(@source)
19
- erb_result(@source, template)
20
- end
21
-
22
- def source(path)
23
- @source = path[0..0] == '/' ? path : "#{@options[:project_root]}/templates/#{path}"
24
- end
25
-
26
- def variables(vars={})
27
- vars.each do |var,value|
28
- instance_variable_set("@#{var}", value)
29
- end
30
- end
31
-
32
- def partial(path,vars={}, options={})
33
- path = "#{@options[:project_root]}/templates/partial/#{path}"
34
- template = IO.read(path)
35
- variables(vars)
36
- result = erb_result(path, template)
37
- result = indent(result, options[:indent]) if options[:indent]
38
- result
39
- end
40
-
41
- # add indentation
42
- def indent(result, indentation_amount)
43
- result.split("\n").map do |line|
44
- " " * indentation_amount + line
45
- end.join("\n")
46
- end
47
-
48
- def erb_result(path, template)
49
- begin
50
- ERB.new(template, nil, "-").result(binding)
51
- rescue Exception => e
52
- puts e
53
-
54
- # how to know where ERB stopped? - https://www.ruby-forum.com/topic/182051
55
- # syntax errors have the (erb):xxx info in e.message
56
- # undefined variables have (erb):xxx info in e.backtrac
57
- error_info = e.message.split("\n").grep(/\(erb\)/)[0]
58
- error_info ||= e.backtrace.grep(/\(erb\)/)[0]
59
- raise unless error_info # unable to find the (erb):xxx: error line
60
- line = error_info.split(':')[1].to_i
61
- puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '')}"
62
-
63
- template_lines = template.split("\n")
64
- context = 5 # lines of context
65
- top, bottom = [line-context-1, 0].max, line+context-1
66
- spacing = template_lines.size.to_s.size
67
- template_lines[top..bottom].each_with_index do |line_content, index|
68
- line_number = top+index+1
69
- if line_number == line
70
- printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
71
- else
72
- printf("%#{spacing}d %s\n", line_number, line_content)
73
- end
74
- end
75
- exit 1 unless ENV['TEST']
76
- end
77
- end
78
-
79
- def user_data(path, vars={})
80
- path = "#{@options[:project_root]}/templates/user_data/#{path}"
81
- template = IO.read(path)
82
- variables(vars)
83
- result = erb_result(path, template)
84
- output = []
85
- result.split("\n").each do |line|
86
- output += transform(line)
87
- end
88
- json = output.to_json
89
- json[0] = '' # remove first char: [
90
- json.chop! # remove last char: ]
91
- end
92
-
93
- def ref(name)
94
- %Q|{"Ref"=>"#{name}"}|
95
- end
96
-
97
- def find_in_map(*args)
98
- %Q|{"Fn::FindInMap" => [ #{transform_array(args)} ]}|
99
- end
100
-
101
- def base64(value)
102
- %Q|{"Fn::Base64"=>"#{value}"}|
103
- end
104
-
105
- def get_att(*args)
106
- %Q|{"Fn::GetAtt" => [ #{transform_array(args)} ]}|
107
- end
108
-
109
- def get_azs(region="AWS::Region")
110
- %Q|{"Fn::GetAZs"=>"#{region}"}|
111
- end
112
-
113
- def join(delimiter, values)
114
- %Q|{"Fn::Join" => ["#{delimiter}", [ #{transform_array(values)} ]]}|
115
- end
116
-
117
- def select(index, list)
118
- %Q|{"Fn::Select" => ["#{index}", [ #{transform_array(list)} ]]}|
119
- end
120
-
121
- def transform_array(arr)
122
- arr.map! {|x| x =~ /=>/ ? x : x.inspect }
123
- arr.join(',')
124
- end
125
-
126
- # transform each line of bash script to array with cloudformation template objects
127
- def transform(data)
128
- data = evaluate(data)
129
- if data[-1].is_a?(String)
130
- data[0..-2] + ["#{data[-1]}\n"]
131
- else
132
- data + ["\n"]
133
- end
134
- end
135
-
136
- # Input:
137
- # String
138
- # Output:
139
- # Array of parse positions
140
- #
141
- # The positions of tokens taking into account when brackets start and close,
142
- # handles nested brackets.
143
- def bracket_positions(line)
144
- positions,pair,count = [],[],0
145
-
146
- line.split('').each_with_index do |char,i|
147
- pair << i if pair.empty?
148
-
149
- first_pair_char = line[pair[0]]
150
- if first_pair_char == '{' # object logic
151
- if char == '{'
152
- count += 1
153
- end
154
-
155
- if char == '}'
156
- count -= 1
157
- if count == 0
158
- pair << i
159
- positions << pair
160
- pair = []
161
- end
162
- end
163
- else # string logic
164
- lookahead = line[i+1]
165
- if lookahead == '{'
166
- pair << i
167
- positions << pair
168
- pair = []
169
- end
170
- end
171
- end # end of loop
172
-
173
- # for string logic when lookahead does not contain a object token
174
- # need to clear out what's left to match the final pair
175
- if !pair.empty?
176
- pair << line.size - 1
177
- positions << pair
178
- end
179
-
180
- positions
181
- end
182
-
183
- # Input:
184
- # Array - bracket_positions
185
- # Ouput:
186
- # Array - positions that can be use to determine what to parse
187
- def parse_positions(line)
188
- positions = bracket_positions(line)
189
- positions.flatten
190
- end
191
-
192
- # Input
193
- # String line of code to decompose into chunks, some can be transformed into objects
194
- # Output
195
- # Array of strings, some can be transformed into objects
196
- #
197
- # Example:
198
- # line = 'a{b}c{d{d}d}e' # nested brackets
199
- # template.decompose(line).should == ['a','{b}','c','{d{d}d}','e']
200
- def decompose(line)
201
- positions = parse_positions(line)
202
- return [line] if positions.empty?
203
-
204
- result = []
205
- str = ''
206
- until positions.empty?
207
- left = positions.shift
208
- right = positions.shift
209
- token = line[left..right]
210
- # if cfn object, add to the result set but after clearing out
211
- # the temp str that is being built up when the token is just a string
212
- if cfn_object?(token)
213
- unless str.empty? # first token might be a object
214
- result << str
215
- str = ''
216
- end
217
- result << token
218
- else
219
- str << token # keeps building up the string
220
- end
221
- end
222
-
223
- # at the of the loop there's a leftover string, unless the last token
224
- # is an object
225
- result << str unless str.empty?
226
-
227
- result
228
- end
229
-
230
- def cfn_object?(s)
231
- exact = %w[Ref]
232
- pattern = %w[Fn::]
233
- exact_match = !!exact.detect {|word| s.include?(word)}
234
- pattern_match = !!pattern.detect {|p| s =~ Regexp.new(p)}
235
- (exact_match || pattern_match) && s =~ /^{/ && s =~ /=>/
236
- end
237
-
238
- def recompose(decomposition)
239
- decomposition.map { |s| cfn_object?(s) ? eval(s) : s }
240
- end
241
-
242
- def evaluate(line)
243
- recompose(decompose(line))
244
- end
245
-
246
- # For simple just parameters files that can also be generated with lono, the CFN
247
- # Fn::Base64 function is not available and as lono is not being used in the context
248
- # of CloudFormation. So this can be used in it's place.
249
- def encode_base64(text)
250
- Base64.strict_encode64(text).strip
5
+ class Template < Command
6
+ autoload :Help, 'lono/template/help'
7
+ autoload :Bashify, 'lono/template/bashify'
8
+ autoload :DSL, 'lono/template/dsl'
9
+ autoload :Template, 'lono/template/template'
10
+
11
+ desc "generate", "Generate the CloudFormation templates"
12
+ Help.generate
13
+ option :clean, type: :boolean, aliases: "-c", desc: "remove all output files before generating"
14
+ option :project_root, default: ".", aliases: "-r", desc: "project root"
15
+ option :quiet, type: :boolean, aliases: "-q", desc: "silence the output"
16
+ option :pretty, type: :boolean, default: true, desc: "json pretty the output. only applies with json format"
17
+ def generate
18
+ DSL.new(options.clone).run
19
+ end
20
+
21
+ desc "bashify [URL-OR-PATH]", "Convert the UserData section of an existing CloudFormation Template to a starter bash script that is compatiable with lono"
22
+ Help.bashify
23
+ def bashify(path)
24
+ Bashify.new(path: path).run
251
25
  end
252
26
  end
253
27
  end