Class: Stackr::CloudFormation

Inherits:
Object
  • Object
show all
Defined in:
lib/stackr/cloudformation.rb

Instance Method Summary collapse

Instance Method Details

#create_change_set(stack_name, template, change_set_name, options) ⇒ Object

template is a Stackr::Template



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/stackr/cloudformation.rb', line 164

def create_change_set(stack_name, template, change_set_name, options)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end

  opts = {
    stack_name:      stack_name,
    change_set_name: change_set_name,
    parameters:      stack_parameters(template.parameter_map),
    capabilities:    template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  begin
    # stack.update(opts)
    resp = cfn.client.create_change_set(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    case e.message
    when 'No updates are to be performed.'
      raise Stackr::StackUpdateNotRequiredError, "Stack [#{stack_name}] requires no updates."
    when "Stack [#{stack_name}] does not exist"
      raise Stackr::StackMissingError, e.message
    else
      raise e
    end
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    raise Stackr::InsufficientCapabilitiesError, "#{e.message}\nPlease add them to your template and run update again."
  end
  say "Change set #{change_set_name}, id: #{resp.id}"
end

#create_stack(template, options) ⇒ Object

Takes a Stackr::Template



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/stackr/cloudformation.rb', line 93

def create_stack(template, options)
  cfn = Aws::CloudFormation::Resource.new
  stack_name = options[:name] || template.name

  opts = {
    stack_name:       options[:name] || template.name,
    parameters:       stack_parameters(template.parameter_map),
    disable_rollback: options[:disable_rollback],
    capabilities:     template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  # Trap error raised when stack already exists
  begin
    cfn.create_stack(opts)
  rescue Aws::CloudFormation::Errors::AlreadyExistsException => e
    raise Stackr::StackAlreadyExistsError, e.message
  end
end

#delete_change_set(change_set_name, stack_name) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/stackr/cloudformation.rb', line 227

def delete_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    resp = cfn.delete_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
end

#delete_stack(stack_name) ⇒ Object



148
149
150
151
152
153
154
155
156
# File 'lib/stackr/cloudformation.rb', line 148

def delete_stack(stack_name)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if stack.exists?
    stack.delete
  else
    raise Stackr::StackMissingError, "Stack [#{stack_name}] does not exist."
  end
end

#execute_change_set(change_set_name, stack_name) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/stackr/cloudformation.rb', line 244

def execute_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    cfn.execute_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
end

#is_too_big?(template_str) ⇒ Boolean

Is this template too big to include in API calls? If so, we better upload it to S3

Returns:

  • (Boolean)


9
10
11
12
# File 'lib/stackr/cloudformation.rb', line 9

def is_too_big?(template_str)
  template_str.bytesize > 51200
  true
end

#is_way_too_big?(template_str) ⇒ Boolean

Is this template too big for CloudFormation

Returns:

  • (Boolean)


15
16
17
# File 'lib/stackr/cloudformation.rb', line 15

def is_way_too_big?(template_str)
  template_str.bytesize > 460800
end

#list_change_sets(stack_name) ⇒ Object



199
200
201
202
203
204
205
206
207
# File 'lib/stackr/cloudformation.rb', line 199

def list_change_sets(stack_name)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end
  resp = cfn.client.list_change_sets({stack_name: stack_name})
  return resp.summaries
end

#list_stacksObject



158
159
160
161
# File 'lib/stackr/cloudformation.rb', line 158

def list_stacks
  cfn = Aws::CloudFormation::Resource.new
  cfn.stacks
end

#show_change_set(change_set_name, stack_name) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/stackr/cloudformation.rb', line 209

def show_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    resp = cfn.describe_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
  return resp.data
end

#stack_parameters(parameter_map) ⇒ Object



64
65
66
# File 'lib/stackr/cloudformation.rb', line 64

def stack_parameters(parameter_map)
  parameter_map.map { |k,v| {parameter_key: k, parameter_value: ENV[v]} }
end

#template_argument(template) ⇒ Object

Return proper argument for CloudFormation api calls. If template is too big, upload it to s3 first and return s3_url otherwise return template_contents But if we’ve already uploaded the template to s3 this run, (because we validated it and then ran a create-stack), don’t upload it a second time, just return the s3 url.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/stackr/cloudformation.rb', line 48

def template_argument(template)

  if is_way_too_big? template.body
    raise Stackr::TemplateTooBigError, "Template #{template.name} is too big for CloudFormation."
  end

  if is_too_big? template.body
    if template.url.nil?
      template.url = upload_to_s3(template.body, template.name)
    end
    return {template_url: template.url}
  end

  return {template_body: template.body}
end

#update_stack(template, options) ⇒ Object

Takes a Stackr::Template



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/stackr/cloudformation.rb', line 116

def update_stack(template, options)
  cfn = Aws::CloudFormation::Resource.new
  stack_name = options[:name] || template.name
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end

  opts = {
    parameters:    stack_parameters(template.parameter_map),
    capabilities:  template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  begin
    stack.update(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    case e.message
    when 'No updates are to be performed.'
      raise Stackr::StackUpdateNotRequiredError, "Stack [#{stack_name}] requires no updates."
    when "Stack [#{stack_name}] does not exist"
      raise Stackr::StackMissingError, e.message
    else
      raise e
    end
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    raise Stackr::InsufficientCapabilitiesError, "#{e.message}\nPlease add them to your template and run update again."
  end
end

#upload_to_s3(template_str, name) ⇒ Object

Requires TEMPLATE_BUCKET environment variable to be set. If TEMPLATE_PREFIX environment variable is set, templates will be uploaded using that prefix.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/stackr/cloudformation.rb', line 22

def upload_to_s3(template_str, name)
  s3 = Aws::S3::Resource.new
  if ENV['TEMPLATE_BUCKET'].nil?
    raise Stackr::TemplateBucketMissingError, 'Please set TEMPLATE_BUCKET environment variable before uploading templates to S3.'
  end
  if ENV['ENVIRONMENT'].nil?
    raise Stackr::EnvironmentMissingError, 'Please set ENVIRONMENT environment variable before uploading templates to S3.'
  end
  bucket = s3.bucket(ENV['TEMPLATE_BUCKET'])
  key = "#{ENV['ENVIRONMENT']}/#{name}.json"
  if ENV['TEMPLATE_PREFIX']
    key = "#{ENV['TEMPLATE_PREFIX']}/#{key}"
  end
  s3_object = bucket.object(key)
  s3_object.put(body: template_str)
  return s3_object.public_url
end

#validate_template(template) ⇒ Object

Raise an error if the template does not validate takes a Stackr::Template



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/stackr/cloudformation.rb', line 70

def validate_template(template)
  cfn = Aws::CloudFormation::Client.new

  opts = template_argument(template)
  begin
    resp = cfn.validate_template(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    raise Stackr::TemplateValidationError, e.message
  end

  # validate parameters
  # Make sure each parameter w/o a default has a value
  resp.parameters.each do |param|
    param_name = param[:parameter_key]
    env_var    = template.parameter_map[param_name]

    if param[:default_value].nil? && ENV[env_var].nil?
      raise Stackr::ParameterMissingError, "Required parameter #{param_name} (#{env_var}) not specified."
    end
  end
end