cfndsl 0.17.5 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +4 -4
  4. data/.travis.yml +6 -1
  5. data/CHANGELOG.md +32 -34
  6. data/README.md +68 -21
  7. data/Rakefile +25 -3
  8. data/TODO.md +18 -0
  9. data/UPGRADING.md +22 -0
  10. data/cfndsl.gemspec +3 -3
  11. data/exe/cfndsl +5 -0
  12. data/lib/cfndsl.rb +2 -116
  13. data/lib/cfndsl/aws/cloud_formation_template.rb +8 -1
  14. data/lib/cfndsl/aws/patches/000_sam.spec.json +574 -0
  15. data/lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json +64 -0
  16. data/lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json +25 -0
  17. data/lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json +20 -0
  18. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Event_Events_patch.json +16 -0
  19. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Location_Version_patch.json +16 -0
  20. data/lib/cfndsl/aws/patches/500_SSM_AssociationName_patch.json +16 -0
  21. data/lib/cfndsl/aws/patches/500_VPCEndpoint_patch.json +17 -0
  22. data/lib/cfndsl/aws/patches/510_ElasticSearch_Domain_patches.json +15 -0
  23. data/lib/cfndsl/aws/patches/600_RefKinds_patch.json +3654 -0
  24. data/lib/cfndsl/aws/patches/700_SAM_Serverless_Function_InlineCode_patch.json +20 -0
  25. data/lib/cfndsl/aws/patches/800_List_types_patch.json +115 -0
  26. data/lib/cfndsl/aws/resource_specification.json +35809 -11627
  27. data/lib/cfndsl/aws/types.rb +3 -3
  28. data/lib/cfndsl/cfnlego.rb +34 -0
  29. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.erb +0 -0
  30. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.rb +0 -0
  31. data/lib/{cfnlego → cfndsl/cfnlego}/resource.rb +3 -8
  32. data/lib/cfndsl/cloudformation.rb +107 -0
  33. data/lib/cfndsl/conditions.rb +11 -1
  34. data/lib/cfndsl/creation_policy.rb +1 -1
  35. data/lib/cfndsl/deep_merge.rb +4 -0
  36. data/lib/cfndsl/external_parameters.rb +4 -13
  37. data/lib/cfndsl/globals.rb +48 -9
  38. data/lib/cfndsl/jsonable.rb +22 -60
  39. data/lib/cfndsl/mappings.rb +1 -1
  40. data/lib/cfndsl/module.rb +16 -5
  41. data/lib/cfndsl/orchestration_template.rb +185 -83
  42. data/lib/cfndsl/outputs.rb +5 -1
  43. data/lib/cfndsl/parameters.rb +1 -1
  44. data/lib/cfndsl/plurals.rb +12 -1
  45. data/lib/cfndsl/properties.rb +1 -1
  46. data/lib/cfndsl/rake_task.rb +206 -12
  47. data/lib/cfndsl/ref_check.rb +19 -11
  48. data/lib/cfndsl/resources.rb +6 -19
  49. data/lib/cfndsl/rules.rb +1 -1
  50. data/lib/cfndsl/runner.rb +143 -0
  51. data/lib/cfndsl/specification.rb +80 -95
  52. data/lib/cfndsl/types.rb +205 -91
  53. data/lib/cfndsl/update_policy.rb +1 -1
  54. data/lib/cfndsl/version.rb +1 -1
  55. data/sample/autoscale.rb +0 -1
  56. data/sample/autoscale2.rb +0 -1
  57. data/sample/config_service.rb +2 -2
  58. data/sample/t1.rb +1 -1
  59. data/sample/vpc_example.rb +1 -1
  60. data/sample/vpc_with_vpn_example.rb +1 -1
  61. data/spec/aws/list_type_patches_spec.rb +35 -0
  62. data/spec/aws/nested_arrays_spec.rb +155 -3
  63. data/spec/aws/serverless_spec.rb +0 -2
  64. data/spec/cfndsl_spec.rb +94 -78
  65. data/spec/cli_spec.rb +16 -54
  66. data/spec/cloud_formation_template_spec.rb +233 -0
  67. data/spec/condition_spec.rb +24 -0
  68. data/spec/direct_ruby_spec.rb +19 -0
  69. data/spec/external_parameters_spec.rb +2 -15
  70. data/spec/fixtures/condition-assertion.json +1 -0
  71. data/spec/fixtures/test.rb +2 -1
  72. data/spec/generate_spec.rb +4 -2
  73. data/spec/resources_spec.rb +0 -7
  74. data/spec/spec_helper.rb +2 -7
  75. data/spec/support/shared_examples/orchestration_template.rb +15 -2
  76. data/spec/types_definition_spec.rb +3 -6
  77. metadata +52 -23
  78. data/bin/cfndsl +0 -160
  79. data/lib/cfndsl/errors.rb +0 -31
  80. data/lib/cfndsl/os/heat_template.rb +0 -18
  81. data/lib/cfndsl/os/types.rb +0 -14
  82. data/lib/cfndsl/os/types.yaml +0 -2423
  83. data/lib/cfndsl/patches.rb +0 -226
  84. data/lib/cfnlego.rb +0 -44
  85. data/spec/fixtures/heattest.rb +0 -24
  86. data/spec/heat_template_spec.rb +0 -7
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/types'
3
+ require_relative '../jsonable'
4
+ require_relative '../types'
4
5
 
5
6
  module CfnDsl
6
7
  module AWS
7
8
  # Cloud Formation Types
8
9
  module Types
9
- TYPE_PREFIX = 'aws'
10
10
  class Type < JSONable; end
11
- include CfnDsl::Types
11
+ include CfnDsl::Types # This include triggers loading and patching of the global specification
12
12
  end
13
13
  end
14
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
File without changes
@@ -26,14 +26,9 @@ module Cfnlego
26
26
  private
27
27
 
28
28
  def definition
29
- content = Cfnlego.fetch_resource_content
30
- datainput = JSON.parse(content)
31
- data = datainput['ResourceTypes']
32
- begin
33
- @definition ||= data[@type]
34
- rescue RuntimeError
35
- raise "unknown #{@type}, no matching definition found"
36
- end
29
+ @definition ||= Cfnlego.resources[@type]
30
+ rescue StandardError
31
+ raise "unknown #{@type}, no matching definition found"
37
32
  end
38
33
  end
39
34
  end
@@ -0,0 +1,107 @@
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
+ params.set_param(*file.split('='))
46
+ end
47
+ end
48
+
49
+ logstream.puts("Loading template file #{filename}") if logstream
50
+ b.eval(File.read(filename), filename)
51
+ end
52
+
53
+ # Syntactic Sugar for embedded ruby usage
54
+ # @example
55
+ # require `cfndsl`
56
+ #
57
+ # class Builder
58
+ # include CfnDsl::CloudFormation
59
+ #
60
+ # def build_template()
61
+ # template = CloudFormation('ANewTemplate')
62
+ # a_param = template.Parameter('AParam')
63
+ # a_param.Type('String')
64
+ # return template
65
+ # end
66
+ #
67
+ # def map_instance_type(instance_type)
68
+ # # logic to auto convert instance types to latest available etc..
69
+ # FnFindInMap("InstanceTypeConversion",Ref('AWS::Region'),instance_type)
70
+ # end
71
+ #
72
+ # # templates can be passed around to other methods/classes
73
+ # def add_instance(template, instance_type)
74
+ # ec2 = template.EC2_Instance(:myInstance)
75
+ # ec2.InstanceType(map_instance_type(instance_type))
76
+ # # Alteratively with DSL block syntax
77
+ # this = self # declare block is eval for the model instance so need to keep a reference
78
+ # template.declare do
79
+ # EC2_Instance(:myInstance) do
80
+ # InstanceType this.map_instance_type(instance_type)
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ # def generate(template,pretty: false)
86
+ # valid = template.validate
87
+ # pretty ? valid.to_json : JSON.pretty_generate(valid)
88
+ # end
89
+ # end
90
+ #
91
+ module CloudFormation
92
+ # Include all the fun JSONABLE stuff and Fn* functions so you can use them
93
+ # in local methods
94
+ include Functions
95
+
96
+ def CloudFormation(description = nil, &block)
97
+ CloudFormationTemplate.new(description, &block)
98
+ end
99
+ end
100
+ end
101
+
102
+ # Main function to build and validate
103
+ # @return [CfnDsl::CloudFormationTemplate]
104
+ # @raise [CfnDsl::Error] if the block does not generate a valid template
105
+ def CloudFormation(description = nil, &block)
106
+ CfnDsl::CloudFormationTemplate.new(description).declare(&block).validate
107
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/jsonable'
3
+ require_relative 'jsonable'
4
4
 
5
5
  module CfnDsl
6
6
  # Handles condition objects
@@ -13,5 +13,15 @@ module CfnDsl
13
13
  def initialize(value)
14
14
  @value = value
15
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
16
26
  end
17
27
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/jsonable'
3
+ require_relative 'jsonable'
4
4
 
5
5
  module CfnDsl
6
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,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
4
+ require 'json'
5
+ require_relative 'deep_merge'
6
+
3
7
  module CfnDsl
4
8
  # Handles all external parameters
5
9
  class ExternalParameters
@@ -46,19 +50,6 @@ module CfnDsl
46
50
  parameters
47
51
  end
48
52
 
49
- def add_to_binding(bind, logstream)
50
- parameters.each_pair do |key, val|
51
- # rubocop:disable Style/SafeNavigation
52
- logstream.puts("Setting local variable #{key} to #{val}") if logstream
53
- # rubocop:enable Style/SafeNavigation
54
- if defined?(key) && val.is_a?(Hash)
55
- bind.eval "#{key}.merge(#{val.inspect})"
56
- else
57
- bind.eval "#{key} = #{val.inspect}"
58
- end
59
- end
60
- end
61
-
62
53
  def load_file(fname)
63
54
  format = File.extname fname
64
55
  case format
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'version'
4
+
3
5
  # Global variables to adjust CfnDsl behavior
4
6
  module CfnDsl
5
- module_function
6
-
7
- def disable_binding
8
- @disable_binding = true
7
+ class Error < StandardError
9
8
  end
10
9
 
11
- def disable_binding?
12
- @disable_binding
13
- 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__)
14
14
 
15
15
  def disable_deep_merge
16
16
  @disable_deep_merge = true
@@ -20,12 +20,51 @@ module CfnDsl
20
20
  @disable_deep_merge
21
21
  end
22
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
23
33
  def specification_file(file = nil)
24
- @specification_file = file if file
25
- @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)
26
37
  @specification_file
27
38
  end
28
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
+
29
68
  def reserved_items
30
69
  %w[Resource Rule Parameter Output].freeze
31
70
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/errors'
4
- require 'cfndsl/ref_check'
5
- require 'cfndsl/json_serialisable_object'
3
+ require_relative 'ref_check'
4
+ require_relative 'json_serialisable_object'
5
+ require_relative 'external_parameters'
6
6
 
7
7
  module CfnDsl
8
8
  # These functions are available anywhere inside
@@ -25,7 +25,7 @@ module CfnDsl
25
25
 
26
26
  # Equivalent to the CloudFormation template built in function Fn::GetAtt
27
27
  def FnGetAtt(logical_resource, attribute)
28
- Fn.new('GetAtt', [logical_resource, attribute])
28
+ Fn.new('GetAtt', [logical_resource, attribute], [logical_resource])
29
29
  end
30
30
 
31
31
  # Equivalent to the CloudFormation template built in function Fn::GetAZs
@@ -57,7 +57,7 @@ module CfnDsl
57
57
 
58
58
  # Equivalent to the Cloudformation template built in function Fn::If
59
59
  def FnIf(condition_name, true_value, false_value)
60
- Fn.new('If', [condition_name, true_value, false_value])
60
+ Fn.new('If', [condition_name, true_value, false_value], [], [condition_name])
61
61
  end
62
62
 
63
63
  # Equivalent to the Cloudformation template built in function Fn::Not
@@ -82,15 +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
87
89
 
90
+ refs = string.scan(FN_SUB_SCANNER).map(&:first)
91
+
88
92
  if substitutions
89
93
  raise ArgumentError, 'The second argument passed to Fn::Sub must be a Hash' unless substitutions.is_a? Hash
90
94
 
91
- Fn.new('Sub', [string, substitutions])
95
+ refs -= substitutions.keys.map(&:to_s)
96
+ Fn.new('Sub', [string, substitutions], refs)
92
97
  else
93
- Fn.new('Sub', string)
98
+ Fn.new('Sub', string, refs)
94
99
  end
95
100
  end
96
101
 
@@ -99,60 +104,11 @@ module CfnDsl
99
104
  Fn.new('ImportValue', value)
100
105
  end
101
106
 
102
- # DEPRECATED
103
- # Usage
104
- # FnFormat('This is a %0. It is 100%% %1', 'test', 'effective')
105
- # or
106
- # FnFormat('This is a %{test}. It is 100%% %{effective}',
107
- # :test => 'test",
108
- # :effective => 'effective')
109
- #
110
- # These will each generate a call to Fn::Join that when
111
- # evaluated will produce the string "This is a test. It is 100%
112
- # effective."
113
- #
114
- # Think of this as %0, %1, etc in the format string being replaced by the
115
- # corresponding arguments given after the format string. '%%' is replaced
116
- # by the '%' character.
117
- #
118
- # The actual Fn::Join call corresponding to the above FnFormat call would be
119
- # {"Fn::Join": ["",["This is a ","test",". It is 100","%"," ","effective"]]}
120
- #
121
- # If no arguments are given, or if a hash is given and the format
122
- # variable name does not exist in the hash, it is used as a Ref
123
- # to an existing resource or parameter.
124
- #
125
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
126
- def FnFormat(string, *arguments)
127
- warn '`FnFormat` is deprecated and will be removed a future release. Use `FnSub` instead'
128
- array = []
129
-
130
- if arguments.empty? || (arguments.length == 1 && arguments[0].instance_of?(Hash))
131
- hash = arguments[0] || {}
132
- string.scan(/(.*?)(?:%(%|\{([\w:]+)\})|\z)/m) do |x, y, z|
133
- array.push x if x && !x.empty?
134
-
135
- next unless y
136
-
137
- array.push(y == '%' ? '%' : (hash[z] || hash[z.to_sym] || Ref(z)))
138
- end
139
- else
140
- string.scan(/(.*?)(?:%(%|\d+)|\z)/m) do |x, y|
141
- array.push x if x && !x.empty?
142
-
143
- next unless y
144
-
145
- array.push(y == '%' ? '%' : arguments[y.to_i])
146
- end
147
- end
148
- Fn.new('Join', ['', array])
149
- end
150
-
151
107
  # Equivalent to the CloudFormation template built in function Fn::Cidr
152
108
  def FnCidr(ipblock, count, sizemask)
153
109
  Fn.new('Cidr', [ipblock, count, sizemask])
154
110
  end
155
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
111
+ # rubocop:enable
156
112
  end
157
113
 
158
114
  # This is the base class for just about everything useful in the
@@ -204,15 +160,17 @@ module CfnDsl
204
160
 
205
161
  def declare(&block)
206
162
  instance_eval(&block) if block_given?
163
+ self
207
164
  end
208
165
  end
209
166
 
210
167
  # Handles all of the Fn:: objects
211
168
  class Fn < JSONable
212
- def initialize(function, argument, refs = [])
169
+ def initialize(function, argument, refs = [], condition_refs = [])
213
170
  @function = function
214
171
  @argument = argument
215
172
  @_refs = refs
173
+ @_condition_refs = condition_refs
216
174
  end
217
175
 
218
176
  def as_json(_options = {})
@@ -225,12 +183,16 @@ module CfnDsl
225
183
  as_json.to_json(*args)
226
184
  end
227
185
 
228
- def references
186
+ def all_refs
229
187
  @_refs
230
188
  end
231
189
 
190
+ def condition_refs
191
+ @_condition_refs
192
+ end
193
+
232
194
  def ref_children
233
- [@argument]
195
+ [@argument].flatten
234
196
  end
235
197
  end
236
198