humidifier 2.15.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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