lono 1.1.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,34 @@
1
+ class Lono::Cfn::Create < Lono::Cfn::Base
2
+ # save_stack is the interface method
3
+ def save_stack(params)
4
+ create_stack(params)
5
+ end
6
+
7
+ # aws cloudformation create-stack --stack-name prod-hi-123456789 --parameters file://output/params/prod-hi-123456789.json --template-body file://output/prod-hi.json
8
+ def create_stack(params)
9
+ message = "Creating #{@stack_name} stack."
10
+ if @options[:noop]
11
+ puts "NOOP #{message}"
12
+ return
13
+ end
14
+
15
+ if stack_exists?(@stack_name)
16
+ puts "Cannot create '#{@stack_name}' stack because it already exists."
17
+ return
18
+ end
19
+
20
+ unless File.exist?(@template_path)
21
+ puts "Cannot create '#{@stack_name}' template not found: #{@template_path}."
22
+ return
23
+ end
24
+
25
+ template_body = IO.read(@template_path)
26
+ cfn.create_stack(
27
+ stack_name: @stack_name,
28
+ template_body: template_body,
29
+ parameters: params#,
30
+ # capabilities: ["CAPABILITY_IAM"]
31
+ )
32
+ puts message unless @options[:mute]
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ class Lono::Cfn::Delete
2
+ include Lono::Cfn::AwsServices
3
+ include Lono::Cfn::Util
4
+
5
+ def initialize(stack_name, options={})
6
+ @stack_name = stack_name
7
+ @options = options
8
+ @project_root = options[:project_root] || '.'
9
+ end
10
+
11
+ def run
12
+ message = "Deleted #{@stack_name} stack."
13
+ if @options[:noop]
14
+ puts "NOOP #{message}"
15
+ else
16
+ are_you_sure?(:delete)
17
+
18
+ if stack_exists?(@stack_name)
19
+ cfn.delete_stack(stack_name: @stack_name)
20
+ puts message
21
+ else
22
+ puts "#{@stack_name.inspect} stack does not exist".colorize(:red)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ class Lono::Cfn::Diff < Lono::Cfn::Base
2
+ include Lono::Cfn::AwsServices
3
+
4
+ def run
5
+ if @options[:noop]
6
+ puts "NOOP Generating CloudFormation source code diff..."
7
+ else
8
+ generate_all # from Base superclass. Generates the output lono teplates
9
+ puts "Generating CloudFormation source code diff..."
10
+ download_existing_cfn_template
11
+ show_changes
12
+ end
13
+ end
14
+
15
+ def download_existing_cfn_template
16
+ resp = cfn.get_template(
17
+ stack_name: @stack_name,
18
+ template_stage: "Original"
19
+ )
20
+ resp.template_body
21
+ IO.write(existing_template_path, resp.template_body)
22
+ end
23
+
24
+ def show_changes
25
+ command = "#{diff_viewer} #{existing_template_path} #{new_cfn_template}"
26
+ puts "Running: #{command}"
27
+ system(command)
28
+ end
29
+
30
+ # for clarity
31
+ def new_cfn_template
32
+ @template_path
33
+ end
34
+
35
+ def diff_viewer
36
+ return ENV['LONO_CFN_DIFF'] if ENV['LONO_CFN_DIFF']
37
+ system("type colordiff > /dev/null") ? "colordiff" : "diff"
38
+ end
39
+
40
+ def existing_template_path
41
+ "/tmp/existing_cfn_template.json"
42
+ end
43
+ end
@@ -0,0 +1,93 @@
1
+ class Lono::Cfn::Help
2
+ class << self
3
+ def create
4
+ <<-EOL
5
+ Examples:
6
+
7
+ Provided that you are in a lono project and have a `my-stack` lono template definition. To create a stack you can simply run:
8
+
9
+ $ lono cfn create my-stack
10
+
11
+ The above command will generate and use the template in output/my-stack.json and parameters in params/my-stack.txt. The template by convention defaults to the name of the stack. In turn, the params by convention defaults to the name of the template.
12
+
13
+ Here are examples of overriding the template and params name conventions.
14
+
15
+ $ lono cfn create my-stack --template different-name1
16
+
17
+ The template that will be use is output/different-name1.json and the parameters will use params/different-name1.json.
18
+
19
+ $ lono cfn create my-stack --params different-name2
20
+
21
+ The template that will be use is output/different-name2.json and the parameters will use params/different-name2.json.
22
+
23
+ $ lono cfn create my-stack --template different-name3 --params different-name4
24
+
25
+ The template that will be use is output/different-name3.json and the parameters will use params/different-name4.json.
26
+
27
+ EOL
28
+ end
29
+
30
+ def update
31
+ <<-EOL
32
+ Examples:
33
+
34
+ Provided that you are in a lono project and have a `my-stack` lono template definition. To update a stack you can simply run:
35
+
36
+ $ lono cfn update my-stack
37
+
38
+ The above command will generate and use the template in output/my-stack.json and parameters in params/my-stack.txt. The template by convention defaults to the name of the stack. In turn, the params by convention defaults to the name of the template.
39
+
40
+ Here are examples of overriding the template and params name conventions.
41
+
42
+ $ lono cfn update my-stack --template different-name1
43
+
44
+ The template that will be use is output/different-name1.json and the parameters will use params/different-name1.json.
45
+
46
+ $ lono cfn update my-stack --params different-name2
47
+
48
+ The template that will be use is output/different-name2.json and the parameters will use params/different-name2.json.
49
+
50
+ $ lono cfn update my-stack --template different-name3 --params different-name4
51
+
52
+ The template that will be use is output/different-name3.json and the parameters will use params/different-name4.json.
53
+
54
+ EOL
55
+ end
56
+
57
+ def delete
58
+ <<-EOL
59
+ Examples:
60
+
61
+ $ lono cfn delete my-stack
62
+
63
+ The above command will delete my-stack.
64
+ EOL
65
+ end
66
+
67
+ def preview
68
+ <<-EOL
69
+ Generates a CloudFormation preview. This is similar to a `terraform plan` or puppet's dry-run mode.
70
+
71
+ Example output:
72
+
73
+ CloudFormation preview for 'example' stack update. Changes:
74
+
75
+ Remove AWS::Route53::RecordSet: DnsRecord testsubdomain.sub.tongueroo.com
76
+
77
+ Examples:
78
+
79
+ $ lono cfn preview my-stack
80
+ EOL
81
+ end
82
+
83
+ def diff
84
+ <<-EOL
85
+ Displays code diff of the generated CloudFormation template locally vs the existing template on AWS. You can set a desired diff viewer by setting the LONO_CFN_DIFF environment variable.
86
+
87
+ Examples:
88
+
89
+ $ lono cfn diff my-stack
90
+ EOL
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,133 @@
1
+ class Lono::Cfn::Preview < Lono::Cfn::Base
2
+ # Override run from Base superclass, the run method is different enough with Preview
3
+ def run
4
+ if @options[:noop]
5
+ puts "NOOP CloudFormation preview for #{@stack_name} update"
6
+ else
7
+ params = generate_all
8
+ preview_change_set(params)
9
+ delete_change_set unless @options[:keep] # Clean up and delete the change set
10
+ end
11
+ end
12
+
13
+ def preview_change_set(params)
14
+ create_change_set(params)
15
+ display_change_set
16
+ end
17
+
18
+ def create_change_set(params)
19
+ unless stack_exists?(@stack_name)
20
+ puts "Cannot create a change set for the stack because the #{@stack_name} does not exists."
21
+ return
22
+ end
23
+ exist_unless_updatable(stack_status(@stack_name))
24
+
25
+ template_body = IO.read(@template_path)
26
+ # begin
27
+ cfn.create_change_set(
28
+ change_set_name: change_set_name,
29
+ stack_name: @stack_name,
30
+ template_body: template_body,
31
+ parameters: params
32
+ )
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
42
+ end
43
+
44
+ def display_change_set
45
+ print "Generating CloudFormation Change Set for preview.."
46
+ change_set = describe_change_set
47
+ until change_set_finished?(change_set) do
48
+ change_set = describe_change_set
49
+ sleep 1
50
+ print '.'
51
+ end
52
+ puts
53
+
54
+ case change_set.status
55
+ when "CREATE_COMPLETE"
56
+ puts "CloudFormation preview for '#{@stack_name}' stack update. Changes:"
57
+ change_set.changes.each do |change|
58
+ display_change(change)
59
+ end
60
+ when "FAILED"
61
+ puts "Fail to create a CloudFormation preview for '#{@stack_name}' stack update. Reason:".colorize(:red)
62
+ puts change_set.status_reason
63
+ quit(1)
64
+ else
65
+ raise "hell: never come here"
66
+ end
67
+ end
68
+
69
+ def delete_change_set
70
+ cfn.delete_change_set(
71
+ change_set_name: change_set_name,
72
+ stack_name: @stack_name
73
+ )
74
+ end
75
+
76
+ def execute_change_set
77
+ cfn.execute_change_set(
78
+ change_set_name: change_set_name,
79
+ stack_name: @stack_name
80
+ )
81
+ end
82
+
83
+ # generates a change set name
84
+ def change_set_name
85
+ @change_set_name ||= "changeset-#{Time.now.strftime("%Y%d%m%H%M%S")}"
86
+ end
87
+
88
+ private
89
+ # Private: formats a Aws::CloudFormation::Types::Change in pretty human readable form
90
+ #
91
+ # change - Aws::CloudFormation::Types::Change
92
+ #
93
+ # Examples
94
+ #
95
+ # display_change(change)
96
+ # => Remove AWS::Route53::RecordSet: DnsRecord testsubdomain.sub.tongueroo.com
97
+ #
98
+ # Returns nil
99
+ #
100
+ # change.to_h
101
+ # {:type=>"Resource",
102
+ # :resource_change=>
103
+ # {:action=>"Remove",
104
+ # :logical_resource_id=>"DnsRecord",
105
+ # :physical_resource_id=>"testsubdomain.sub.tongueroo.com",
106
+ # :resource_type=>"AWS::Route53::RecordSet",
107
+ # :scope=>[],
108
+ # :details=>[]}}
109
+ def display_change(change)
110
+ message = if change.type == "Resource"
111
+ c = change.resource_change
112
+ "#{c.action} #{c.resource_type}: #{c.logical_resource_id} #{c.physical_resource_id}"
113
+ else
114
+ change.to_h
115
+ end
116
+
117
+ colors = { Remove: :red, Add: :green, Modify: :yellow }
118
+ action = change.resource_change.action.to_sym
119
+ message = message.colorize(colors[action]) if colors.has_key?(action)
120
+ puts message
121
+ end
122
+
123
+ def change_set_finished?(change_set)
124
+ change_set.status =~ /_COMPLETE/ || change_set.status == "FAILED"
125
+ end
126
+
127
+ def describe_change_set
128
+ cfn.describe_change_set(
129
+ change_set_name: change_set_name,
130
+ stack_name: @stack_name
131
+ )
132
+ end
133
+ end
@@ -0,0 +1,62 @@
1
+ class Lono::Cfn::Update < Lono::Cfn::Base
2
+ # save_stack is the interface method
3
+ def save_stack(params)
4
+ update_stack(params)
5
+ end
6
+
7
+ # aws cloudformation update-stack --stack-name prod-hi-123456789 --parameters file://output/params/prod-hi-123456789.json --template-body file://output/prod-hi.json
8
+ def update_stack(params)
9
+ message = "Updating #{@stack_name} stack"
10
+ if @options[:noop]
11
+ puts "NOOP #{message}"
12
+ return
13
+ end
14
+
15
+ unless stack_exists?(@stack_name)
16
+ puts "Cannot update a stack because the #{@stack_name} does not exists."
17
+ return
18
+ end
19
+ exist_unless_updatable(stack_status(@stack_name))
20
+
21
+ error = nil
22
+ diff.run if @options[:diff]
23
+ preview.run if @options[:preview]
24
+ are_you_sure?(:update)
25
+
26
+ if @options[:change_set] # defaults to this
27
+ message << " via change set: #{preview.change_set_name}"
28
+ change_set_update
29
+ else
30
+ standard_update(params)
31
+ end
32
+ puts message unless @options[:mute] || error
33
+ end
34
+
35
+ def standard_update(params)
36
+ template_body = IO.read(@template_path)
37
+ begin
38
+ cfn.update_stack(
39
+ stack_name: @stack_name,
40
+ template_body: template_body,
41
+ parameters: params#,
42
+ # capabilities: ["CAPABILITY_IAM"]
43
+ )
44
+ rescue Aws::CloudFormation::Errors::ValidationError => e
45
+ puts "ERROR: #{e.message}".red
46
+ error = true
47
+ end
48
+ end
49
+
50
+ def preview
51
+ options = @options.merge(lono: false, mute_params: true, mute_using: true, keep: true)
52
+ @preview ||= Preview.new(@stack_name, options)
53
+ end
54
+
55
+ def diff
56
+ @diff ||= Diff.new(@stack_name, @options.merge(lono: false, mute_params: true, mute_using: true))
57
+ end
58
+
59
+ def change_set_update
60
+ preview.execute_change_set
61
+ end
62
+ end
@@ -0,0 +1,21 @@
1
+ module Lono::Cfn::Util
2
+ def are_you_sure?(action)
3
+ if @options[:sure]
4
+ sure = 'y'
5
+ else
6
+ message = case action
7
+ when :update
8
+ "Are you sure you want to want to update the stack with the changes? (y/N)"
9
+ when :delete
10
+ "Are you sure you want to want to delete the stack with the changes? (y/N)"
11
+ end
12
+ puts message
13
+ sure = $stdin.gets
14
+ end
15
+
16
+ unless sure =~ /^y/
17
+ puts "Exiting without #{action}"
18
+ exit 0
19
+ end
20
+ end
21
+ end
@@ -1,8 +1,10 @@
1
1
  require 'thor'
2
- require 'lono/cli/help'
2
+ require 'lono/command'
3
3
 
4
4
  module Lono
5
- class CLI < Thor
5
+ autoload :Help, 'lono/help'
6
+
7
+ class CLI < Command
6
8
 
7
9
  desc "new [NAME]", "Generates lono starter project"
8
10
  Help.new_long_desc
@@ -13,20 +15,16 @@ module Lono
13
15
  Lono::New.new(options.clone.merge(project_root: project_root)).run
14
16
  end
15
17
 
16
- desc "generate", "Generate the cloudformation templates"
18
+ desc "generate", "Generate both CloudFormation templates and parameters files"
17
19
  Help.generate
18
20
  option :clean, type: :boolean, aliases: "-c", desc: "remove all output files before generating"
19
21
  option :project_root, default: ".", aliases: "-r", desc: "project root"
20
22
  option :quiet, type: :boolean, aliases: "-q", desc: "silence the output"
21
23
  option :pretty, type: :boolean, default: true, desc: "json pretty the output. only applies with json format"
22
24
  def generate
23
- Lono::DSL.new(options.clone).run
24
- end
25
-
26
- desc "bashify [URL-OR-PATH]", "Convert the UserData section of an existing CloudFormation Template to a starter bash script that is compatiable with lono"
27
- Help.bashify
28
- def bashify(path)
29
- Lono::Bashify.new(path: path).run
25
+ puts "Generating both CloudFormation template and parameter files."
26
+ Lono::Template::DSL.new(options.clone).run
27
+ Lono::Param::Generator.generate_all(options.clone)
30
28
  end
31
29
 
32
30
  desc "version", "Prints version"
@@ -34,5 +32,16 @@ module Lono
34
32
  puts Lono::VERSION
35
33
  end
36
34
 
35
+ desc "template ACTION", "template subcommand tasks"
36
+ long_desc Help.template
37
+ subcommand "template", Template
38
+
39
+ desc "cfn ACTION", "cfn subcommand tasks"
40
+ long_desc Help.cfn
41
+ subcommand "cfn", Cfn
42
+
43
+ desc "param ACTION", "param subcommand tasks"
44
+ long_desc Help.param
45
+ subcommand "param", Lono::Param
37
46
  end
38
47
  end