cfndsl 0.15.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +6 -6
  4. data/.travis.yml +10 -5
  5. data/CHANGELOG.md +721 -400
  6. data/Gemfile +2 -0
  7. data/README.md +85 -23
  8. data/Rakefile +28 -6
  9. data/TODO.md +18 -0
  10. data/UPGRADING.md +22 -0
  11. data/cfndsl.gemspec +17 -16
  12. data/exe/cfndsl +5 -0
  13. data/lib/cfndsl.rb +3 -113
  14. data/lib/cfndsl/aws/cloud_formation_template.rb +10 -1
  15. data/lib/cfndsl/aws/patches/000_CloudFormationResourceSpecification.json +51726 -0
  16. data/lib/cfndsl/aws/patches/000_sam.spec.json +1242 -0
  17. data/lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json +64 -0
  18. data/lib/cfndsl/aws/patches/200_Scrutinies_patch.json +86 -0
  19. data/lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json +25 -0
  20. data/lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json +20 -0
  21. data/lib/cfndsl/aws/patches/500_NetworkAclEntry_patch.json +16 -0
  22. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Event_Events_patch.json +16 -0
  23. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Location_Version_patch.json +16 -0
  24. data/lib/cfndsl/aws/patches/500_SSM_AssociationName_patch.json +16 -0
  25. data/lib/cfndsl/aws/patches/500_VPCEndpoint_patch.json +17 -0
  26. data/lib/cfndsl/aws/patches/510_ElasticSearch_Domain_patches.json +15 -0
  27. data/lib/cfndsl/aws/patches/520_ServiceDiscovery_InstanceAttributes_patch.json +16 -0
  28. data/lib/cfndsl/aws/patches/600_RefKinds_patch.json +3654 -0
  29. data/lib/cfndsl/aws/patches/700_SAM_Serverless_Function_InlineCode_patch.json +20 -0
  30. data/lib/cfndsl/aws/patches/800_List_types_patch.json +115 -0
  31. data/lib/cfndsl/aws/resource_specification.json +39955 -9293
  32. data/lib/cfndsl/aws/types.rb +5 -3
  33. data/lib/cfndsl/cfnlego.rb +34 -0
  34. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.erb +0 -0
  35. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.rb +3 -1
  36. data/lib/{cfnlego → cfndsl/cfnlego}/resource.rb +5 -8
  37. data/lib/cfndsl/cloudformation.rb +114 -0
  38. data/lib/cfndsl/conditions.rb +13 -1
  39. data/lib/cfndsl/creation_policy.rb +3 -1
  40. data/lib/cfndsl/deep_merge.rb +4 -0
  41. data/lib/cfndsl/external_parameters.rb +12 -13
  42. data/lib/cfndsl/globals.rb +51 -10
  43. data/lib/cfndsl/json_serialisable_object.rb +4 -2
  44. data/lib/cfndsl/jsonable.rb +51 -68
  45. data/lib/cfndsl/mappings.rb +3 -1
  46. data/lib/cfndsl/module.rb +18 -5
  47. data/lib/cfndsl/names.rb +2 -0
  48. data/lib/cfndsl/orchestration_template.rb +193 -73
  49. data/lib/cfndsl/outputs.rb +7 -1
  50. data/lib/cfndsl/parameters.rb +3 -1
  51. data/lib/cfndsl/plurals.rb +23 -10
  52. data/lib/cfndsl/properties.rb +3 -1
  53. data/lib/cfndsl/rake_task.rb +217 -15
  54. data/lib/cfndsl/ref_check.rb +21 -11
  55. data/lib/cfndsl/resources.rb +8 -19
  56. data/lib/cfndsl/rules.rb +46 -0
  57. data/lib/cfndsl/runner.rb +143 -0
  58. data/lib/cfndsl/specification.rb +82 -84
  59. data/lib/cfndsl/types.rb +212 -95
  60. data/lib/cfndsl/update_policy.rb +3 -1
  61. data/lib/cfndsl/version.rb +3 -1
  62. data/lib/deep_merge/core.rb +10 -6
  63. data/lib/deep_merge/deep_merge.rb +3 -1
  64. data/sample/autoscale.rb +1 -1
  65. data/sample/autoscale2.rb +4 -3
  66. data/sample/circular.rb +2 -0
  67. data/sample/codedeploy.rb +3 -1
  68. data/sample/config_service.rb +5 -3
  69. data/sample/ecs.rb +3 -1
  70. data/sample/export.rb +5 -3
  71. data/sample/iam_policies.rb +2 -0
  72. data/sample/import.rb +4 -2
  73. data/sample/lambda.rb +3 -1
  74. data/sample/s3.rb +2 -0
  75. data/sample/t1.rb +3 -1
  76. data/sample/vpc_example.rb +3 -1
  77. data/sample/vpc_with_vpn_example.rb +3 -1
  78. data/spec/aws/ec2_security_group_spec.rb +2 -0
  79. data/spec/aws/ecs_task_definition_spec.rb +2 -0
  80. data/spec/aws/iam_managed_policy_spec.rb +2 -0
  81. data/spec/aws/kms_alias_spec.rb +2 -0
  82. data/spec/aws/list_type_patches_spec.rb +35 -0
  83. data/spec/aws/logs_log_group_spec.rb +2 -0
  84. data/spec/aws/nested_arrays_spec.rb +194 -0
  85. data/spec/aws/rds_db_instance_spec.rb +2 -0
  86. data/spec/aws/serverless_spec.rb +2 -2
  87. data/spec/cfndsl_spec.rb +102 -63
  88. data/spec/cli_spec.rb +52 -49
  89. data/spec/cloud_formation_template_spec.rb +235 -0
  90. data/spec/condition_spec.rb +24 -0
  91. data/spec/deep_merge_spec.rb +2 -0
  92. data/spec/direct_ruby_spec.rb +19 -0
  93. data/spec/external_parameters_spec.rb +25 -20
  94. data/spec/fixtures/condition-assertion.json +1 -0
  95. data/spec/fixtures/params.json +1 -0
  96. data/spec/fixtures/params.yaml +2 -0
  97. data/spec/fixtures/params_struct1.yaml +4 -0
  98. data/spec/fixtures/params_struct2.yaml +4 -0
  99. data/spec/fixtures/rule-assertion.json +1 -0
  100. data/spec/fixtures/test.rb +12 -4
  101. data/spec/generate_spec.rb +4 -0
  102. data/spec/jsonable_spec.rb +2 -0
  103. data/spec/metadata_spec.rb +2 -0
  104. data/spec/names_spec.rb +2 -0
  105. data/spec/output_spec.rb +2 -0
  106. data/spec/plurals_spec.rb +2 -0
  107. data/spec/resource_name_spec.rb +21 -0
  108. data/spec/resources_spec.rb +2 -7
  109. data/spec/rule_spec.rb +17 -0
  110. data/spec/spec_helper.rb +4 -7
  111. data/spec/support/shared_examples/orchestration_template.rb +17 -2
  112. data/spec/transform_spec.rb +2 -0
  113. data/spec/types_definition_spec.rb +6 -7
  114. metadata +79 -25
  115. data/bin/cfndsl +0 -143
  116. data/lib/cfndsl/errors.rb +0 -29
  117. data/lib/cfndsl/os/heat_template.rb +0 -16
  118. data/lib/cfndsl/os/types.rb +0 -12
  119. data/lib/cfndsl/os/types.yaml +0 -2423
  120. data/lib/cfndsl/patches.rb +0 -98
  121. data/lib/cfnlego.rb +0 -42
  122. data/spec/fixtures/heattest.rb +0 -22
  123. data/spec/heat_template_spec.rb +0 -5
@@ -1,12 +1,14 @@
1
- require 'cfndsl/types'
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".freeze
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
- content = Cfnlego.fetch_resource_content
28
- datainput = JSON.parse(content)
29
- data = datainput['ResourceTypes']
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
@@ -1,4 +1,6 @@
1
- require 'cfndsl/jsonable'
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,4 +1,6 @@
1
- require 'cfndsl/jsonable'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jsonable'
2
4
 
3
5
  module CfnDsl
4
6
  # Handles creation policy objects for Resources
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'globals'
4
+ require_relative '../deep_merge/deep_merge' unless CfnDsl.disable_deep_merge?
@@ -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(k, v)
31
- parameters[k.to_sym] = v
36
+ def set_param(key, val)
37
+ parameters[key.to_sym] = val
32
38
  end
33
39
 
34
- def merge_param(x)
35
- parameters.deep_merge!(x)
40
+ def merge_param(xray)
41
+ parameters.deep_merge!(xray)
36
42
  end
37
43
 
38
- def get_param(k)
39
- parameters[k.to_sym]
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
@@ -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
- module_function
4
-
5
- def disable_binding
6
- @disable_binding = true
7
+ class Error < StandardError
7
8
  end
8
9
 
9
- def disable_binding?
10
- @disable_binding
11
- end
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
- @specification_file = file if file
23
- @specification_file ||= File.join(ENV['HOME'], '.cfndsl/resource_specification.json')
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(*a)
9
- as_json.to_json(*a)
10
+ def to_json(*args)
11
+ as_json.to_json(*args)
10
12
  end
11
13
  end
12
14
  end
@@ -1,6 +1,8 @@
1
- require 'cfndsl/errors'
2
- require 'cfndsl/ref_check'
3
- require 'cfndsl/json_serialisable_object'
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
- raise 'The array passed to Fn::And must have at least 2 elements and no more than 10'
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
- raise 'The array passed to Fn::Or must have at least 2 elements and no more than 10'
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
- Fn.new('Sub', [string, substitutions])
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
- # DEPRECATED
101
- # Usage
102
- # FnFormat('This is a %0. It is 100%% %1', 'test', 'effective')
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 Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
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(*a)
191
- as_json.to_json(*a)
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(*a)
218
- as_json.to_json(*a)
196
+ def to_json(*args)
197
+ as_json.to_json(*args)
219
198
  end
220
199
 
221
- def references
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