humidifier 1.8.0 → 1.9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,8 @@ module Humidifier
31
31
  # Upload a CFN stack to S3 so that it can be referenced via template_url
32
32
  def upload(payload)
33
33
  Humidifier.config.ensure_upload_configured!(payload)
34
- upload_object(payload, "#{Humidifier.config.s3_prefix}#{payload.identifier}.json")
34
+ filename = "#{Humidifier.config.s3_prefix}#{payload.identifier}.json"
35
+ upload_object(payload, filename)
35
36
  end
36
37
 
37
38
  # Validate a template in CFN
@@ -48,7 +49,8 @@ module Humidifier
48
49
  private
49
50
 
50
51
  def client
51
- @client ||= base_module::CloudFormation::Client.new(region: AwsShim::REGION)
52
+ @client ||=
53
+ base_module::CloudFormation::Client.new(region: AwsShim::REGION)
52
54
  end
53
55
 
54
56
  def try_valid
@@ -2,7 +2,8 @@ module Humidifier
2
2
  module AwsAdapters
3
3
  # An adapter used when neither SDK is loaded
4
4
  class Noop
5
- # Capture all STACK_METHODS method calls and warn that they will not be run
5
+ # Capture all STACK_METHODS method calls and warn that they will not be
6
+ # run
6
7
  def method_missing(method, *)
7
8
  if AwsShim::STACK_METHODS.include?(method)
8
9
  puts "WARNING: Cannot run #{method} because aws-sdk not loaded."
@@ -23,12 +23,20 @@ module Humidifier
23
23
  AWS
24
24
  end
25
25
 
26
+ def failure_event_filtered?(event)
27
+ !event.resource_status.include?('FAILED') ||
28
+ !event.key?(:resource_status_reason)
29
+ end
30
+
26
31
  def handle_failure(payload)
27
32
  reasons = []
28
- client.describe_stack_events(stack_name: payload.identifier).stack_events.each do |event|
29
- next unless event.resource_status.include?('FAILED') && event.key?(:resource_status_reason)
33
+ response = client.describe_stack_events(stack_name: payload.identifier)
34
+
35
+ response.stack_events.each do |event|
36
+ next if failure_event_filtered?(event)
30
37
  reasons.unshift(event.resource_status_reason)
31
38
  end
39
+
32
40
  raise "#{payload.name} stack failed:\n#{reasons.join("\n")}"
33
41
  end
34
42
 
@@ -37,7 +45,8 @@ module Humidifier
37
45
 
38
46
  aws_stack = nil
39
47
  Sleeper.new(payload.max_wait) do
40
- aws_stack = client.describe_stacks(stack_name: payload.identifier).stacks.first
48
+ response = client.describe_stacks(stack_name: payload.identifier)
49
+ aws_stack = response.stacks.first
41
50
  !aws_stack.stack_status.end_with?('IN_PROGRESS')
42
51
  end
43
52
 
@@ -48,11 +57,14 @@ module Humidifier
48
57
  def upload_object(payload, key)
49
58
  base_module.config(region: AwsShim::REGION)
50
59
  @s3 ||= base_module::S3.new
51
- @s3.buckets[Humidifier.config.s3_bucket].objects.create(key, payload.template_body).url_for(:read)
60
+
61
+ objects = @s3.buckets[Humidifier.config.s3_bucket].objects
62
+ objects.create(key, payload.template_body).url_for(:read)
52
63
  end
53
64
 
54
65
  def unsupported(method)
55
- puts "WARNING: Cannot #{method} because that functionality is not supported in V1 of aws-sdk."
66
+ puts "WARNING: Cannot #{method} because that functionality is not " \
67
+ 'supported in V1 of aws-sdk.'
56
68
  false
57
69
  end
58
70
  end
@@ -2,9 +2,13 @@ module Humidifier
2
2
  module AwsAdapters
3
3
  # The adapter for v2 of aws-sdk
4
4
  class SDKV2 < Base
5
+ # Format of the timestamp used in changeset naming
6
+ TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'.freeze
7
+
5
8
  # Create a change set in CFN
6
9
  def create_change_set(payload)
7
- payload.merge(change_set_name: "changeset-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
10
+ change_set_name = "changeset-#{Time.now.strftime(TIME_FORMAT)}"
11
+ payload.merge(change_set_name: change_set_name)
8
12
  try_valid { client.create_change_set(payload.create_change_set_params) }
9
13
  end
10
14
 
@@ -27,8 +31,9 @@ module Humidifier
27
31
  def perform_and_wait(method, payload)
28
32
  method = exists?(payload) ? :update : :create if method == :deploy
29
33
  response = public_send(method, payload)
34
+ signal = :"stack_#{method}_complete"
30
35
 
31
- client.wait_until(:"stack_#{method}_complete", stack_name: payload.identifier) do |waiter|
36
+ client.wait_until(signal, stack_name: payload.identifier) do |waiter|
32
37
  waiter.max_attempts = payload.max_wait / 5
33
38
  waiter.delay = 5
34
39
  end
@@ -39,8 +44,15 @@ module Humidifier
39
44
  def upload_object(payload, key)
40
45
  base_module.config.update(region: AwsShim::REGION)
41
46
  @s3_client ||= base_module::S3::Client.new
42
- @s3_client.put_object(body: payload.template_body, bucket: Humidifier.config.s3_bucket, key: key)
43
- base_module::S3::Object.new(Humidifier.config.s3_bucket, key).presigned_url(:get)
47
+
48
+ @s3_client.put_object(
49
+ body: payload.template_body,
50
+ bucket: Humidifier.config.s3_bucket,
51
+ key: key
52
+ )
53
+
54
+ object = base_module::S3::Object.new(Humidifier.config.s3_bucket, key)
55
+ object.presigned_url(:get)
44
56
  end
45
57
  end
46
58
  end
@@ -17,13 +17,13 @@ module Humidifier
17
17
  create_change_set deploy_change_set
18
18
  ].freeze
19
19
 
20
- attr_accessor :shim
20
+ attr_reader :shim
21
21
 
22
22
  # Either set the SDK based on the configured option or guess the SDK
23
23
  # version by attempting to require both aws-sdk-v1 and aws-sdk, then setting
24
24
  # the shim based on what successfully loaded
25
25
  def initialize
26
- self.shim =
26
+ @shim =
27
27
  if Humidifier.config.sdk_version_1?
28
28
  AwsAdapters::SDKV1.new
29
29
  elsif Humidifier.config.sdk_version_2?
@@ -1,10 +1,10 @@
1
1
  module Humidifier
2
2
  # Represents a CFN stack condition
3
3
  class Condition
4
- attr_accessor :opts
4
+ attr_reader :opts
5
5
 
6
6
  def initialize(opts)
7
- self.opts = opts
7
+ @opts = opts
8
8
  end
9
9
 
10
10
  # CFN stack syntax
@@ -4,9 +4,10 @@ module Humidifier
4
4
  # The message that gets displayed when the stack body is too large to use
5
5
  # the template_body option
6
6
  UPLOAD_MESSAGE = <<-MSG.freeze
7
- The %<identifier>s stack's body is too large to be use the template_body option, and therefore must use the
8
- template_url option instead. You can configure Humidifier to do this automatically by setting up the s3 config
9
- on the top-level Humidifier object like so:
7
+ The %<identifier>s stack's body is too large to be use the template_body option,
8
+ and therefore must use the template_url option instead. You can configure
9
+ Humidifier to do this automatically by setting up the s3 config on the top-level
10
+ Humidifier object like so:
10
11
 
11
12
  Humidifier.configure do |config|
12
13
  config.s3_bucket = 'my.s3.bucket'
@@ -24,7 +25,8 @@ MSG
24
25
 
25
26
  # raise an error unless the s3_bucket field is set
26
27
  def ensure_upload_configured!(payload)
27
- raise UPLOAD_MESSAGE.gsub('%<identifier>s', payload.identifier) if s3_bucket.nil?
28
+ return if s3_bucket
29
+ raise UPLOAD_MESSAGE.gsub('%<identifier>s', payload.identifier)
28
30
  end
29
31
 
30
32
  # true if the sdk_version option is set to 1 or '1'
data/lib/humidifier/fn.rb CHANGED
@@ -2,20 +2,22 @@ module Humidifier
2
2
  # Builds CFN function calls
3
3
  class Fn
4
4
  # The list of all internal functions provided by AWS from
5
- # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
5
+ # http://docs.aws.amazon.com
6
+ # /AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
6
7
  FUNCTIONS = Utils.underscored(%w[And Base64 Equals FindInMap GetAtt GetAZs
7
8
  If ImportValue Join Not Or Select Sub])
8
9
 
9
- attr_accessor :name, :value
10
+ attr_reader :name, :value
10
11
 
11
12
  def initialize(name, value)
12
- self.name = "Fn::#{name}"
13
- self.value = value
13
+ @name = "Fn::#{name}"
14
+ @value = value
14
15
  end
15
16
 
16
17
  # CFN stack syntax
17
18
  def to_cf
18
- { name => value }
19
+ cf_value = value.respond_to?(:cf) ? value.to_cf : value
20
+ { name => cf_value }
19
21
  end
20
22
 
21
23
  class << self
@@ -10,8 +10,10 @@ module Humidifier
10
10
  # Reads the specs/CloudFormationResourceSpecification.json file and load each
11
11
  # resource as a class
12
12
  class Loader
13
+ filename = 'CloudFormationResourceSpecification.json'
14
+
13
15
  # The path to the specification file
14
- SPECPATH = File.expand_path(File.join('..', '..', '..', 'CloudFormationResourceSpecification.json'), __FILE__)
16
+ SPECPATH = File.expand_path(File.join('..', '..', '..', filename), __FILE__)
15
17
 
16
18
  # Handles searching the PropertyTypes specifications for a specific
17
19
  # resource type
@@ -25,7 +27,8 @@ module Humidifier
25
27
  # find the substructures necessary for the given resource key
26
28
  def search(key)
27
29
  results = structs.keys.grep(/#{key}/)
28
- Hash[results.map { |result| result.gsub("#{key}.", '') }.zip(structs.values_at(*results))].merge(global)
30
+ shortened_names = results.map { |result| result.gsub("#{key}.", '') }
31
+ shortened_names.zip(structs.values_at(*results)).to_h.merge(global)
29
32
  end
30
33
 
31
34
  private
@@ -37,8 +40,8 @@ module Humidifier
37
40
 
38
41
  # loop through the specs and register each class
39
42
  def load
40
- parsed = JSON.parse(File.read(SPECPATH))
41
- structs = StructureContainer.new(parsed['PropertyTypes'])
43
+ parsed = JSON.parse(File.read(SPECPATH))
44
+ structs = StructureContainer.new(parsed['PropertyTypes'])
42
45
 
43
46
  parsed['ResourceTypes'].each do |key, spec|
44
47
  match = key.match(/\AAWS::(\w+)::(\w+)\z/)
@@ -68,7 +71,10 @@ module Humidifier
68
71
  aws_name = "AWS::#{group}::#{resource}"
69
72
  resource_class = build_class(aws_name, spec, substructs)
70
73
 
71
- Humidifier.const_set(group, Module.new) unless Humidifier.const_defined?(group)
74
+ unless Humidifier.const_defined?(group)
75
+ Humidifier.const_set(group, Module.new)
76
+ end
77
+
72
78
  Humidifier.const_get(group).const_set(resource, resource_class)
73
79
  Humidifier.registry[aws_name] = resource_class
74
80
  end
@@ -1,10 +1,10 @@
1
1
  module Humidifier
2
2
  # Represents a CFN stack mapping
3
3
  class Mapping
4
- attr_accessor :opts
4
+ attr_reader :opts
5
5
 
6
6
  def initialize(opts = {})
7
- self.opts = opts
7
+ @opts = opts
8
8
  end
9
9
 
10
10
  # CFN stack syntax
@@ -2,14 +2,19 @@ module Humidifier
2
2
  # Represents a CFN stack parameter
3
3
  class Parameter
4
4
  # The allowed properties of all stack parameters
5
- PROPERTIES = Utils.underscored(%w[AllowedPattern AllowedValues ConstraintDescription Default Description
6
- MaxLength MaxValue MinLength MinValue NoEcho])
5
+ PROPERTIES =
6
+ Utils.underscored(%w[AllowedPattern AllowedValues ConstraintDescription
7
+ Default Description MaxLength MaxValue MinLength
8
+ MinValue NoEcho])
7
9
 
8
- attr_accessor :type, *PROPERTIES.values
10
+ attr_reader :type, *PROPERTIES.values
9
11
 
10
12
  def initialize(opts = {})
11
- PROPERTIES.each_value { |prop| send(:"#{prop}=", opts[prop]) }
12
- self.type = opts.fetch(:type, 'String')
13
+ PROPERTIES.each_value do |property|
14
+ instance_variable_set(:"@#{property}", opts[property])
15
+ end
16
+
17
+ @type = opts.fetch(:type, 'String')
13
18
  end
14
19
 
15
20
  # CFN stack syntax
@@ -2,7 +2,8 @@ module Humidifier
2
2
  module Props
3
3
  # Superclass for all CFN properties
4
4
  class Base
5
- # The list of classes that are valid beyond the normal values for each prop
5
+ # The list of classes that are valid beyond the normal values for each
6
+ # prop
6
7
  WHITELIST = [Fn, Ref].freeze
7
8
 
8
9
  attr_reader :key, :name, :spec, :substructs
@@ -29,7 +30,8 @@ module Humidifier
29
30
  [key, Serializer.dump(value)]
30
31
  end
31
32
 
32
- # the type of update that occurs when this property is updated on its associated resource
33
+ # the type of update that occurs when this property is updated on its
34
+ # associated resource
33
35
  def update_type
34
36
  spec['UpdateType']
35
37
  end
@@ -14,7 +14,8 @@ module Humidifier
14
14
 
15
15
  # true if it is a boolean
16
16
  def valid?(value)
17
- whitelisted_value?(value) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
17
+ return true if whitelisted_value?(value)
18
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
18
19
  end
19
20
  end
20
21
  end
@@ -11,12 +11,21 @@ module Humidifier
11
11
 
12
12
  # CFN stack syntax
13
13
  def to_cf(list)
14
- [key, list.map { |value| subprop.to_cf(value).last }]
14
+ cf_value =
15
+ if list.respond_to?(:to_cf)
16
+ list.to_cf
17
+ else
18
+ list.map { |value| subprop.to_cf(value).last }
19
+ end
20
+
21
+ [key, cf_value]
15
22
  end
16
23
 
17
- # Valid if the value is whitelisted or every value in the list is valid on the subprop
24
+ # Valid if the value is whitelisted or every value in the list is valid
25
+ # on the subprop
18
26
  def valid?(list)
19
- whitelisted_value?(list) || (list.is_a?(Enumerable) && list.all? { |value| subprop.valid?(value) })
27
+ return true if whitelisted_value?(list)
28
+ list.is_a?(Enumerable) && list.all? { |value| subprop.valid?(value) }
20
29
  end
21
30
 
22
31
  private
@@ -6,18 +6,29 @@ module Humidifier
6
6
 
7
7
  # converts the value through mapping using the subprop unless it is valid
8
8
  def convert(map)
9
- valid?(map) ? map : map.map { |key, value| [key, subprop.convert(value)] }.to_h
9
+ return map if valid?(map)
10
+ map.map { |key, value| [key, subprop.convert(value)] }.to_h
10
11
  end
11
12
 
12
13
  # CFN stack syntax
13
14
  def to_cf(map)
14
- [key, map.map { |subkey, subvalue| [subkey, subprop.to_cf(subvalue).last] }.to_h]
15
+ cf_value =
16
+ if map.respond_to?(:to_cf)
17
+ map.to_cf
18
+ else
19
+ map.each_with_object({}) do |(subkey, subvalue), serialized|
20
+ serialized[subkey] = subprop.to_cf(subvalue).last
21
+ end
22
+ end
23
+
24
+ [key, cf_value]
15
25
  end
16
26
 
17
27
  # Valid if the value is whitelisted or every value in the map is valid on
18
28
  # the subprop
19
29
  def valid?(map)
20
- whitelisted_value?(map) || (map.is_a?(Hash) && map.values.all? { |value| subprop.valid?(value) })
30
+ return true if whitelisted_value?(map)
31
+ map.is_a?(Hash) && map.values.all? { |value| subprop.valid?(value) }
21
32
  end
22
33
 
23
34
  private
@@ -18,27 +18,50 @@ module Humidifier
18
18
 
19
19
  # CFN stack syntax
20
20
  def to_cf(struct)
21
- [key, struct.map { |subkey, subvalue| subprops[subkey.to_s].to_cf(subvalue) }.to_h]
21
+ cf_value =
22
+ if struct.respond_to?(:to_cf)
23
+ struct.to_cf
24
+ else
25
+ struct.map do |subkey, subvalue|
26
+ subprops[subkey.to_s].to_cf(subvalue)
27
+ end.to_h
28
+ end
29
+
30
+ [key, cf_value]
22
31
  end
23
32
 
24
- # true if the value is whitelisted or Hash and all keys are valid for their corresponding props
33
+ # true if the value is whitelisted or Hash and all keys are valid for
34
+ # their corresponding props
25
35
  def valid?(struct)
26
- whitelisted_value?(struct) || (struct.is_a?(Hash) && valid_struct?(struct))
36
+ return true if whitelisted_value?(struct)
37
+ struct.is_a?(Hash) && valid_struct?(struct)
27
38
  end
28
39
 
29
40
  private
30
41
 
31
42
  def after_initialize(substructs)
32
- type = spec['ItemType'] || spec['Type']
33
- @subprops =
34
- substructs.fetch(type, {}).fetch('Properties', {}).map do |key, config|
35
- subprop = config['ItemType'] == type ? self : Props.from(key, config, substructs)
36
- [Utils.underscore(key), subprop]
37
- end.to_h
43
+ @subprops = subprops_from(substructs, spec['ItemType'] || spec['Type'])
44
+ end
45
+
46
+ def subprops_from(substructs, type)
47
+ subprop_names = substructs.fetch(type, {}).fetch('Properties', {})
48
+
49
+ subprop_names.each_with_object({}) do |(key, config), subprops|
50
+ subprop =
51
+ if config['ItemType'] == type
52
+ self
53
+ else
54
+ Props.from(key, config, substructs)
55
+ end
56
+
57
+ subprops[Utils.underscore(key)] = subprop
58
+ end
38
59
  end
39
60
 
40
61
  def valid_struct?(struct)
41
- struct.all? { |key, value| subprops.key?(key.to_s) && subprops[key.to_s].valid?(value) }
62
+ struct.all? do |key, value|
63
+ subprops.key?(key.to_s) && subprops[key.to_s].valid?(value)
64
+ end
42
65
  end
43
66
  end
44
67
  end
@@ -12,7 +12,8 @@ module Humidifier
12
12
  end
13
13
 
14
14
  # builds a prop that is not a List or Map type
15
- # PrimitiveType is one of Boolean, Double, Integer, Json, String, or Timestamp
15
+ # PrimitiveType is one of Boolean, Double, Integer, Json, String, or
16
+ # Timestamp
16
17
  def singular_from(key, spec, substructs)
17
18
  primitive = spec['PrimitiveItemType'] || spec['PrimitiveType']
18
19
 
@@ -1,10 +1,10 @@
1
1
  module Humidifier
2
2
  # Builds CFN references
3
3
  class Ref
4
- attr_accessor :reference
4
+ attr_reader :reference
5
5
 
6
6
  def initialize(reference)
7
- self.reference = reference
7
+ @reference = reference
8
8
  end
9
9
 
10
10
  # Builds CFN syntax
@@ -3,7 +3,8 @@ module Humidifier
3
3
  class Resource
4
4
  # Attributes that are available to every stack
5
5
  COMMON_ATTRIBUTES =
6
- Utils.underscored(%w[Condition CreationPolicy DeletionPolicy DependsOn Metadata UpdatePolicy])
6
+ Utils.underscored(%w[Condition CreationPolicy DeletionPolicy DependsOn
7
+ Metadata UpdatePolicy])
7
8
 
8
9
  attr_accessor :properties, *COMMON_ATTRIBUTES.values
9
10
 
@@ -13,7 +14,8 @@ module Humidifier
13
14
  end
14
15
 
15
16
  # Patches method_missing to include property accessors
16
- # After the first method call, builds the accessor methods to get a speed boost
17
+ # After the first method call, builds the accessor methods to get a speed
18
+ # boost
17
19
  def method_missing(name, *args)
18
20
  if !valid_accessor?(name)
19
21
  super
@@ -33,19 +35,31 @@ module Humidifier
33
35
 
34
36
  # CFN stack syntax
35
37
  def to_cf
36
- props_cf = properties.map { |key, value| self.class.props[key].to_cf(value) }.to_h
37
- { 'Type' => self.class.aws_name, 'Properties' => props_cf }.merge(common_attributes)
38
+ props_cf =
39
+ properties.map do |key, value|
40
+ self.class.props[key].to_cf(value)
41
+ end
42
+
43
+ common_attributes.merge!(
44
+ 'Type' => self.class.aws_name,
45
+ 'Properties' => props_cf.to_h
46
+ )
38
47
  end
39
48
 
40
49
  # Update a set of properties defined by the given properties hash
41
50
  def update(properties, raw = false)
42
- properties.each { |property, value| update_property(property, value, raw) }
51
+ properties.each do |property, value|
52
+ update_property(property, value, raw)
53
+ end
43
54
  end
44
55
 
45
56
  # Update the attributes of the resource defined by COMMON_ATTRIBUTES
46
57
  def update_attributes(attributes)
47
58
  attributes.each do |attribute, value|
48
- raise ArgumentError, "Invalid attribute: #{attribute}" unless COMMON_ATTRIBUTES.value?(attribute)
59
+ unless COMMON_ATTRIBUTES.value?(attribute)
60
+ raise ArgumentError, "Invalid attribute: #{attribute}"
61
+ end
62
+
49
63
  public_send(:"#{attribute}=", value)
50
64
  end
51
65
  end
@@ -96,18 +110,23 @@ module Humidifier
96
110
 
97
111
  def validate_property(property, raw)
98
112
  property = Utils.underscore(property) if raw
113
+
99
114
  unless self.class.prop?(property)
100
- raise ArgumentError, "Attempting to set invalid property for #{self.class.name}: #{property}"
115
+ raise ArgumentError, 'Attempting to set invalid property for ' \
116
+ "#{self.class.name}: #{property}"
101
117
  end
118
+
102
119
  property
103
120
  end
104
121
 
105
122
  def validate_value(property, value, raw)
106
123
  prop = self.class.props[property]
107
124
  value = prop.convert(value) if raw
125
+
108
126
  unless prop.valid?(value)
109
127
  raise ArgumentError, "Invalid value for #{property}: #{value.inspect}"
110
128
  end
129
+
111
130
  value
112
131
  end
113
132
  end
@@ -1,35 +1,38 @@
1
1
  module Humidifier
2
2
  # The payload sent to the shim methods, representing the stack and the options
3
3
  class SdkPayload
4
- # The maximum size a template body can be before it has to be put somewhere and referenced through a URL
4
+ # The maximum size a template body can be before it has to be put somewhere
5
+ # and referenced through a URL
5
6
  MAX_TEMPLATE_BODY_SIZE = 51_200
6
7
 
7
8
  # The maximum size a template body can be inside of an S3 bucket
8
9
  MAX_TEMPLATE_URL_SIZE = 460_800
9
10
 
10
- # The maximum amount of time that Humidifier should wait for a stack to complete a CRUD operation
11
+ # The maximum amount of time that Humidifier should wait for a stack to
12
+ # complete a CRUD operation
11
13
  MAX_WAIT = 600
12
14
 
13
15
  # Thrown when a template is too large to use the template_url option
14
16
  class TemplateTooLargeError < StandardError
15
17
  def initialize(bytesize)
16
- message = <<-MSG
17
- Cannot use a template > #{MAX_TEMPLATE_URL_SIZE} bytes (currently #{bytesize} bytes), consider using nested stacks
18
- (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html)
19
- MSG
18
+ message =
19
+ "Cannot use a template > #{MAX_TEMPLATE_URL_SIZE} bytes " \
20
+ "(currently #{bytesize} bytes), consider using nested stacks " \
21
+ '(http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide' \
22
+ '/aws-properties-stack.html)'
20
23
  super(message)
21
24
  end
22
25
  end
23
26
 
24
- attr_accessor :stack, :options, :max_wait
27
+ attr_reader :stack, :options, :max_wait
25
28
 
26
29
  extend Forwardable
27
30
  def_delegators :stack, :id=, :identifier, :name
28
31
 
29
32
  def initialize(stack, options)
30
- self.stack = stack
31
- self.options = options
32
- self.max_wait = options.delete(:max_wait) || MAX_WAIT
33
+ @stack = stack
34
+ @options = options
35
+ @max_wait = options.delete(:max_wait) || MAX_WAIT
33
36
  end
34
37
 
35
38
  # True if the stack and options are the same as the other (used for testing)
@@ -39,7 +42,7 @@ MSG
39
42
 
40
43
  # Merge in options
41
44
  def merge(new_options)
42
- self.options = new_options.merge(options)
45
+ @options = new_options.merge(options)
43
46
  end
44
47
 
45
48
  # The body of the template
@@ -78,17 +81,19 @@ MSG
78
81
 
79
82
  private
80
83
 
81
- def template_param
82
- @template_param ||= begin
83
- bytesize = template_body.bytesize
84
- raise TemplateTooLargeError, bytesize if bytesize > MAX_TEMPLATE_URL_SIZE
84
+ def bytesize
85
+ template_body.bytesize.tap do |size|
86
+ raise TemplateTooLargeError, size if size > MAX_TEMPLATE_URL_SIZE
87
+ end
88
+ end
85
89
 
90
+ def template_param
91
+ @template_param ||=
86
92
  if bytesize > MAX_TEMPLATE_BODY_SIZE
87
93
  { template_url: AwsShim.upload(self) }
88
94
  else
89
95
  { template_body: template_body }
90
96
  end
91
- end
92
97
  end
93
98
  end
94
99
  end
@@ -1,11 +1,11 @@
1
1
  module Humidifier
2
2
  # Manages waiting for stack events for v1 of the SDK
3
3
  class Sleeper
4
- attr_accessor :attempts
4
+ attr_reader :attempts
5
5
 
6
6
  # Store attempts and wait for the block to return true
7
7
  def initialize(attempts, &block)
8
- self.attempts = attempts
8
+ @attempts = attempts
9
9
  hibernate_until(&block)
10
10
  end
11
11
 
@@ -15,7 +15,7 @@ module Humidifier
15
15
  raise 'Waited too long, giving up' if attempts.zero?
16
16
  return if yield
17
17
 
18
- self.attempts -= 1
18
+ @attempts -= 1
19
19
  sleep 1
20
20
  hibernate_until(&block)
21
21
  end