Class: Lono::Cfn::Base

Inherits:
Object
  • Object
show all
Includes:
AwsService, Util
Defined in:
lib/lono/cfn/base.rb

Direct Known Subclasses

Create, Diff, Download, Preview, Update

Instance Method Summary collapse

Methods included from Util

#are_you_sure?

Methods included from AwsService

#cfn, #stack_exists?, #testing_update?

Constructor Details

#initialize(stack_name, options = {}) ⇒ Base

Returns a new instance of Base.



7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/lono/cfn/base.rb', line 7

def initialize(stack_name, options={})
  @options = options # options must be set first because @option used in append_suffix
  stack_name = switch_current(stack_name)
  @stack_name = append_suffix(stack_name)
  Lono::ProjectChecker.check unless options[:lono] # already ran checker in lono generate

  @template_name = options[:template] || remove_suffix(@stack_name)
  @param_name = options[:param] || @template_name
  @template_path = get_source_path(@template_name, :template)
  @param_path = get_source_path(@param_name, :param)
  puts "Using template: #{@template_path}" unless @options[:mute_using]
  puts "Using parameters: #{@param_path}" unless @options[:mute_using]
end

Instance Method Details

#append_suffix(stack_name) ⇒ Object

Appends a short suffix at the end of a stack name. Lono internally strips this same suffix for the template name. Makes it convenient for the development flow.

lono cfn current --suffix 1
lono cfn create demo => demo-1
lono cfn update demo-1

Instead of typing:

lono cfn create demo-1 --template demo
lono cfn update demo-1 --template demo

The suffix can be specified at the CLI but can also be saved as a preference.

A random suffix can be specified with random. Example:

lono cfn current --suffix random
lono cfn create demo => demo-[RANDOM], example: demo-abc
lono cfn update demo-abc

It is not a default setting because it might confuse new lono users.



221
222
223
224
# File 'lib/lono/cfn/base.rb', line 221

def append_suffix(stack_name)
  suffix = Lono.suffix == 'random' ? random_suffix : Lono.suffix
  [stack_name, suffix].compact.join('-')
end

#build_scriptsObject



84
85
86
# File 'lib/lono/cfn/base.rb', line 84

def build_scripts
  Lono::Script::Build.new.run
end

#capabilitiesObject



255
256
257
258
259
260
# File 'lib/lono/cfn/base.rb', line 255

def capabilities
  return @options[:capabilities] if @options[:capabilities]
  if @options[:iam]
    ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
  end
end

#check_filesObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/lono/cfn/base.rb', line 131

def check_files
  errors, warns = [], []
  unless File.exist?(@template_path)
    errors << "Template file missing: could not find #{@template_path}"
  end
  # Examples:
  #   @param_path = params/prod/ecs.txt
  #              => output/params/prod/ecs.json
  output_param_path = @param_path.sub(/\.txt/, '.json')
  output_param_path = "#{Lono.config.output_path}/#{output_param_path}"
  if @options[:param] && !File.exist?(output_param_path)
    warns << "Parameters file missing: could not find #{output_param_path}"
  end
  [errors, warns]
end

#check_for_errorsObject



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/lono/cfn/base.rb', line 118

def check_for_errors
  errors, warns = check_files
  unless errors.empty?
    puts "Please double check the command you ran.  There were some errors."
    puts "ERROR: #{errors.join("\n")}".colorize(:red)
    exit
  end
  unless warns.empty?
    puts "Please double check the command you ran.  There were some warnings."
    puts "WARN: #{warns.join("\n")}".colorize(:yellow)
  end
end

#command_with_iam(capabilities) ⇒ Object



65
66
67
# File 'lib/lono/cfn/base.rb', line 65

def command_with_iam(capabilities)
  "#{File.basename($0)} #{ARGV.join(' ')} --capabilities #{capabilities}"
end

#convention_path(name, type) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/lono/cfn/base.rb', line 161

def convention_path(name, type)
  path = case type
  when :template
    "#{Lono.config.output_path}/templates/#{name}.yml"
  when :param
    "#{Lono.config.params_path}/#{Lono.env}/#{name}.txt"
  else
    raise "hell: dont come here"
  end
  path.sub(/^\.\//, '')
end

#exit_unless_updatable!(status) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/lono/cfn/base.rb', line 183

def exit_unless_updatable!(status)
  return true if testing_update?
  return false if @options[:noop]

  unless status =~ /_COMPLETE$/
    puts "Cannot create a change set for the stack because the #{@stack_name} is not in an updatable state.  Stack status: #{status}".colorize(:red)
    quit(1)
  end
end

#generate_allObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/lono/cfn/base.rb', line 69

def generate_all
  if @options[:lono]
    build_scripts
    generate_templates
    unless @options[:noop]
      upload_scripts
      upload_files
      upload_templates
    end
  end
  params = generate_params(mute: @options[:mute_params])
  check_for_errors
  params
end

#generate_params(options = {}) ⇒ Object



108
109
110
111
112
113
114
115
116
# File 'lib/lono/cfn/base.rb', line 108

def generate_params(options={})
  generator_options = {
    path: @param_path,
    allow_no_file: true
  }.merge(options)
  generator = Lono::Param::Generator.new(@param_name, generator_options)
  generator.generate  # Writes the json file in CamelCase keys format
  generator.params    # Returns Array in underscore keys format
end

#generate_templatesObject



88
89
90
# File 'lib/lono/cfn/base.rb', line 88

def generate_templates
  Lono::Template::DSL.new.run
end

#get_source_path(path, type) ⇒ Object

if existing in params path then use that if it doesnt assume it is a full path and check that else fall back to convention, which also eventually gets checked in check_for_errors

Type - :param or :template



152
153
154
155
156
157
158
159
# File 'lib/lono/cfn/base.rb', line 152

def get_source_path(path, type)
  if path.nil?
    convention_path(@stack_name, type) # default convention
  else
    # convention path based on the input from the user
    convention_path(path, type)
  end
end

#prompt_for_iam(capabilities) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/lono/cfn/base.rb', line 57

def prompt_for_iam(capabilities)
  puts "This stack will create IAM resources.  Please approve to run the command again with #{capabilities} capabilities."
  puts "  #{command_with_iam(capabilities)}"

  puts "Please confirm (y/n)"
  $stdin.gets
end

#quit(signal) ⇒ Object

To allow mocking in specs



194
195
196
# File 'lib/lono/cfn/base.rb', line 194

def quit(signal)
  exit signal
end

#random_suffixObject

only generate random suffix for Lono::Cfn::Create class



240
241
242
243
# File 'lib/lono/cfn/base.rb', line 240

def random_suffix
  return nil unless self.class.name.to_s =~ /Create/
  (0...3).map { (65 + rand(26)).chr }.join.downcase # Ex: jhx
end

#remove_suffix(stack_name) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/lono/cfn/base.rb', line 226

def remove_suffix(stack_name)
  return stack_name unless Lono.suffix

  if stack_name_suffix == 'random'
    stack_name.sub(/-(\w{3})$/,'') # strip the random suffix at the end
  elsif stack_name_suffix
    pattern = Regexp.new("-#{stack_name_suffix}$",'')
    stack_name.sub(pattern, '') # strip suffix
  else
    stack_name
  end
end

#runObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/lono/cfn/base.rb', line 31

def run
  starting_message
  params = generate_all
  begin
    save_stack(params) # defined in the sub class
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    capabilities = e.message.match(/\[(.*)\]/)[1]
    confirm = prompt_for_iam(capabilities)
    if confirm =~ /^y/
      @options.merge!(capabilities: [capabilities])
      puts "Re-running: #{command_with_iam(capabilities).colorize(:green)}"
      retry
    else
      puts "Exited"
      exit 1
    end
  end

  return unless @options[:wait]
  status.wait
end

#s3_folderObject



270
271
272
273
# File 'lib/lono/cfn/base.rb', line 270

def s3_folder
  setting = Lono::Setting.new
  setting.s3_folder
end

#set_template_body!(params) ⇒ Object

Either set the templmate_body or template_url attribute based on if template was uploaded to s3. Nice to reference s3 url because the max size of the template body is greater if the template body is on s3. Limits:

template_body: 51,200 bytes
template_url: 460,800 bytes

Reference: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html



284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/lono/cfn/base.rb', line 284

def set_template_body!(params)
  # if s3_folder is set this means s3 upload is enabled
  if s3_folder # s3_folder defined in cfn/base.rb
    upload = Lono::Template::Upload.new
    url = upload.s3_presigned_url(@template_path)
    params[:template_url] = url
  else
    params[:template_body] = IO.read(@template_path)
  end

  params
end

#show_parameters(params, meth = nil) ⇒ Object



262
263
264
265
266
267
268
# File 'lib/lono/cfn/base.rb', line 262

def show_parameters(params, meth=nil)
  params = params.clone.compact
  params[:template_body] = "Hidden due to size... View at: #{@template_path}"
  to = meth || "AWS API"
  puts "Parameters passed to #{to}:"
  puts YAML.dump(params.deep_stringify_keys)
end

#stack_name_suffixObject



245
246
247
248
249
250
251
252
253
# File 'lib/lono/cfn/base.rb', line 245

def stack_name_suffix
  if @options[:suffix] && !@options[:suffix].nil?
    return @options[:suffix] # CLI option takes highest precedence
  end

  # otherwise use the settings preference
  settings = Lono::Setting.new
  settings.data['stack_name_suffix']
end

#stack_status(stack_name) ⇒ Object



175
176
177
178
179
180
181
# File 'lib/lono/cfn/base.rb', line 175

def stack_status(stack_name)
  return true if testing_update?
  return false if @options[:noop]

  resp = cfn.describe_stacks(stack_name: stack_name)
  resp.stacks[0].stack_status
end

#starting_messageObject



25
26
27
28
29
# File 'lib/lono/cfn/base.rb', line 25

def starting_message
  action = self.class.to_s.split('::').last
  action = action[0..-2] + 'ing' # create => creating
  puts "#{action} #{@stack_name.colorize(:green)} stack..."
end

#statusObject



53
54
55
# File 'lib/lono/cfn/base.rb', line 53

def status
  @status ||= Lono::Cfn::Status.new(@stack_name)
end

#switch_current(stack_name) ⇒ Object



21
22
23
# File 'lib/lono/cfn/base.rb', line 21

def switch_current(stack_name)
  Lono::Cfn::Current.name!(stack_name)
end

#upload_filesObject



103
104
105
106
# File 'lib/lono/cfn/base.rb', line 103

def upload_files
  return unless s3_folder
  Lono::FileUploader.new.upload_all
end

#upload_scriptsObject

only upload templates if s3_folder configured in settings



98
99
100
101
# File 'lib/lono/cfn/base.rb', line 98

def upload_scripts
  return unless s3_folder
  Lono::Script::Upload.new.run
end

#upload_templatesObject

only upload templates if s3_folder configured in settings



93
94
95
# File 'lib/lono/cfn/base.rb', line 93

def upload_templates
  Lono::Template::Upload.new.run if s3_folder
end