humidifier 2.15.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CloudFormationResourceSpecification.json +24305 -18109
- data/LICENSE +1 -1
- data/README.md +55 -33
- data/lib/humidifier/condition.rb +0 -2
- data/lib/humidifier/config.rb +46 -0
- data/lib/humidifier/fn.rb +7 -8
- data/lib/humidifier/loader.rb +37 -45
- data/lib/humidifier/mapping.rb +0 -2
- data/lib/humidifier/output.rb +4 -6
- data/lib/humidifier/parameter.rb +9 -10
- data/lib/humidifier/props.rb +177 -2
- data/lib/humidifier/resource.rb +13 -14
- data/lib/humidifier/stack.rb +166 -21
- data/lib/humidifier/version.rb +1 -1
- data/lib/humidifier.rb +31 -24
- metadata +67 -31
- data/lib/humidifier/aws_adapters/base.rb +0 -67
- data/lib/humidifier/aws_adapters/noop.rb +0 -25
- data/lib/humidifier/aws_adapters/sdkv1.rb +0 -75
- data/lib/humidifier/aws_adapters/sdkv2.rb +0 -61
- data/lib/humidifier/aws_adapters/sdkv3.rb +0 -31
- data/lib/humidifier/aws_shim.rb +0 -83
- data/lib/humidifier/configuration.rb +0 -69
- data/lib/humidifier/props/base.rb +0 -47
- data/lib/humidifier/props/boolean_prop.rb +0 -25
- data/lib/humidifier/props/double_prop.rb +0 -23
- data/lib/humidifier/props/integer_prop.rb +0 -23
- data/lib/humidifier/props/json_prop.rb +0 -31
- data/lib/humidifier/props/list_prop.rb +0 -42
- data/lib/humidifier/props/map_prop.rb +0 -46
- data/lib/humidifier/props/string_prop.rb +0 -23
- data/lib/humidifier/props/structure_prop.rb +0 -71
- data/lib/humidifier/props/timestamp_prop.rb +0 -23
- data/lib/humidifier/sdk_payload.rb +0 -122
- data/lib/humidifier/sleeper.rb +0 -25
- 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
|
data/lib/humidifier/aws_shim.rb
DELETED
@@ -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
|