lono 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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"