cfndsl 0.15.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.rubocop.yml +6 -6
- data/.travis.yml +10 -5
- data/CHANGELOG.md +721 -400
- data/Gemfile +2 -0
- data/README.md +85 -23
- data/Rakefile +28 -6
- data/TODO.md +18 -0
- data/UPGRADING.md +22 -0
- data/cfndsl.gemspec +17 -16
- data/exe/cfndsl +5 -0
- data/lib/cfndsl.rb +3 -113
- data/lib/cfndsl/aws/cloud_formation_template.rb +10 -1
- data/lib/cfndsl/aws/patches/000_CloudFormationResourceSpecification.json +51726 -0
- data/lib/cfndsl/aws/patches/000_sam.spec.json +1242 -0
- data/lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json +64 -0
- data/lib/cfndsl/aws/patches/200_Scrutinies_patch.json +86 -0
- data/lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json +25 -0
- data/lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json +20 -0
- data/lib/cfndsl/aws/patches/500_NetworkAclEntry_patch.json +16 -0
- data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Event_Events_patch.json +16 -0
- data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Location_Version_patch.json +16 -0
- data/lib/cfndsl/aws/patches/500_SSM_AssociationName_patch.json +16 -0
- data/lib/cfndsl/aws/patches/500_VPCEndpoint_patch.json +17 -0
- data/lib/cfndsl/aws/patches/510_ElasticSearch_Domain_patches.json +15 -0
- data/lib/cfndsl/aws/patches/520_ServiceDiscovery_InstanceAttributes_patch.json +16 -0
- data/lib/cfndsl/aws/patches/600_RefKinds_patch.json +3654 -0
- data/lib/cfndsl/aws/patches/700_SAM_Serverless_Function_InlineCode_patch.json +20 -0
- data/lib/cfndsl/aws/patches/800_List_types_patch.json +115 -0
- data/lib/cfndsl/aws/resource_specification.json +39955 -9293
- data/lib/cfndsl/aws/types.rb +5 -3
- data/lib/cfndsl/cfnlego.rb +34 -0
- data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.erb +0 -0
- data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.rb +3 -1
- data/lib/{cfnlego → cfndsl/cfnlego}/resource.rb +5 -8
- data/lib/cfndsl/cloudformation.rb +114 -0
- data/lib/cfndsl/conditions.rb +13 -1
- data/lib/cfndsl/creation_policy.rb +3 -1
- data/lib/cfndsl/deep_merge.rb +4 -0
- data/lib/cfndsl/external_parameters.rb +12 -13
- data/lib/cfndsl/globals.rb +51 -10
- data/lib/cfndsl/json_serialisable_object.rb +4 -2
- data/lib/cfndsl/jsonable.rb +51 -68
- data/lib/cfndsl/mappings.rb +3 -1
- data/lib/cfndsl/module.rb +18 -5
- data/lib/cfndsl/names.rb +2 -0
- data/lib/cfndsl/orchestration_template.rb +193 -73
- data/lib/cfndsl/outputs.rb +7 -1
- data/lib/cfndsl/parameters.rb +3 -1
- data/lib/cfndsl/plurals.rb +23 -10
- data/lib/cfndsl/properties.rb +3 -1
- data/lib/cfndsl/rake_task.rb +217 -15
- data/lib/cfndsl/ref_check.rb +21 -11
- data/lib/cfndsl/resources.rb +8 -19
- data/lib/cfndsl/rules.rb +46 -0
- data/lib/cfndsl/runner.rb +143 -0
- data/lib/cfndsl/specification.rb +82 -84
- data/lib/cfndsl/types.rb +212 -95
- data/lib/cfndsl/update_policy.rb +3 -1
- data/lib/cfndsl/version.rb +3 -1
- data/lib/deep_merge/core.rb +10 -6
- data/lib/deep_merge/deep_merge.rb +3 -1
- data/sample/autoscale.rb +1 -1
- data/sample/autoscale2.rb +4 -3
- data/sample/circular.rb +2 -0
- data/sample/codedeploy.rb +3 -1
- data/sample/config_service.rb +5 -3
- data/sample/ecs.rb +3 -1
- data/sample/export.rb +5 -3
- data/sample/iam_policies.rb +2 -0
- data/sample/import.rb +4 -2
- data/sample/lambda.rb +3 -1
- data/sample/s3.rb +2 -0
- data/sample/t1.rb +3 -1
- data/sample/vpc_example.rb +3 -1
- data/sample/vpc_with_vpn_example.rb +3 -1
- data/spec/aws/ec2_security_group_spec.rb +2 -0
- data/spec/aws/ecs_task_definition_spec.rb +2 -0
- data/spec/aws/iam_managed_policy_spec.rb +2 -0
- data/spec/aws/kms_alias_spec.rb +2 -0
- data/spec/aws/list_type_patches_spec.rb +35 -0
- data/spec/aws/logs_log_group_spec.rb +2 -0
- data/spec/aws/nested_arrays_spec.rb +194 -0
- data/spec/aws/rds_db_instance_spec.rb +2 -0
- data/spec/aws/serverless_spec.rb +2 -2
- data/spec/cfndsl_spec.rb +102 -63
- data/spec/cli_spec.rb +52 -49
- data/spec/cloud_formation_template_spec.rb +235 -0
- data/spec/condition_spec.rb +24 -0
- data/spec/deep_merge_spec.rb +2 -0
- data/spec/direct_ruby_spec.rb +19 -0
- data/spec/external_parameters_spec.rb +25 -20
- data/spec/fixtures/condition-assertion.json +1 -0
- data/spec/fixtures/params.json +1 -0
- data/spec/fixtures/params.yaml +2 -0
- data/spec/fixtures/params_struct1.yaml +4 -0
- data/spec/fixtures/params_struct2.yaml +4 -0
- data/spec/fixtures/rule-assertion.json +1 -0
- data/spec/fixtures/test.rb +12 -4
- data/spec/generate_spec.rb +4 -0
- data/spec/jsonable_spec.rb +2 -0
- data/spec/metadata_spec.rb +2 -0
- data/spec/names_spec.rb +2 -0
- data/spec/output_spec.rb +2 -0
- data/spec/plurals_spec.rb +2 -0
- data/spec/resource_name_spec.rb +21 -0
- data/spec/resources_spec.rb +2 -7
- data/spec/rule_spec.rb +17 -0
- data/spec/spec_helper.rb +4 -7
- data/spec/support/shared_examples/orchestration_template.rb +17 -2
- data/spec/transform_spec.rb +2 -0
- data/spec/types_definition_spec.rb +6 -7
- metadata +79 -25
- data/bin/cfndsl +0 -143
- data/lib/cfndsl/errors.rb +0 -29
- data/lib/cfndsl/os/heat_template.rb +0 -16
- data/lib/cfndsl/os/types.rb +0 -12
- data/lib/cfndsl/os/types.yaml +0 -2423
- data/lib/cfndsl/patches.rb +0 -98
- data/lib/cfnlego.rb +0 -42
- data/spec/fixtures/heattest.rb +0 -22
- data/spec/heat_template_spec.rb +0 -5
data/lib/cfndsl/aws/types.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../jsonable'
|
4
|
+
require_relative '../types'
|
2
5
|
|
3
6
|
module CfnDsl
|
4
7
|
module AWS
|
5
8
|
# Cloud Formation Types
|
6
9
|
module Types
|
7
|
-
TYPE_PREFIX = 'aws'.freeze
|
8
10
|
class Type < JSONable; end
|
9
|
-
include CfnDsl::Types
|
11
|
+
include CfnDsl::Types # This include triggers loading and patching of the global specification
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
require 'net/http'
|
6
|
+
require 'uri'
|
7
|
+
require_relative 'cfnlego/cloudformation'
|
8
|
+
require_relative 'cfnlego/resource'
|
9
|
+
require_relative 'specification'
|
10
|
+
|
11
|
+
# Cfnlego
|
12
|
+
module Cfnlego
|
13
|
+
def self.resources
|
14
|
+
@resources ||= CfnDsl::Specification.load_file.resources
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.run(options)
|
18
|
+
resources =
|
19
|
+
options[:resources].each_with_object([]) do |r, list|
|
20
|
+
/(.*),(.*)/.match(r) do |m|
|
21
|
+
type = m[1]
|
22
|
+
name = m[2]
|
23
|
+
list << Cfnlego::Resource.new(type, name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
return Cfnlego::CloudFormation.new(resources).render
|
29
|
+
rescue RuntimeError => e
|
30
|
+
warn "Error: #{e.message}"
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
File without changes
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Cfnlego
|
2
4
|
module Cfnlego
|
3
5
|
# CloudFormation
|
4
6
|
class CloudFormation
|
5
|
-
TEMPLATE = "#{File.dirname(__FILE__)}/cloudformation.erb"
|
7
|
+
TEMPLATE = "#{File.dirname(__FILE__)}/cloudformation.erb"
|
6
8
|
|
7
9
|
attr_reader :resources
|
8
10
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
require 'net/http'
|
3
5
|
require 'uri'
|
@@ -24,14 +26,9 @@ module Cfnlego
|
|
24
26
|
private
|
25
27
|
|
26
28
|
def definition
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
begin
|
31
|
-
@definition ||= data[@type]
|
32
|
-
rescue
|
33
|
-
raise "unknown #{@type}, no matching definition found"
|
34
|
-
end
|
29
|
+
@definition ||= Cfnlego.resources[@type]
|
30
|
+
rescue StandardError
|
31
|
+
raise "unknown #{@type}, no matching definition found"
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'globals'
|
4
|
+
require_relative 'external_parameters'
|
5
|
+
require_relative 'aws/cloud_formation_template'
|
6
|
+
|
7
|
+
# CfnDsl
|
8
|
+
module CfnDsl
|
9
|
+
# This function handles the eval of the template file and returns the
|
10
|
+
# results. It does this with a ruby "eval", but it builds up a customized
|
11
|
+
# binding environment before it calls eval. The environment can be
|
12
|
+
# customized by passing a list of customizations in the extras parameter.
|
13
|
+
#
|
14
|
+
# These customizations are expressed as an array of pairs of
|
15
|
+
# (type,filename). They are evaluated in the order they appear in the
|
16
|
+
# extras array. The types are as follows
|
17
|
+
#
|
18
|
+
# :yaml - the second element is treated as a file name, which is loaded
|
19
|
+
# as a yaml file. The yaml file should contain a top level
|
20
|
+
# dictionary. Each of the keys of the top level dictionary is
|
21
|
+
# used as a local variable in the evalation context.
|
22
|
+
#
|
23
|
+
# :json - the second element is treated as a file name, which is loaded
|
24
|
+
# as a json file. The yaml file should contain a top level
|
25
|
+
# dictionary. Each of the keys of the top level dictionary is
|
26
|
+
# used as a local variable in the evalation context.
|
27
|
+
#
|
28
|
+
# :raw - the second element is treated as a ruby statement and is
|
29
|
+
# evaluated in the binding context, similar to the contents of
|
30
|
+
# a ruby file.
|
31
|
+
#
|
32
|
+
# Note that the order is important, as later extra sections can overwrite
|
33
|
+
# or even undo things that were done by earlier sections.
|
34
|
+
|
35
|
+
def self.eval_file_with_extras(filename, extras = [], logstream = nil)
|
36
|
+
b = binding
|
37
|
+
params = CfnDsl::ExternalParameters.refresh!
|
38
|
+
extras.each do |type, file|
|
39
|
+
case type
|
40
|
+
when :yaml, :json
|
41
|
+
klass_name = type.to_s.upcase
|
42
|
+
logstream.puts("Loading #{klass_name} file #{file}") if logstream
|
43
|
+
params.load_file file
|
44
|
+
when :raw
|
45
|
+
file_parts = file.split('=')
|
46
|
+
if file_parts[1].downcase == 'true'
|
47
|
+
params.set_param(file_parts[0], true)
|
48
|
+
elsif file_parts[1].downcase == 'false'
|
49
|
+
params.set_param(file_parts[0], false)
|
50
|
+
else
|
51
|
+
params.set_param(*file.split('='))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
logstream.puts("Loading template file #{filename}") if logstream
|
57
|
+
b.eval(File.read(filename), filename)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Syntactic Sugar for embedded ruby usage
|
61
|
+
# @example
|
62
|
+
# require `cfndsl`
|
63
|
+
#
|
64
|
+
# class Builder
|
65
|
+
# include CfnDsl::CloudFormation
|
66
|
+
#
|
67
|
+
# def build_template()
|
68
|
+
# template = CloudFormation('ANewTemplate')
|
69
|
+
# a_param = template.Parameter('AParam')
|
70
|
+
# a_param.Type('String')
|
71
|
+
# return template
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# def map_instance_type(instance_type)
|
75
|
+
# # logic to auto convert instance types to latest available etc..
|
76
|
+
# FnFindInMap("InstanceTypeConversion",Ref('AWS::Region'),instance_type)
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# # templates can be passed around to other methods/classes
|
80
|
+
# def add_instance(template, instance_type)
|
81
|
+
# ec2 = template.EC2_Instance(:myInstance)
|
82
|
+
# ec2.InstanceType(map_instance_type(instance_type))
|
83
|
+
# # Alteratively with DSL block syntax
|
84
|
+
# this = self # declare block is eval for the model instance so need to keep a reference
|
85
|
+
# template.declare do
|
86
|
+
# EC2_Instance(:myInstance) do
|
87
|
+
# InstanceType this.map_instance_type(instance_type)
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# def generate(template,pretty: false)
|
93
|
+
# valid = template.validate
|
94
|
+
# pretty ? valid.to_json : JSON.pretty_generate(valid)
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
module CloudFormation
|
99
|
+
# Include all the fun JSONABLE stuff and Fn* functions so you can use them
|
100
|
+
# in local methods
|
101
|
+
include Functions
|
102
|
+
|
103
|
+
def CloudFormation(description = nil, &block)
|
104
|
+
CloudFormationTemplate.new(description, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Main function to build and validate
|
110
|
+
# @return [CfnDsl::CloudFormationTemplate]
|
111
|
+
# @raise [CfnDsl::Error] if the block does not generate a valid template
|
112
|
+
def CloudFormation(description = nil, &block)
|
113
|
+
CfnDsl::CloudFormationTemplate.new(description).declare(&block).validate
|
114
|
+
end
|
data/lib/cfndsl/conditions.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'jsonable'
|
2
4
|
|
3
5
|
module CfnDsl
|
4
6
|
# Handles condition objects
|
@@ -11,5 +13,15 @@ module CfnDsl
|
|
11
13
|
def initialize(value)
|
12
14
|
@value = value
|
13
15
|
end
|
16
|
+
|
17
|
+
# For when Condition is used inside Fn::And, Fn::Or, Fn::Not
|
18
|
+
def condition_refs
|
19
|
+
case @value
|
20
|
+
when String, Symbol
|
21
|
+
[@value.to_s]
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
end
|
14
26
|
end
|
15
27
|
end
|
@@ -1,3 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'json'
|
5
|
+
require_relative 'deep_merge'
|
6
|
+
|
1
7
|
module CfnDsl
|
2
8
|
# Handles all external parameters
|
3
9
|
class ExternalParameters
|
@@ -27,16 +33,16 @@ module CfnDsl
|
|
27
33
|
@parameters = self.class.defaults.clone
|
28
34
|
end
|
29
35
|
|
30
|
-
def set_param(
|
31
|
-
parameters[
|
36
|
+
def set_param(key, val)
|
37
|
+
parameters[key.to_sym] = val
|
32
38
|
end
|
33
39
|
|
34
|
-
def merge_param(
|
35
|
-
parameters.deep_merge!(
|
40
|
+
def merge_param(xray)
|
41
|
+
parameters.deep_merge!(xray)
|
36
42
|
end
|
37
43
|
|
38
|
-
def get_param(
|
39
|
-
parameters[
|
44
|
+
def get_param(key)
|
45
|
+
parameters[key.to_sym]
|
40
46
|
end
|
41
47
|
alias [] get_param
|
42
48
|
|
@@ -44,13 +50,6 @@ module CfnDsl
|
|
44
50
|
parameters
|
45
51
|
end
|
46
52
|
|
47
|
-
def add_to_binding(bind, logstream)
|
48
|
-
parameters.each_pair do |key, val|
|
49
|
-
logstream.puts("Setting local variable #{key} to #{val}") if logstream
|
50
|
-
bind.eval "#{key} = #{val.inspect}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
53
|
def load_file(fname)
|
55
54
|
format = File.extname fname
|
56
55
|
case format
|
data/lib/cfndsl/globals.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'version'
|
4
|
+
|
1
5
|
# Global variables to adjust CfnDsl behavior
|
2
6
|
module CfnDsl
|
3
|
-
|
4
|
-
|
5
|
-
def disable_binding
|
6
|
-
@disable_binding = true
|
7
|
+
class Error < StandardError
|
7
8
|
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
module_function
|
11
|
+
|
12
|
+
AWS_SPECIFICATION_URL = 'https://d1uauaxba7bl26.cloudfront.net/%<version>s/gzip/CloudFormationResourceSpecification.json'
|
13
|
+
LOCAL_SPEC_FILE = File.expand_path('aws/resource_specification.json', __dir__)
|
12
14
|
|
13
15
|
def disable_deep_merge
|
14
16
|
@disable_deep_merge = true
|
@@ -18,13 +20,52 @@ module CfnDsl
|
|
18
20
|
@disable_deep_merge
|
19
21
|
end
|
20
22
|
|
23
|
+
def specification_file=(file)
|
24
|
+
raise Error, "Specification #{file} does not exist" unless File.exist?(file)
|
25
|
+
|
26
|
+
@specification_file = file
|
27
|
+
end
|
28
|
+
|
29
|
+
# @overload specification_file()
|
30
|
+
# @return [String] the specification file name
|
31
|
+
# @overload specification_file(file)
|
32
|
+
# @deprecated Use specification_file= to override the specification file
|
21
33
|
def specification_file(file = nil)
|
22
|
-
|
23
|
-
@specification_file ||=
|
34
|
+
self.specification_file = file if file
|
35
|
+
@specification_file ||= user_specification_file
|
36
|
+
@specification_file = LOCAL_SPEC_FILE unless File.exist?(@specification_file)
|
24
37
|
@specification_file
|
25
38
|
end
|
26
39
|
|
40
|
+
def user_specification_file
|
41
|
+
File.join(ENV['HOME'], '.cfndsl/resource_specification.json')
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_specification_file(file: user_specification_file, version: nil)
|
45
|
+
require 'open-uri'
|
46
|
+
version ||= 'latest'
|
47
|
+
FileUtils.mkdir_p File.dirname(file)
|
48
|
+
url = format(AWS_SPECIFICATION_URL, version: version)
|
49
|
+
content = URI.parse(url).open.read
|
50
|
+
version = JSON.parse(content)['ResourceSpecificationVersion'] if version == 'latest'
|
51
|
+
File.open(file, 'w') { |f| f.puts content }
|
52
|
+
{ file: file, version: version, url: url }
|
53
|
+
rescue StandardError
|
54
|
+
raise "Failed updating specification file #{file} from #{url}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def additional_specs(*specs)
|
58
|
+
@additional_specs ||= Dir[File.expand_path('aws/patches/*.spec.json', __dir__)]
|
59
|
+
@additional_specs.concat(specs.flatten)
|
60
|
+
end
|
61
|
+
|
62
|
+
def specification_patches(*patches)
|
63
|
+
# TODO: This is not capturing all the files in patches dir!
|
64
|
+
@patches ||= Dir[File.expand_path('aws/patches/*patch.json', __dir__)]
|
65
|
+
@patches.concat(patches.flatten)
|
66
|
+
end
|
67
|
+
|
27
68
|
def reserved_items
|
28
|
-
%w[Resource Parameter Output].freeze
|
69
|
+
%w[Resource Rule Parameter Output].freeze
|
29
70
|
end
|
30
71
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CfnDsl
|
2
4
|
# JSONSerialisableObject
|
3
5
|
module JSONSerialisableObject
|
@@ -5,8 +7,8 @@ module CfnDsl
|
|
5
7
|
@value
|
6
8
|
end
|
7
9
|
|
8
|
-
def to_json(*
|
9
|
-
as_json.to_json(*
|
10
|
+
def to_json(*args)
|
11
|
+
as_json.to_json(*args)
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
data/lib/cfndsl/jsonable.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ref_check'
|
4
|
+
require_relative 'json_serialisable_object'
|
5
|
+
require_relative 'external_parameters'
|
4
6
|
|
5
7
|
module CfnDsl
|
6
8
|
# These functions are available anywhere inside
|
@@ -23,7 +25,7 @@ module CfnDsl
|
|
23
25
|
|
24
26
|
# Equivalent to the CloudFormation template built in function Fn::GetAtt
|
25
27
|
def FnGetAtt(logical_resource, attribute)
|
26
|
-
Fn.new('GetAtt', [logical_resource, attribute])
|
28
|
+
Fn.new('GetAtt', [logical_resource, attribute], [logical_resource])
|
27
29
|
end
|
28
30
|
|
29
31
|
# Equivalent to the CloudFormation template built in function Fn::GetAZs
|
@@ -43,9 +45,8 @@ module CfnDsl
|
|
43
45
|
|
44
46
|
# Equivalent to the CloudFormation template built in function Fn::And
|
45
47
|
def FnAnd(array)
|
46
|
-
if !array || array.count < 2 || array.count > 10
|
47
|
-
|
48
|
-
end
|
48
|
+
raise 'The array passed to Fn::And must have at least 2 elements and no more than 10' if !array || array.count < 2 || array.count > 10
|
49
|
+
|
49
50
|
Fn.new('And', array)
|
50
51
|
end
|
51
52
|
|
@@ -56,7 +57,7 @@ module CfnDsl
|
|
56
57
|
|
57
58
|
# Equivalent to the Cloudformation template built in function Fn::If
|
58
59
|
def FnIf(condition_name, true_value, false_value)
|
59
|
-
Fn.new('If', [condition_name, true_value, false_value])
|
60
|
+
Fn.new('If', [condition_name, true_value, false_value], [], [condition_name])
|
60
61
|
end
|
61
62
|
|
62
63
|
# Equivalent to the Cloudformation template built in function Fn::Not
|
@@ -70,9 +71,8 @@ module CfnDsl
|
|
70
71
|
|
71
72
|
# Equivalent to the CloudFormation template built in function Fn::Or
|
72
73
|
def FnOr(array)
|
73
|
-
if !array || array.count < 2 || array.count > 10
|
74
|
-
|
75
|
-
end
|
74
|
+
raise 'The array passed to Fn::Or must have at least 2 elements and no more than 10' if !array || array.count < 2 || array.count > 10
|
75
|
+
|
76
76
|
Fn.new('Or', array)
|
77
77
|
end
|
78
78
|
|
@@ -82,13 +82,20 @@ module CfnDsl
|
|
82
82
|
end
|
83
83
|
|
84
84
|
# Equivalent to the CloudFormation template built in function Fn::Sub
|
85
|
+
FN_SUB_SCANNER = /\$\{([^!}]*)\}/.freeze
|
86
|
+
|
85
87
|
def FnSub(string, substitutions = nil)
|
86
88
|
raise ArgumentError, 'The first argument passed to Fn::Sub must be a string' unless string.is_a? String
|
89
|
+
|
90
|
+
refs = string.scan(FN_SUB_SCANNER).map(&:first)
|
91
|
+
|
87
92
|
if substitutions
|
88
93
|
raise ArgumentError, 'The second argument passed to Fn::Sub must be a Hash' unless substitutions.is_a? Hash
|
89
|
-
|
94
|
+
|
95
|
+
refs -= substitutions.keys.map(&:to_s)
|
96
|
+
Fn.new('Sub', [string, substitutions], refs)
|
90
97
|
else
|
91
|
-
Fn.new('Sub', string)
|
98
|
+
Fn.new('Sub', string, refs)
|
92
99
|
end
|
93
100
|
end
|
94
101
|
|
@@ -97,55 +104,11 @@ module CfnDsl
|
|
97
104
|
Fn.new('ImportValue', value)
|
98
105
|
end
|
99
106
|
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
# or
|
104
|
-
# FnFormat('This is a %{test}. It is 100%% %{effective}',
|
105
|
-
# :test => 'test",
|
106
|
-
# :effective => 'effective')
|
107
|
-
#
|
108
|
-
# These will each generate a call to Fn::Join that when
|
109
|
-
# evaluated will produce the string "This is a test. It is 100%
|
110
|
-
# effective."
|
111
|
-
#
|
112
|
-
# Think of this as %0, %1, etc in the format string being replaced by the
|
113
|
-
# corresponding arguments given after the format string. '%%' is replaced
|
114
|
-
# by the '%' character.
|
115
|
-
#
|
116
|
-
# The actual Fn::Join call corresponding to the above FnFormat call would be
|
117
|
-
# {"Fn::Join": ["",["This is a ","test",". It is 100","%"," ","effective"]]}
|
118
|
-
#
|
119
|
-
# If no arguments are given, or if a hash is given and the format
|
120
|
-
# variable name does not exist in the hash, it is used as a Ref
|
121
|
-
# to an existing resource or parameter.
|
122
|
-
#
|
123
|
-
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
124
|
-
def FnFormat(string, *arguments)
|
125
|
-
warn '`FnFormat` is deprecated and will be removed a future release. Use `FnSub` instead'
|
126
|
-
array = []
|
127
|
-
|
128
|
-
if arguments.empty? || (arguments.length == 1 && arguments[0].instance_of?(Hash))
|
129
|
-
hash = arguments[0] || {}
|
130
|
-
string.scan(/(.*?)(?:%(%|\{([\w:]+)\})|\z)/m) do |x, y, z|
|
131
|
-
array.push x if x && !x.empty?
|
132
|
-
|
133
|
-
next unless y
|
134
|
-
|
135
|
-
array.push(y == '%' ? '%' : (hash[z] || hash[z.to_sym] || Ref(z)))
|
136
|
-
end
|
137
|
-
else
|
138
|
-
string.scan(/(.*?)(?:%(%|\d+)|\z)/m) do |x, y|
|
139
|
-
array.push x if x && !x.empty?
|
140
|
-
|
141
|
-
next unless y
|
142
|
-
|
143
|
-
array.push(y == '%' ? '%' : arguments[y.to_i])
|
144
|
-
end
|
145
|
-
end
|
146
|
-
Fn.new('Join', ['', array])
|
107
|
+
# Equivalent to the CloudFormation template built in function Fn::Cidr
|
108
|
+
def FnCidr(ipblock, count, sizemask)
|
109
|
+
Fn.new('Cidr', [ipblock, count, sizemask])
|
147
110
|
end
|
148
|
-
# rubocop:enable
|
111
|
+
# rubocop:enable
|
149
112
|
end
|
150
113
|
|
151
114
|
# This is the base class for just about everything useful in the
|
@@ -170,6 +133,7 @@ module CfnDsl
|
|
170
133
|
# Instance variables that begin with two underscores have one of
|
171
134
|
# them removed.
|
172
135
|
def as_json(_options = {})
|
136
|
+
check_names
|
173
137
|
hash = {}
|
174
138
|
instance_variables.each do |var|
|
175
139
|
name = var[1..-1]
|
@@ -187,8 +151,8 @@ module CfnDsl
|
|
187
151
|
hash
|
188
152
|
end
|
189
153
|
|
190
|
-
def to_json(*
|
191
|
-
as_json.to_json(*
|
154
|
+
def to_json(*args)
|
155
|
+
as_json.to_json(*args)
|
192
156
|
end
|
193
157
|
|
194
158
|
def ref_children
|
@@ -197,15 +161,30 @@ module CfnDsl
|
|
197
161
|
|
198
162
|
def declare(&block)
|
199
163
|
instance_eval(&block) if block_given?
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def check_names
|
170
|
+
return if instance_variable_get('@Resources').nil?
|
171
|
+
|
172
|
+
instance_variable_get('@Resources').keys.each do |name|
|
173
|
+
next unless name !~ /\A\p{Alnum}+\z/
|
174
|
+
|
175
|
+
warn "Resource name: #{name} is invalid"
|
176
|
+
exit 1
|
177
|
+
end
|
200
178
|
end
|
201
179
|
end
|
202
180
|
|
203
181
|
# Handles all of the Fn:: objects
|
204
182
|
class Fn < JSONable
|
205
|
-
def initialize(function, argument, refs = [])
|
183
|
+
def initialize(function, argument, refs = [], condition_refs = [])
|
206
184
|
@function = function
|
207
185
|
@argument = argument
|
208
186
|
@_refs = refs
|
187
|
+
@_condition_refs = condition_refs
|
209
188
|
end
|
210
189
|
|
211
190
|
def as_json(_options = {})
|
@@ -214,16 +193,20 @@ module CfnDsl
|
|
214
193
|
hash
|
215
194
|
end
|
216
195
|
|
217
|
-
def to_json(*
|
218
|
-
as_json.to_json(*
|
196
|
+
def to_json(*args)
|
197
|
+
as_json.to_json(*args)
|
219
198
|
end
|
220
199
|
|
221
|
-
def
|
200
|
+
def all_refs
|
222
201
|
@_refs
|
223
202
|
end
|
224
203
|
|
204
|
+
def condition_refs
|
205
|
+
@_condition_refs
|
206
|
+
end
|
207
|
+
|
225
208
|
def ref_children
|
226
|
-
[@argument]
|
209
|
+
[@argument].flatten
|
227
210
|
end
|
228
211
|
end
|
229
212
|
|