humidifier 2.15.0 → 3.0.0

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CloudFormationResourceSpecification.json +24305 -18109
  3. data/LICENSE +1 -1
  4. data/README.md +55 -33
  5. data/lib/humidifier/condition.rb +0 -2
  6. data/lib/humidifier/config.rb +46 -0
  7. data/lib/humidifier/fn.rb +7 -8
  8. data/lib/humidifier/loader.rb +37 -45
  9. data/lib/humidifier/mapping.rb +0 -2
  10. data/lib/humidifier/output.rb +4 -6
  11. data/lib/humidifier/parameter.rb +9 -10
  12. data/lib/humidifier/props.rb +177 -2
  13. data/lib/humidifier/resource.rb +13 -14
  14. data/lib/humidifier/stack.rb +166 -21
  15. data/lib/humidifier/version.rb +1 -1
  16. data/lib/humidifier.rb +31 -24
  17. metadata +67 -31
  18. data/lib/humidifier/aws_adapters/base.rb +0 -67
  19. data/lib/humidifier/aws_adapters/noop.rb +0 -25
  20. data/lib/humidifier/aws_adapters/sdkv1.rb +0 -75
  21. data/lib/humidifier/aws_adapters/sdkv2.rb +0 -61
  22. data/lib/humidifier/aws_adapters/sdkv3.rb +0 -31
  23. data/lib/humidifier/aws_shim.rb +0 -83
  24. data/lib/humidifier/configuration.rb +0 -69
  25. data/lib/humidifier/props/base.rb +0 -47
  26. data/lib/humidifier/props/boolean_prop.rb +0 -25
  27. data/lib/humidifier/props/double_prop.rb +0 -23
  28. data/lib/humidifier/props/integer_prop.rb +0 -23
  29. data/lib/humidifier/props/json_prop.rb +0 -31
  30. data/lib/humidifier/props/list_prop.rb +0 -42
  31. data/lib/humidifier/props/map_prop.rb +0 -46
  32. data/lib/humidifier/props/string_prop.rb +0 -23
  33. data/lib/humidifier/props/structure_prop.rb +0 -71
  34. data/lib/humidifier/props/timestamp_prop.rb +0 -23
  35. data/lib/humidifier/sdk_payload.rb +0 -122
  36. data/lib/humidifier/sleeper.rb +0 -25
  37. data/lib/humidifier/utils.rb +0 -19
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module AwsAdapters
5
- # The adapter for v1 of aws-sdk
6
- class SDKV1 < Base
7
- # Cannot create change sets in V1
8
- def create_change_set(*)
9
- unsupported('create change set')
10
- end
11
-
12
- # Cannot deploy change sets in V1
13
- def deploy_change_set(*)
14
- unsupported('deploy change set')
15
- end
16
-
17
- # True if the stack exists in CFN
18
- def exists?(payload)
19
- base_module::CloudFormation::Stack.new(payload.identifier).exists?
20
- end
21
-
22
- private
23
-
24
- def base_module
25
- AWS
26
- end
27
-
28
- def failure_event_filtered?(event)
29
- !event.resource_status.include?('FAILED') ||
30
- !event.key?(:resource_status_reason)
31
- end
32
-
33
- def handle_failure(payload)
34
- reasons = []
35
- response = client.describe_stack_events(stack_name: payload.identifier)
36
-
37
- response.stack_events.each do |event|
38
- next if failure_event_filtered?(event)
39
-
40
- reasons.unshift(event.resource_status_reason)
41
- end
42
-
43
- raise "#{payload.name} stack failed:\n#{reasons.join("\n")}"
44
- end
45
-
46
- def perform_and_wait(method, payload)
47
- response = public_send(method, payload)
48
-
49
- aws_stack = nil
50
- Sleeper.new(payload.max_wait) do
51
- response = client.describe_stacks(stack_name: payload.identifier)
52
- aws_stack = response.stacks.first
53
- !aws_stack.stack_status.end_with?('IN_PROGRESS')
54
- end
55
-
56
- handle_failure(payload) if aws_stack.stack_status =~ /(FAILED|ROLLBACK)/
57
- response
58
- end
59
-
60
- def upload_object(payload, key)
61
- base_module.config(region: AwsShim::REGION)
62
- @s3 ||= base_module::S3.new
63
-
64
- objects = @s3.buckets[Humidifier.config.s3_bucket].objects
65
- objects.create(key, payload.template_body).url_for(:read)
66
- end
67
-
68
- def unsupported(method)
69
- puts "WARNING: Cannot #{method} because that functionality is not " \
70
- 'supported in V1 of aws-sdk.'
71
- false
72
- end
73
- end
74
- end
75
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module AwsAdapters
5
- # The adapter for v2 of aws-sdk
6
- class SDKV2 < Base
7
- # Format of the timestamp used in changeset naming
8
- TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
9
-
10
- # Create a change set in CFN
11
- def create_change_set(payload)
12
- change_set_name = "changeset-#{Time.now.strftime(TIME_FORMAT)}"
13
- payload.merge(change_set_name: change_set_name)
14
- try_valid { client.create_change_set(payload.create_change_set_params) }
15
- end
16
-
17
- # Create a change set if the stack exists, otherwise create the stack
18
- def deploy_change_set(payload)
19
- exists?(payload) ? create_change_set(payload) : create(payload)
20
- end
21
-
22
- # True if the stack exists in CFN
23
- def exists?(payload)
24
- base_module::CloudFormation::Stack.new(name: payload.identifier).exists?
25
- end
26
-
27
- private
28
-
29
- def base_module
30
- Aws
31
- end
32
-
33
- def perform_and_wait(method, payload)
34
- method = exists?(payload) ? :update : :create if method == :deploy
35
- response = public_send(method, payload)
36
- signal = :"stack_#{method}_complete"
37
-
38
- client.wait_until(signal, stack_name: payload.identifier) do |waiter|
39
- waiter.max_attempts = payload.max_wait / 5
40
- waiter.delay = 5
41
- end
42
-
43
- response
44
- end
45
-
46
- def upload_object(payload, key)
47
- base_module.config.update(region: AwsShim::REGION)
48
- @s3_client ||= base_module::S3::Client.new
49
-
50
- @s3_client.put_object(
51
- body: payload.template_body,
52
- bucket: Humidifier.config.s3_bucket,
53
- key: key
54
- )
55
-
56
- object = base_module::S3::Object.new(Humidifier.config.s3_bucket, key)
57
- object.presigned_url(:get)
58
- end
59
- end
60
- end
61
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module AwsAdapters
5
- # The adapter for v3 of aws-sdk
6
- class SDKV3 < SDKV2
7
- # The notice to add the `aws-sdk-s3` gem when it is needed.
8
- S3_SDK_MESSAGE = <<-MSG
9
- The AWS SDK for versions 3+ have broken out individual AWS modules into their
10
- own gems. Since the stack that you're attempting to use is large enough that it
11
- needs to be uploaded to S3, humidifier needs to load the S3 SDK. Please make
12
- sure that the 'aws-sdk-s3' gem is available in your load path.
13
- MSG
14
-
15
- private
16
-
17
- def upload_object(payload, key)
18
- raise S3_SDK_MESSAGE unless s3_sdk_loaded?
19
-
20
- super
21
- end
22
-
23
- def s3_sdk_loaded?
24
- require 'aws-sdk-s3'
25
- true
26
- rescue LoadError
27
- false
28
- end
29
- end
30
- end
31
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'humidifier/aws_adapters/base'
4
- require 'humidifier/aws_adapters/noop'
5
- require 'humidifier/aws_adapters/sdkv1'
6
- require 'humidifier/aws_adapters/sdkv2'
7
- require 'humidifier/aws_adapters/sdkv3'
8
-
9
- module Humidifier
10
- # Optionally provides aws-sdk functionality if the gem is loaded
11
- class AwsShim
12
- # The AWS region, can be set through the environment, defaults to us-east-1
13
- REGION = ENV['AWS_REGION'] || 'us-east-1'
14
-
15
- # Methods that are sent over to the aws adapter from the stack
16
- STACK_METHODS = %i[
17
- create delete deploy exists? update upload valid?
18
- create_and_wait delete_and_wait deploy_and_wait update_and_wait
19
- create_change_set deploy_change_set
20
- ].freeze
21
-
22
- attr_reader :shim
23
-
24
- # Either set the SDK based on the configured option or guess the SDK
25
- # version by attempting to require both aws-sdk-v1 and aws-sdk, then setting
26
- # the shim based on what successfully loaded
27
- def initialize
28
- @shim =
29
- if Humidifier.config.sdk_version_1?
30
- AwsAdapters::SDKV1.new
31
- elsif Humidifier.config.sdk_version_2?
32
- AwsAdapters::SDKV2.new
33
- elsif Humidifier.config.sdk_version_3?
34
- AwsAdapters::SDKV3.new
35
- else
36
- guess_sdk
37
- end
38
- end
39
-
40
- class << self
41
- extend Forwardable
42
- def_delegators :shim, *STACK_METHODS
43
-
44
- # The shim singleton
45
- def instance
46
- @instance ||= new
47
- end
48
-
49
- # The target of all of the forwarding
50
- def shim
51
- instance.shim
52
- end
53
- end
54
-
55
- private
56
-
57
- def guess_sdk
58
- try_requiring_sdks
59
-
60
- if defined?(Aws) && Aws::CORE_GEM_VERSION[0] == '3'
61
- AwsAdapters::SDKV3.new
62
- elsif defined?(Aws)
63
- AwsAdapters::SDKV2.new
64
- elsif Object.const_defined?(:AWS)
65
- AwsAdapters::SDKV1.new
66
- else
67
- AwsAdapters::Noop.new
68
- end
69
- end
70
-
71
- def try_require_sdk(name)
72
- require name || true
73
- rescue LoadError
74
- false
75
- end
76
-
77
- def try_requiring_sdks
78
- try_require_sdk('aws-sdk-cloudformation') ||
79
- try_require_sdk('aws-sdk') ||
80
- try_require_sdk('aws-sdk-v1')
81
- end
82
- end
83
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- # a container for user params
5
- class Configuration
6
- # The message that gets displayed when the stack body is too large to use
7
- # the template_body option
8
- UPLOAD_MESSAGE = <<-MSG
9
- The %<identifier>s stack's body is too large to be use the template_body option,
10
- and therefore must use the template_url option instead. You can configure
11
- Humidifier to do this automatically by setting up the s3 config on the top-level
12
- Humidifier object like so:
13
-
14
- Humidifier.configure do |config|
15
- config.s3_bucket = 'my.s3.bucket'
16
- config.s3_prefix = 'my-prefix/' # optional
17
- end
18
- MSG
19
-
20
- # If true, always upload the CloudFormation template to the configured S3
21
- # destination. A useful option if you're going to be deploying multiple
22
- # copies of a template or you just generally want a backup.
23
- attr_accessor :force_upload
24
-
25
- # The S3 bucket to which to deploy CloudFormation templates when
26
- # `always_upload` is set to true or the template is too big for a string
27
- # literal.
28
- attr_accessor :s3_bucket
29
-
30
- # An optional prefix for the stack names.
31
- attr_accessor :s3_prefix
32
-
33
- # By default, `humidifier` will attempt to determine which SDK you have
34
- # loaded. (There's not really a story for peer dependencies with bundler).
35
- # If you want to enforce a specific version (for instance if you have both
36
- # `aws-sdk-v1` and `aws-sdk` but want to use the former) you can set this
37
- # variable to `1`.
38
- attr_accessor :sdk_version
39
-
40
- def initialize(opts = {})
41
- @force_upload = opts[:force_upload]
42
- @s3_bucket = opts[:s3_bucket]
43
- @s3_prefix = opts[:s3_prefix]
44
- @sdk_version = opts[:sdk_version]
45
- end
46
-
47
- # raise an error unless the s3_bucket field is set
48
- def ensure_upload_configured!(payload)
49
- return if s3_bucket
50
-
51
- raise UPLOAD_MESSAGE.gsub('%<identifier>s', payload.identifier)
52
- end
53
-
54
- # true if the sdk_version option is set to 1 or '1'
55
- def sdk_version_1?
56
- sdk_version.to_s == '1'
57
- end
58
-
59
- # true if the sdk_version option is set to 2 or '2'
60
- def sdk_version_2?
61
- sdk_version.to_s == '2'
62
- end
63
-
64
- # true if the sdk_version option is set to 3 or '3'
65
- def sdk_version_3?
66
- sdk_version.to_s == '3'
67
- end
68
- end
69
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # Superclass for all CFN properties
6
- class Base
7
- # The list of classes that are valid beyond the normal values for each
8
- # prop
9
- WHITELIST = [Fn, Ref].freeze
10
-
11
- attr_reader :key, :name, :spec, :substructs
12
-
13
- def initialize(key, spec = {}, substructs = {})
14
- @key = key
15
- @name = Utils.underscore(key)
16
- @spec = spec
17
- after_initialize(substructs) if respond_to?(:after_initialize, true)
18
- end
19
-
20
- # the link to the AWS docs
21
- def documentation
22
- spec['Documentation']
23
- end
24
-
25
- # true if this property is required by the resource
26
- def required?
27
- spec['Required']
28
- end
29
-
30
- # CFN stack syntax
31
- def to_cf(value)
32
- [key, Serializer.dump(value)]
33
- end
34
-
35
- # the type of update that occurs when this property is updated on its
36
- # associated resource
37
- def update_type
38
- spec['UpdateType']
39
- end
40
-
41
- # true if the given value is of a type contained in the whitelist
42
- def whitelisted_value?(value)
43
- WHITELIST.any? { |clazz| value.is_a?(clazz) }
44
- end
45
- end
46
- end
47
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A boolean property
6
- class BooleanProp < Base
7
- # converts the value through `value == 'true'` unless it is valid
8
- def convert(value)
9
- if valid?(value) || !%w[true false].include?(value)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be a boolean, not a string"
13
- value == 'true'
14
- end
15
- end
16
-
17
- # true if it is a boolean
18
- def valid?(value)
19
- return true if whitelisted_value?(value)
20
-
21
- value.is_a?(TrueClass) || value.is_a?(FalseClass)
22
- end
23
- end
24
- end
25
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A double property
6
- class DoubleProp < Base
7
- # converts the value through #to_f unless it is valid
8
- def convert(value)
9
- if valid?(value) || !value.respond_to?(:to_f)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be a double"
13
- value.to_f
14
- end
15
- end
16
-
17
- # true if it is whitelisted, an Integer, or a Float
18
- def valid?(value)
19
- whitelisted_value?(value) || value.is_a?(Integer) || value.is_a?(Float)
20
- end
21
- end
22
- end
23
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # An integer property
6
- class IntegerProp < Base
7
- # converts the value through #to_i unless it is valid
8
- def convert(value)
9
- if valid?(value) || !value.respond_to?(:to_i)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be an integer"
13
- value.to_i
14
- end
15
- end
16
-
17
- # true if it is whitelisted or a Integer
18
- def valid?(value)
19
- whitelisted_value?(value) || value.is_a?(Integer)
20
- end
21
- end
22
- end
23
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A Json property
6
- class JsonProp < Base
7
- # converts the value through `Hash[value]` unless it is valid
8
- def convert(value)
9
- if valid?(value) || !convertable?(value)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be a Hash"
13
- Hash[value]
14
- end
15
- end
16
-
17
- # true if the value is whitelisted, a Hash, or an Array
18
- def valid?(value)
19
- whitelisted_value?(value) || value.is_a?(Hash)
20
- end
21
-
22
- private
23
-
24
- def convertable?(value)
25
- Hash[value]
26
- rescue ArgumentError
27
- false
28
- end
29
- end
30
- end
31
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A property that is contained in a list
6
- class ListProp < Base
7
- attr_reader :subprop
8
-
9
- # converts the value through mapping using the subprop unless it is valid
10
- def convert(list)
11
- valid?(list) ? list : list.map { |value| subprop.convert(value) }
12
- end
13
-
14
- # CFN stack syntax
15
- def to_cf(list)
16
- cf_value =
17
- if list.respond_to?(:to_cf)
18
- list.to_cf
19
- else
20
- list.map { |value| subprop.to_cf(value).last }
21
- end
22
-
23
- [key, cf_value]
24
- end
25
-
26
- # Valid if the value is whitelisted or every value in the list is valid
27
- # on the subprop
28
- def valid?(list)
29
- return true if whitelisted_value?(list)
30
-
31
- list.is_a?(Enumerable) && list.all? { |value| subprop.valid?(value) }
32
- end
33
-
34
- private
35
-
36
- # Finds the subprop that's specified in the spec
37
- def after_initialize(substructs)
38
- @subprop = Props.singular_from(key, spec, substructs)
39
- end
40
- end
41
- end
42
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A property that is contained in a Map
6
- class MapProp < Base
7
- attr_reader :subprop
8
-
9
- # converts the value through mapping using the subprop unless it is valid
10
- def convert(map)
11
- return map if valid?(map)
12
-
13
- map.map { |key, value| [key, subprop.convert(value)] }.to_h
14
- end
15
-
16
- # CFN stack syntax
17
- def to_cf(map)
18
- cf_value =
19
- if map.respond_to?(:to_cf)
20
- map.to_cf
21
- else
22
- map.each_with_object({}) do |(subkey, subvalue), serialized|
23
- serialized[subkey] = subprop.to_cf(subvalue).last
24
- end
25
- end
26
-
27
- [key, cf_value]
28
- end
29
-
30
- # Valid if the value is whitelisted or every value in the map is valid on
31
- # the subprop
32
- def valid?(map)
33
- return true if whitelisted_value?(map)
34
-
35
- map.is_a?(Hash) && map.values.all? { |value| subprop.valid?(value) }
36
- end
37
-
38
- private
39
-
40
- # Finds the subprop that's specified in the spec
41
- def after_initialize(substructs)
42
- @subprop = Props.singular_from(key, spec, substructs)
43
- end
44
- end
45
- end
46
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A string property
6
- class StringProp < Base
7
- # converts the value through #to_s unless it is valid
8
- def convert(value)
9
- if valid?(value)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be a string"
13
- value.to_s
14
- end
15
- end
16
-
17
- # true if it is whitelisted or a String
18
- def valid?(value)
19
- whitelisted_value?(value) || value.is_a?(String)
20
- end
21
- end
22
- end
23
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A structure property that references a structure from the specification
6
- class StructureProp < Base
7
- attr_reader :subprops
8
-
9
- # converts the value through mapping using the subprop unless it is valid
10
- def convert(struct)
11
- if valid?(struct)
12
- struct
13
- else
14
- struct.map do |subkey, subvalue|
15
- subkey = Utils.underscore(subkey.to_s)
16
- [subkey.to_sym, subprops[subkey].convert(subvalue)]
17
- end.to_h
18
- end
19
- end
20
-
21
- # CFN stack syntax
22
- def to_cf(struct)
23
- cf_value =
24
- if struct.respond_to?(:to_cf)
25
- struct.to_cf
26
- else
27
- struct.map do |subkey, subvalue|
28
- subprops[subkey.to_s].to_cf(subvalue)
29
- end.to_h
30
- end
31
-
32
- [key, cf_value]
33
- end
34
-
35
- # true if the value is whitelisted or Hash and all keys are valid for
36
- # their corresponding props
37
- def valid?(struct)
38
- return true if whitelisted_value?(struct)
39
-
40
- struct.is_a?(Hash) && valid_struct?(struct)
41
- end
42
-
43
- private
44
-
45
- def after_initialize(substructs)
46
- @subprops = subprops_from(substructs, spec['ItemType'] || spec['Type'])
47
- end
48
-
49
- def subprops_from(substructs, type)
50
- subprop_names = substructs.fetch(type, {}).fetch('Properties', {})
51
-
52
- subprop_names.each_with_object({}) do |(key, config), subprops|
53
- subprop =
54
- if config['ItemType'] == type
55
- self
56
- else
57
- Props.from(key, config, substructs)
58
- end
59
-
60
- subprops[Utils.underscore(key)] = subprop
61
- end
62
- end
63
-
64
- def valid_struct?(struct)
65
- struct.all? do |key, value|
66
- subprops.key?(key.to_s) && subprops[key.to_s].valid?(value)
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Humidifier
4
- module Props
5
- # A timestamp (ISO 8601) property
6
- class TimestampProp < Base
7
- # converts the value through DateTime.parse(value) unless it is valid
8
- def convert(value)
9
- if valid?(value) || !value.is_a?(String)
10
- value
11
- else
12
- puts "WARNING: Property #{name} should be a Date or Time"
13
- DateTime.parse(value)
14
- end
15
- end
16
-
17
- # true if it is whitelisted, a Time, or a Date
18
- def valid?(value)
19
- whitelisted_value?(value) || value.is_a?(Time) || value.is_a?(Date)
20
- end
21
- end
22
- end
23
- end