Class: Stackr::CloudFormation

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

Instance Method Summary collapse

Instance Method Details

#create_stack(template, options) ⇒ Object

Takes a Stackr::Template



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

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_stack(stack_name) ⇒ Object



145
146
147
148
149
150
151
152
153
# File 'lib/stackr/cloudformation.rb', line 145

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

#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_stacksObject



155
156
157
158
# File 'lib/stackr/cloudformation.rb', line 155

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

#stack_parameters(parameter_map) ⇒ Object



61
62
63
# File 'lib/stackr/cloudformation.rb', line 61

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.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/stackr/cloudformation.rb', line 45

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



113
114
115
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
# File 'lib/stackr/cloudformation.rb', line 113

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
# 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::MissingTemplateBucketError, 'Please set TEMPLATE_BUCKET environment variable before uploading templates to S3.'
  end
  bucket = s3.bucket(ENV['TEMPLATE_BUCKET'])
  key = "#{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



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

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::MissingParameterError, "Required parameter #{param_name} (#{env_var}) not specified."
    end
  end
end