Class: Humidifier::Stack

Inherits:
Object
  • Object
show all
Defined in:
lib/humidifier/stack.rb

Overview

Represents a CFN stack

Defined Under Namespace

Classes: NoResourcesError, TemplateTooLargeError, UploadNotConfiguredError

Constant Summary collapse

AWS_REGION =

The AWS region, can be set through the environment, defaults to us-east-1

ENV['AWS_REGION'] || 'us-east-1'
ENUMERABLE_RESOURCES =

Lists of objects linked to the stack

Humidifier.underscore(
  %w[Conditions Mappings Outputs Parameters Resources]
)
MAX_TEMPLATE_BODY_SIZE =

The maximum size a template body can be before it has to be put somewhere and referenced through a URL

51_200
MAX_TEMPLATE_URL_SIZE =

The maximum size a template body can be inside of an S3 bucket

460_800
MAX_WAIT =

The maximum amount of time that Humidifier should wait for a stack to complete a CRUD operation

600
STATIC_RESOURCES =

Single settings on the stack

Humidifier.underscore(%w[AWSTemplateFormatVersion Description Metadata])

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Stack

Returns a new instance of Stack.



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/humidifier/stack.rb', line 68

def initialize(opts = {})
  @name = opts[:name]
  @id = opts[:id]
  @default_identifier = self.class.next_default_identifier

  ENUMERABLE_RESOURCES.each_value do |property|
    instance_variable_set(:"@#{property}", opts.fetch(property, {}))
  end

  STATIC_RESOURCES.each_value do |property|
    instance_variable_set(:"@#{property}", opts[property])
  end
end

Instance Attribute Details

#clientObject



104
105
106
# File 'lib/humidifier/stack.rb', line 104

def client
  @client ||= Aws::CloudFormation::Client.new(region: AWS_REGION)
end

#idObject

Returns the value of attribute id.



63
64
65
# File 'lib/humidifier/stack.rb', line 63

def id
  @id
end

Class Method Details

.next_default_identifierObject



212
213
214
215
216
# File 'lib/humidifier/stack.rb', line 212

def self.next_default_identifier
  @count ||= 0
  @count += 1
  "humidifier-stack-template-#{@count}"
end

Instance Method Details

#add(name, resource, attributes = {}) ⇒ Object



82
83
84
85
86
# File 'lib/humidifier/stack.rb', line 82

def add(name, resource, attributes = {})
  resources[name] = resource
  resource.update_attributes(attributes) if attributes.any?
  resource
end

#add_condition(name, opts = {}) ⇒ Object



88
89
90
# File 'lib/humidifier/stack.rb', line 88

def add_condition(name, opts = {})
  conditions[name] = Condition.new(opts)
end

#add_mapping(name, opts = {}) ⇒ Object



92
93
94
# File 'lib/humidifier/stack.rb', line 92

def add_mapping(name, opts = {})
  mappings[name] = Mapping.new(opts)
end

#add_output(name, opts = {}) ⇒ Object



96
97
98
# File 'lib/humidifier/stack.rb', line 96

def add_output(name, opts = {})
  outputs[name] = Output.new(opts)
end

#add_parameter(name, opts = {}) ⇒ Object



100
101
102
# File 'lib/humidifier/stack.rb', line 100

def add_parameter(name, opts = {})
  parameters[name] = Parameter.new(opts)
end

#create(opts = {}) ⇒ Object



121
122
123
124
125
126
127
# File 'lib/humidifier/stack.rb', line 121

def create(opts = {})
  params = { stack_name: name }.merge!(template_for(opts)).merge!(opts)

  try_valid do
    client.create_stack(params).tap { |response| @id = response.stack_id }
  end
end

#create_and_wait(opts = {}) ⇒ Object



129
130
131
# File 'lib/humidifier/stack.rb', line 129

def create_and_wait(opts = {})
  perform_and_wait(:create, opts)
end

#create_change_set(opts = {}) ⇒ Object

Raises:



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/humidifier/stack.rb', line 133

def create_change_set(opts = {})
  raise NoResourcesError.new(self, :change) unless resources.any?

  params = {
    stack_name: identifier,
    change_set_name: "changeset-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}"
  }
  params.merge!(template_for(opts)).merge!(opts)

  try_valid { client.create_change_set(params) }
end

#delete(opts = {}) ⇒ Object



145
146
147
148
# File 'lib/humidifier/stack.rb', line 145

def delete(opts = {})
  client.delete_stack({ stack_name: identifier }.merge!(opts))
  true
end

#delete_and_wait(opts = {}) ⇒ Object



150
151
152
# File 'lib/humidifier/stack.rb', line 150

def delete_and_wait(opts = {})
  perform_and_wait(:delete, opts)
end

#deploy(opts = {}) ⇒ Object

Raises:



154
155
156
157
158
# File 'lib/humidifier/stack.rb', line 154

def deploy(opts = {})
  raise NoResourcesError.new(self, :deploy) unless resources.any?

  exists? ? update(opts) : create(opts)
end

#deploy_and_wait(opts = {}) ⇒ Object



160
161
162
# File 'lib/humidifier/stack.rb', line 160

def deploy_and_wait(opts = {})
  perform_and_wait(exists? ? :update : :create, opts)
end

#deploy_change_set(opts = {}) ⇒ Object



164
165
166
# File 'lib/humidifier/stack.rb', line 164

def deploy_change_set(opts = {})
  exists? ? create_change_set(opts) : create(opts)
end

#exists?Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/humidifier/stack.rb', line 168

def exists?
  Aws::CloudFormation::Stack.new(name: identifier).exists?
end

#identifierObject



108
109
110
# File 'lib/humidifier/stack.rb', line 108

def identifier
  id || name || default_identifier
end

#to_cf(serializer = :json) ⇒ Object



112
113
114
115
116
117
118
119
# File 'lib/humidifier/stack.rb', line 112

def to_cf(serializer = :json)
  resources = static_resources.merge!(enumerable_resources)

  case serializer
  when :json then JSON.pretty_generate(resources)
  when :yaml then YAML.dump(resources)
  end
end

#update(opts = {}) ⇒ Object



172
173
174
175
176
177
178
179
180
181
# File 'lib/humidifier/stack.rb', line 172

def update(opts = {})
  params = {
    capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM],
    stack_name: identifier
  }

  params.merge!(template_for(opts)).merge!(opts)

  try_valid { client.update_stack(params) }
end

#update_and_wait(opts = {}) ⇒ Object



183
184
185
# File 'lib/humidifier/stack.rb', line 183

def update_and_wait(opts = {})
  perform_and_wait(:update, opts)
end

#uploadObject

rubocop:disable Metrics/AbcSize

Raises:



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/humidifier/stack.rb', line 187

def upload # rubocop:disable Metrics/AbcSize
  raise NoResourcesError.new(self, :upload) unless resources.any?

  bucket = Humidifier.config.s3_bucket
  raise UploadNotConfiguredError, identifier unless bucket

  Aws.config.update(region: AWS_REGION)
  key = "#{Humidifier.config.s3_prefix}#{identifier}.json"

  Aws::S3::Client.new.put_object(body: to_cf, bucket: bucket, key: key)
  Aws::S3::Object.new(bucket, key).presigned_url(:get)
end

#valid?(opts = {}) ⇒ Boolean

Returns:

  • (Boolean)


200
201
202
203
204
205
206
207
208
209
210
# File 'lib/humidifier/stack.rb', line 200

def valid?(opts = {})
  params = template_for(opts).merge!(opts)

  try_valid { client.validate_template(params) }
rescue Aws::CloudFormation::Errors::AccessDenied
  raise Error, <<~MSG
    The authenticated AWS profile does not have the requisite permissions
    to run this command. Ensure the profile has the
    "cloudformation:ValidateTemplate" IAM permission.
  MSG
end