lono 2.1.0 → 3.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +16 -284
  4. data/lib/lono.rb +6 -0
  5. data/lib/lono/cfn.rb +5 -0
  6. data/lib/lono/cfn/base.rb +86 -9
  7. data/lib/lono/cfn/create.rb +27 -2
  8. data/lib/lono/cfn/help.rb +12 -12
  9. data/lib/lono/cfn/preview.rb +12 -11
  10. data/lib/lono/cfn/update.rb +3 -2
  11. data/lib/lono/default/settings.yml +13 -0
  12. data/lib/lono/env.rb +11 -0
  13. data/lib/lono/param.rb +1 -1
  14. data/lib/lono/param/generator.rb +144 -22
  15. data/lib/lono/settings.rb +28 -0
  16. data/lib/lono/template.rb +13 -2
  17. data/lib/lono/template/aws_services.rb +7 -0
  18. data/lib/lono/template/dsl.rb +24 -33
  19. data/lib/lono/template/helpers.rb +144 -0
  20. data/lib/lono/template/template.rb +69 -68
  21. data/lib/lono/template/upload.rb +75 -0
  22. data/lib/lono/version.rb +1 -1
  23. data/lib/starter_projects/json_project/config/{lono.rb → templates/base/blog.rb} +3 -3
  24. data/lib/starter_projects/{yaml_project/config/lono/api.rb → json_project/config/templates/base/stacks.rb} +9 -9
  25. data/lib/starter_projects/json_project/templates/{db.json.erb → db.json} +1 -1
  26. data/lib/starter_projects/json_project/templates/partial/{host_record.json.erb → host_record.json} +0 -0
  27. data/lib/starter_projects/json_project/templates/partial/{server.json.erb → server.json} +2 -2
  28. data/lib/starter_projects/json_project/templates/user_data/{app.sh.erb → app.sh} +0 -0
  29. data/lib/starter_projects/json_project/templates/user_data/{db.sh.erb → db.sh} +0 -0
  30. data/lib/starter_projects/json_project/templates/user_data/{db2.sh.erb → db2.sh} +0 -0
  31. data/lib/starter_projects/json_project/templates/user_data/{ruby_script.rb.erb → ruby_script.rb} +0 -0
  32. data/lib/starter_projects/json_project/templates/{web.json.erb → web.json} +2 -2
  33. data/lib/starter_projects/yaml_project/config/{lono.rb → templates/base/blog.rb} +4 -8
  34. data/lib/starter_projects/{json_project/config/lono/api.rb → yaml_project/config/templates/base/stacks.rb} +11 -13
  35. data/lib/starter_projects/yaml_project/config/templates/prod/stacks.rb +1 -0
  36. data/lib/starter_projects/yaml_project/config/templates/stag/stacks.rb +1 -0
  37. data/lib/starter_projects/yaml_project/config/variables/base/variables.rb +4 -0
  38. data/lib/starter_projects/yaml_project/config/variables/prod/variables.rb +1 -0
  39. data/lib/starter_projects/yaml_project/config/variables/stag/variables.rb +1 -0
  40. data/lib/starter_projects/yaml_project/helpers/my_custom_helper.rb +17 -0
  41. data/lib/starter_projects/yaml_project/params/{api-web-prod.txt → base/api-web-prod.txt} +0 -0
  42. data/lib/starter_projects/yaml_project/params/{example.txt → base/example.txt} +0 -0
  43. data/lib/starter_projects/yaml_project/params/prod/example.txt +1 -0
  44. data/lib/starter_projects/yaml_project/params/stag/example.txt +1 -0
  45. data/lib/starter_projects/yaml_project/templates/{db.yml.erb → db.yml} +1 -1
  46. data/lib/starter_projects/yaml_project/templates/{example.yml.erb → example.yml} +0 -0
  47. data/lib/starter_projects/yaml_project/templates/partial/{host_record.yml.erb → host_record.yml} +0 -0
  48. data/lib/starter_projects/yaml_project/templates/partial/{server.yml.erb → server.yml} +0 -0
  49. data/lib/starter_projects/yaml_project/templates/partial/user_data/{bootstrap.sh.erb → bootstrap.sh} +0 -0
  50. data/lib/starter_projects/yaml_project/templates/{web.yml.erb → web.yml} +2 -2
  51. data/lono.gemspec +1 -0
  52. data/spec/fixtures/params/baseonly/params/base/network.txt +1 -0
  53. data/spec/fixtures/params/envonly/params/prod/network.txt +1 -0
  54. data/spec/fixtures/params/overlay/params/base/network.txt +1 -0
  55. data/spec/fixtures/params/overlay/params/prod/network.txt +1 -0
  56. data/spec/lib/lono/new_spec.rb +1 -1
  57. data/spec/lib/lono/param/generator_spec.rb +34 -0
  58. data/spec/lib/lono/template/dsl_spec.rb +1 -1
  59. data/spec/lib/lono/template_spec.rb +5 -0
  60. metadata +60 -22
@@ -26,9 +26,34 @@ class Lono::Cfn::Create < Lono::Cfn::Base
26
26
  cfn.create_stack(
27
27
  stack_name: @stack_name,
28
28
  template_body: template_body,
29
- parameters: params#,
30
- # capabilities: ["CAPABILITY_IAM"]
29
+ parameters: params,
30
+ capabilities: capabilities, # ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
31
+ disable_rollback: !@options[:rollback],
31
32
  )
32
33
  puts message unless @options[:mute]
33
34
  end
35
+
36
+ # Appends a short random string at the end of a stack name.
37
+ # Later we will strip this same random string from the template name.
38
+ # Very makes it convenient. We can just type:
39
+ #
40
+ # lono cfn create main --randomize-stack-name
41
+ #
42
+ # instead of:
43
+ #
44
+ # lono cfn create main-[RANDOM] --template main
45
+ #
46
+ # The randomize_stack_name can be specified at the CLI but can also be saved as a
47
+ # preference.
48
+ #
49
+ # It is not a default setting because it might confuse new lono users.
50
+ def randomize(stack_name)
51
+ if randomize_stack_name?
52
+ random = (0...3).map { (65 + rand(26)).chr }.join.downcase # Ex: jhx
53
+ [stack_name, random].join('-')
54
+ else
55
+ stack_name
56
+ end
57
+ end
58
+
34
59
  end
@@ -12,17 +12,17 @@ The above command will generate and use the template in output/my-stack.json and
12
12
 
13
13
  Here are examples of overriding the template and params name conventions.
14
14
 
15
- $ lono cfn create my-stack --template different-name1
15
+ $ lono cfn create my-stack --template different1
16
16
 
17
- The template that will be use is output/different-name1.json and the parameters will use params/different-name1.json.
17
+ The template used is output/different1.json and the parameters used is output/params/prod/different1.json.
18
18
 
19
- $ lono cfn create my-stack --params different-name2
19
+ $ lono cfn create my-stack --params different2
20
20
 
21
- The template that will be use is output/different-name2.json and the parameters will use params/different-name2.json.
21
+ The template used is output/my-stack.json and the parameters used is output/params/prod/different2.json.
22
22
 
23
- $ lono cfn create my-stack --template different-name3 --params different-name4
23
+ $ lono cfn create my-stack --template different3 --params different4
24
24
 
25
- The template that will be use is output/different-name3.json and the parameters will use params/different-name4.json.
25
+ The template used is output/different3.json and the parameters used is output/params/prod/different4.json.
26
26
 
27
27
  EOL
28
28
  end
@@ -39,17 +39,17 @@ The above command will generate and use the template in output/my-stack.json and
39
39
 
40
40
  Here are examples of overriding the template and params name conventions.
41
41
 
42
- $ lono cfn update my-stack --template different-name1
42
+ $ lono cfn update my-stack --template different1
43
43
 
44
- The template that will be use is output/different-name1.json and the parameters will use params/different-name1.json.
44
+ The template used is output/different1.json and the parameters used is output/params/prod/different1.json.
45
45
 
46
- $ lono cfn update my-stack --params different-name2
46
+ $ lono cfn update my-stack --params different2
47
47
 
48
- The template that will be use is output/different-name2.json and the parameters will use params/different-name2.json.
48
+ The template used is output/my-stack.json and the parameters used is output/params/prod/different2.json.
49
49
 
50
- $ lono cfn update my-stack --template different-name3 --params different-name4
50
+ $ lono cfn update my-stack --template different3 --params different4
51
51
 
52
- The template that will be use is output/different-name3.json and the parameters will use params/different-name4.json.
52
+ The template used is output/different3.json and the parameters used is output/params/prod/different4.json.
53
53
 
54
54
  EOL
55
55
  end
@@ -23,22 +23,23 @@ class Lono::Cfn::Preview < Lono::Cfn::Base
23
23
  exist_unless_updatable(stack_status(@stack_name))
24
24
 
25
25
  template_body = IO.read(@template_path)
26
- # begin
26
+ begin
27
27
  cfn.create_change_set(
28
28
  change_set_name: change_set_name,
29
29
  stack_name: @stack_name,
30
30
  template_body: template_body,
31
- parameters: params
31
+ parameters: params,
32
+ capabilities: capabilities, # ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
32
33
  )
33
- # rescue Aws::CloudFormation::Errors::ValidationError => e
34
- # if e.message =~ /^Parameters: /
35
- # puts "Error creating CloudFormation preview because invalid CloudFormation parameters. Full error message:".colorize(:red)
36
- # puts e.message
37
- # quit(1)
38
- # else
39
- # raise
40
- # end
41
- # end
34
+ rescue Aws::CloudFormation::Errors::ValidationError => e
35
+ if e.message =~ /^Parameters: /
36
+ puts "Error creating CloudFormation preview because invalid CloudFormation parameters. Full error message:".colorize(:red)
37
+ puts e.message
38
+ quit(1)
39
+ else
40
+ raise
41
+ end
42
+ end
42
43
  true
43
44
  end
44
45
 
@@ -38,8 +38,9 @@ class Lono::Cfn::Update < Lono::Cfn::Base
38
38
  cfn.update_stack(
39
39
  stack_name: @stack_name,
40
40
  template_body: template_body,
41
- parameters: params#,
42
- # capabilities: ["CAPABILITY_IAM"]
41
+ parameters: params,
42
+ capabilities: capabilities, # ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
43
+ disable_rollback: !@options[:rollback],
43
44
  )
44
45
  rescue Aws::CloudFormation::Errors::ValidationError => e
45
46
  puts "ERROR: #{e.message}".red
@@ -0,0 +1,13 @@
1
+ aws_profile_lono_env_map:
2
+ default: prod
3
+ # More examples:
4
+ # aws_profile1: prod
5
+ # aws_profile2: stag
6
+ # aws_profile3: dev
7
+ s3:
8
+ path:
9
+ default: # default is nil
10
+ # You can specify different buckets for different LONO_ENV. Examples:
11
+ # prod: s3://mybucket/templates/storage/path
12
+ # stag: s3://mybucket/templates/storage/path
13
+ randomize_stack_name: false
@@ -0,0 +1,11 @@
1
+ class Lono::Env
2
+ def self.setup!(project_root='.')
3
+ settings = Lono::Settings.new(project_root).data
4
+ map = settings['aws_profile_lono_env_map']
5
+
6
+ lono_env = map[ENV['AWS_PROFILE']] || map['default'] || 'prod' # defaults to prod
7
+ lono_env = ENV['LONO_ENV'] if ENV['LONO_ENV'] # highest precedence
8
+
9
+ Kernel.const_set(:LONO_ENV, lono_env)
10
+ end
11
+ end
@@ -9,7 +9,7 @@ class Lono::Param < Lono::Command
9
9
  class_option :mute, type: :boolean
10
10
  class_option :project_root, desc: "project root to use", default: '.'
11
11
 
12
- desc "generate NAME", "generate parameter json file for NAME"
12
+ desc "generate", "generate all parameter files to json format"
13
13
  long_desc Help.generate
14
14
  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"
15
15
  def generate
@@ -2,44 +2,134 @@ class Lono::Param::Generator
2
2
  def self.generate_all(options)
3
3
  puts "Generating params files"
4
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', '')
5
+
6
+ params = param_names(project_root, "base") + param_names(project_root, LONO_ENV)
7
+ params.uniq.each do |name|
8
8
  param = Lono::Param::Generator.new(name, options)
9
9
  param.generate
10
10
  end
11
11
  end
12
12
 
13
+ # Returns param names
14
+ # Example:
15
+ # Given params:
16
+ # params/base/a.txt params/base/b.txt params/base/c.txt
17
+ # Returns:
18
+ # param_names("base") => ["a", "b", "c"]
19
+ def self.param_names(project_root, folder)
20
+ base_folder = "#{project_root}/params/#{folder}" # Example: "./params/base"
21
+ Dir.glob("#{base_folder}/**/*.txt").map do |path|
22
+ path.sub("#{base_folder}/", '').sub('.txt','')
23
+ end
24
+ end
25
+
13
26
  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"
27
+ @_name = "#{LONO_ENV}/#{name}"
28
+ @_options = options
29
+ @_project_root = options[:project_root] || '.'
30
+ @_env_path = options[:path] || "#{@_project_root}/params/#{@_name}.txt"
31
+ @_base_path = @_env_path.sub("/#{LONO_ENV}/", "/base/")
18
32
  end
19
33
 
20
34
  def generate
21
35
  # useful option for lono cfn
22
- return if @options[:allow_no_file] && !File.exist?(@source_path)
36
+ return if @_options[:allow_no_file] && !source_exist?
23
37
 
24
- if File.exist?(@source_path)
25
- contents = IO.read(@source_path)
38
+ if source_exist?
39
+ contents = overlay_sources
26
40
  data = convert_to_cfn_format(contents)
27
41
  json = JSON.pretty_generate(data)
28
42
  write_output(json)
29
- puts "Params file generated for #{@name} at #{output_path}" unless @options[:mute]
43
+ # Example: @_name = stag/ecs/private
44
+ # pretty_name = ecs/private
45
+ pretty_name = @_name.sub("#{LONO_ENV}/", '')
46
+ puts "Params file generated for #{pretty_name} at #{output_path}" unless @_options[:mute]
30
47
  else
31
- puts "#{@source_path} could not be found? Are you sure it exist?"
48
+ puts "#{@_base_path} or #{@_env_path} could not be found? Are you sure it exist?"
32
49
  exit 1
33
50
  end
51
+ json
52
+ end
53
+
54
+ # Reads both the base source and env source and overlay the two
55
+ # Example 1:
56
+ # params/base/mystack.txt - base path
57
+ # params/prod/mystack.txt - env path
58
+ #
59
+ # the base/mystack.txt gets combined with the prod/mystack.txt
60
+ # it produces a final prod/mystack.txt
61
+ #
62
+ # Example 2:
63
+ # params/base/mystack.txt - base path
64
+ #
65
+ # the base/mystack.txt is used to produced a prod/mystack.txt
66
+ #
67
+ # Example 3:
68
+ # params/prod/mystack.txt - env path
69
+ #
70
+ # the prod/mystack.txt is used to produced a prod/mystack.txt
71
+ def overlay_sources
72
+ contents = []
73
+ contents << process_erb(@_base_path)
74
+ contents << process_erb(@_env_path)
75
+ contents.compact.join("\n")
76
+ end
77
+
78
+ def process_erb(path)
79
+ if File.exist?(path)
80
+ template = IO.read(path)
81
+ erb_result(path, template)
82
+ end
83
+ end
84
+
85
+ def erb_result(path, template)
86
+ load_variables
87
+ begin
88
+ ERB.new(template, nil, "-").result(binding)
89
+ rescue Exception => e
90
+ puts e
91
+ puts e.backtrace if ENV['DEBUG']
92
+
93
+ # how to know where ERB stopped? - https://www.ruby-forum.com/topic/182051
94
+ # syntax errors have the (erb):xxx info in e.message
95
+ # undefined variables have (erb):xxx info in e.backtrac
96
+ error_info = e.message.split("\n").grep(/\(erb\)/)[0]
97
+ error_info ||= e.backtrace.grep(/\(erb\)/)[0]
98
+ raise unless error_info # unable to find the (erb):xxx: error line
99
+ line = error_info.split(':')[1].to_i
100
+ puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '').colorize(:green)}"
101
+
102
+ template_lines = template.split("\n")
103
+ context = 5 # lines of context
104
+ top, bottom = [line-context-1, 0].max, line+context-1
105
+ spacing = template_lines.size.to_s.size
106
+ template_lines[top..bottom].each_with_index do |line_content, index|
107
+ line_number = top+index+1
108
+ if line_number == line
109
+ printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
110
+ else
111
+ printf("%#{spacing}d %s\n", line_number, line_content)
112
+ end
113
+ end
114
+ exit 1 unless ENV['TEST']
115
+ end
116
+ end
117
+
118
+ # Checks both base and source path for existing of the param file.
119
+ # Example:
120
+ # params/base/mystack.txt - base path
121
+ # params/prod/mystack.txt - source path
122
+ def source_exist?
123
+ File.exist?(@_base_path) || File.exist?(@_env_path)
34
124
  end
35
125
 
36
126
  # useful for when calling CloudFormation via the aws-sdk gem
37
- def params
127
+ def params(casing = :underscore)
38
128
  # useful option for lono cfn
39
- return {} if @options[:allow_no_file] && !File.exist?(@source_path)
129
+ return {} if @_options[:allow_no_file] && !source_exist?
40
130
 
41
- contents = IO.read(@source_path)
42
- convert_to_cfn_format(contents, :underscore)
131
+ contents = overlay_sources
132
+ convert_to_cfn_format(contents, casing)
43
133
  end
44
134
 
45
135
  def parse_contents(contents)
@@ -55,18 +145,27 @@ class Lono::Param::Generator
55
145
 
56
146
  def convert_to_cfn_format(contents, casing=:camel)
57
147
  lines = parse_contents(contents)
58
- params = []
148
+
149
+ # First use a Hash structure so that overlay env files will override
150
+ # the base param file.
151
+ data = {}
59
152
  lines.each do |line|
60
153
  key,value = line.strip.split("=").map {|x| x.strip}
154
+ data[key] = value
155
+ end
156
+
157
+ # Now build up the aws json format for parameters
158
+ params = []
159
+ data.each do |key,value|
61
160
  param = if value == "use_previous_value"
62
161
  {
63
- ParameterKey: key,
64
- UsePreviousValue: true
162
+ "ParameterKey": key,
163
+ "UsePreviousValue": true
65
164
  }
66
165
  elsif value
67
166
  {
68
- ParameterKey: key,
69
- ParameterValue: value
167
+ "ParameterKey": key,
168
+ "ParameterValue": value
70
169
  }
71
170
  end
72
171
  if param
@@ -78,7 +177,7 @@ class Lono::Param::Generator
78
177
  end
79
178
 
80
179
  def output_path
81
- "#{@project_root}/output/params/#{@name}.json".sub(/\.\//,'')
180
+ "#{@_project_root}/output/params/#{@_name}.json".sub(/\.\//,'')
82
181
  end
83
182
 
84
183
  def write_output(json)
@@ -87,4 +186,27 @@ class Lono::Param::Generator
87
186
  IO.write(output_path, json)
88
187
  end
89
188
 
189
+
190
+ def load_variables
191
+ load_variables_folder("base")
192
+ load_variables_folder(LONO_ENV)
193
+ end
194
+
195
+ # Load the variables defined in config/variables/* to make available the params/*.txt files
196
+ #
197
+ # Example:
198
+ #
199
+ # `config/variables/base/variables.rb`:
200
+ # @ami = 123
201
+ #
202
+ # `params/ecs/private.txt`:
203
+ # AmiId=<%= @ami %>
204
+ #
205
+ def load_variables_folder(folder)
206
+ paths = Dir.glob("#{@_project_root}/config/variables/#{folder}/**/*")
207
+ paths.select{ |e| File.file? e }.each do |path|
208
+ instance_eval(IO.read(path))
209
+ end
210
+ end
211
+
90
212
  end
@@ -0,0 +1,28 @@
1
+ class Lono::Settings
2
+ def initialize(project_root=nil)
3
+ @project_root = project_root || '.'
4
+ end
5
+
6
+ def data
7
+ return @settings_yaml if @settings_yaml
8
+
9
+ project_file = "#{@project_root}/.lono/settings.yml"
10
+ project = File.exist?(project_file) ? YAML.load_file(project_file) : {}
11
+
12
+ user_file = "#{ENV['HOME']}/.lono/settings.yml"
13
+ user = File.exist?(user_file) ? YAML.load_file(user_file) : {}
14
+
15
+ default_file = File.expand_path("../default/settings.yml", __FILE__)
16
+ default = YAML.load_file(default_file)
17
+
18
+ @settings_yaml = default.merge(user.merge(project))
19
+ end
20
+
21
+
22
+ def s3_path
23
+ s3 = data['s3']
24
+ # s3['default']['path'] - key will always exist because of default lono/settings.yml
25
+ # defauult value is nil though
26
+ s3['path'][LONO_ENV] || s3['path']['default']
27
+ end
28
+ end
@@ -3,18 +3,29 @@ require_relative "command"
3
3
 
4
4
  class Lono::Template < Lono::Command
5
5
  autoload :Help, 'lono/template/help'
6
+ autoload :Helpers, 'lono/template/helpers'
6
7
  autoload :Bashify, 'lono/template/bashify'
7
8
  autoload :DSL, 'lono/template/dsl'
8
9
  autoload :Template, 'lono/template/template'
10
+ autoload :Upload, 'lono/template/upload'
11
+ autoload :AwsServices, 'lono/template/aws_services'
12
+
13
+ class_option :quiet, type: :boolean, aliases: "-q", desc: "silence the output"
14
+ class_option :noop, type: :boolean, desc: "noop mode, do nothing destructive"
9
15
 
10
16
  desc "generate", "Generate the CloudFormation templates"
11
17
  Help.generate
12
18
  option :clean, type: :boolean, aliases: "-c", desc: "remove all output files before generating"
13
19
  option :project_root, default: ".", aliases: "-r", desc: "project root"
14
- option :quiet, type: :boolean, aliases: "-q", desc: "silence the output"
15
20
  option :pretty, type: :boolean, default: true, desc: "json pretty the output. only applies with json format"
16
21
  def generate
17
- DSL.new(options.clone).run
22
+ DSL.new(options.clone).run
23
+ end
24
+
25
+ desc "upload", "Uploads templates to configured s3 folder"
26
+ option :project_root, default: ".", aliases: "-r", desc: "project root"
27
+ def upload
28
+ Upload.new(options.clone).run
18
29
  end
19
30
 
20
31
  desc "bashify [URL-OR-PATH]", "Convert the UserData section of an existing CloudFormation Template to a starter bash script that is compatiable with lono"