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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe CfnDsl::CloudFormationTemplate do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  def read_json_fixture(filename)
@@ -28,7 +30,6 @@ describe CfnDsl::CloudFormationTemplate do
28
30
  }
29
31
  )
30
32
  end
31
- # File.open('/tmp/dump', 'w') { |f| f.write(template.to_json) }
32
33
  expect(JSON.parse(template.to_json)).to eq(read_json_fixture('serverless-function.json'))
33
34
  end
34
35
 
@@ -40,7 +41,6 @@ describe CfnDsl::CloudFormationTemplate do
40
41
  CacheClusterSize '512M'
41
42
  Variables(Var1: 'value1')
42
43
  end
43
- File.open('/tmp/dump', 'w') { |f| f.write(template.to_json) }
44
44
  expect(JSON.parse(template.to_json)).to eq(read_json_fixture('serverless-api.json'))
45
45
  end
46
46
  end
data/spec/cfndsl_spec.rb CHANGED
@@ -1,30 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe CfnDsl do
6
+ let(:test_template_file_name) { "#{File.dirname(__FILE__)}/fixtures/test.rb" }
7
+
4
8
  after(:example) { CfnDsl::ExternalParameters.refresh! }
5
9
 
6
10
  it 'evaluates a cloud formation' do
7
- filename = "#{File.dirname(__FILE__)}/fixtures/test.rb"
8
- subject.eval_file_with_extras(filename, [[:raw, 'test=123']])
9
- end
10
-
11
- it 'evaluates a heat' do
12
- filename = "#{File.dirname(__FILE__)}/fixtures/heattest.rb"
13
- subject.eval_file_with_extras(filename)
14
- end
15
- end
16
-
17
- describe CfnDsl::HeatTemplate do
18
- it 'honors last-set value for non-array properties' do
19
- spec = self
20
- subject.declare do
21
- Server('myserver') do
22
- flavor 'foo'
23
- flavor 'bar'
24
- f = @Properties['flavor'].value
25
- spec.expect(f).to spec.eq('bar')
26
- end
27
- end
11
+ subject.eval_file_with_extras(test_template_file_name, [[:raw, 'test=123']])
28
12
  end
29
13
  end
30
14
 
@@ -53,16 +37,97 @@ describe CfnDsl::CloudFormationTemplate do
53
37
  end
54
38
  end
55
39
 
56
- it 'validates references' do
40
+ it 'detects cyclic Resource references' do
57
41
  q = subject.Resource('q') { DependsOn ['r'] }
58
42
  r = subject.Resource('r') { Property('z', Ref('q')) }
59
- q_refs = q.build_references({})
60
- r_refs = r.build_references({})
61
- expect(q_refs).to have_key('r')
62
- expect(q_refs).to_not have_key('q')
63
- expect(r_refs).to have_key('q')
64
- expect(r_refs).to_not have_key('r')
65
- expect(subject.check_refs.length).to eq(2)
43
+ q_refs = q.build_references
44
+ r_refs = r.build_references
45
+ expect(q_refs).to include('r')
46
+ expect(q_refs).to_not include('q')
47
+ expect(r_refs).to include('q')
48
+ expect(r_refs).to_not include('r')
49
+ expect(subject.check_refs.first).to match(/cyclic reference/i)
50
+ end
51
+
52
+ it 'detects a self reference in a Resource' do
53
+ q = subject.Resource('q') { Property('p', SomeDeepPropery: ['x', Ref('q')]) }
54
+ q_refs = q.build_references
55
+ expect(q_refs).to include('q')
56
+ messages = subject.check_refs
57
+ expect(messages.size).to eq(1) # Expect a self reference
58
+ expect(messages.first).to match(/references itself/i)
59
+ end
60
+
61
+ it 'detects a self reference in a Condition' do
62
+ q = subject.Condition('q', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('q')]))
63
+ q_refs = q.build_references([], nil, :condition_refs)
64
+ expect(q_refs).to include('q')
65
+ messages = subject.check_refs
66
+ expect(messages.size).to eq(1) # Expect a self reference
67
+ expect(messages.first).to match(/references itself/i)
68
+ end
69
+
70
+ it 'detects deep cycles in a Resource' do
71
+ subject.Condition('c', subject.FnEquals('a', 'b'))
72
+ subject.Resource('q') { Property('p', Ref('r')) }
73
+ subject.Resource('r') { Property('p', FnIf('c', FnGetAtt('s', 'attr'), 'x')) }
74
+ subject.Resource('s') { Property('p', FnSub('Something ${q}')) }
75
+ messages = subject.check_refs
76
+ expect(messages.size).to eq(1)
77
+ expect(messages.first).to match(/cyclic reference/i)
78
+ end
79
+
80
+ it 'detects deep cycles in Conditions' do
81
+ subject.Condition('c', subject.FnEquals('a', 'b'))
82
+ subject.Condition('d', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('c')]))
83
+ subject.Condition('q', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('r')]))
84
+ subject.Condition('r', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('s')]))
85
+ subject.Condition('s', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('q')]))
86
+ messages = subject.check_refs
87
+ expect(messages.size).to eq(1)
88
+ expect(messages.first).to match(/cyclic reference/i)
89
+ end
90
+
91
+ it 'detects invalid parameter references in Condition expressions' do
92
+ subject.Condition('x', subject.FnEquals('a', subject.Ref('p')))
93
+ messages = subject.check_refs
94
+ expect(messages.size).to eq(1)
95
+ expect(messages.first).to match(/^Invalid Reference: Conditions.*x.*p/)
96
+ end
97
+
98
+ it 'detects invalid condition references in Condition expressions' do
99
+ subject.Condition('d', subject.FnAnd([subject.FnEquals('x', 'x'), subject.Condition('c')]))
100
+ messages = subject.check_refs
101
+ expect(messages.size).to eq(1)
102
+ expect(messages.first).to match(/^Invalid Reference: Conditions.*d.*c/)
103
+ end
104
+
105
+ it 'detects invalid condition references in Resource Conditions' do
106
+ subject.Resource('r') { Condition 'd' }
107
+ messages = subject.check_refs
108
+ expect(messages.size).to eq(1)
109
+ expect(messages.first).to match(/^Invalid Reference: Resources.*r.*d/)
110
+ end
111
+
112
+ it 'detects invalid condition references in FnIf expressions deep inside Resources' do
113
+ subject.Resource('r') { Property(:p, FnIf(:d, 'vx', 'vy')) }
114
+ messages = subject.check_refs
115
+ expect(messages.size).to eq(1)
116
+ expect(messages.first).to match(/^Invalid Reference: Resources.*r.*d/)
117
+ end
118
+
119
+ it 'detects invalid condition references in Output Conditions' do
120
+ subject.Output('o') { Condition 'd' }
121
+ messages = subject.check_refs
122
+ expect(messages.size).to eq(1)
123
+ expect(messages.first).to match(/^Invalid Reference: Outputs.*o.*d/)
124
+ end
125
+
126
+ it 'detects invalid condition references in FnIf expressions deep inside Outputs' do
127
+ subject.Output('o') { Value(FnIf(:d, 'vx', 'vy')) }
128
+ messages = subject.check_refs
129
+ expect(messages.size).to eq(1)
130
+ expect(messages.first).to match(/^Invalid Reference: Outputs.*o.*d/)
66
131
  end
67
132
 
68
133
  it 'is a data-driven language' do
@@ -80,7 +145,7 @@ describe CfnDsl::CloudFormationTemplate do
80
145
  end
81
146
 
82
147
  it 'singularizes indirectly' do
83
- user = subject.User 'TestUser'
148
+ user = subject.IAM_User 'TestUser'
84
149
  policy = user.Policy 'stuff'
85
150
  expect(policy).to eq('stuff')
86
151
 
@@ -104,8 +169,8 @@ describe CfnDsl::CloudFormationTemplate do
104
169
  ].each do |param|
105
170
  ref = subject.Ref param
106
171
  expect(ref.to_json).to eq("{\"Ref\":\"#{param}\"}")
107
- refs = ref.build_references({})
108
- expect(refs).to have_key(param)
172
+ refs = ref.build_references
173
+ expect(refs).to include(param)
109
174
  end
110
175
  end
111
176
 
@@ -140,8 +205,8 @@ describe CfnDsl::CloudFormationTemplate do
140
205
  it 'Ref' do
141
206
  ref = subject.Ref 'X'
142
207
  expect(ref.to_json).to eq('{"Ref":"X"}')
143
- refs = ref.build_references({})
144
- expect(refs).to have_key('X')
208
+ refs = ref.build_references
209
+ expect(refs).to include('X')
145
210
  end
146
211
 
147
212
  it 'FnBase64' do
@@ -202,36 +267,10 @@ describe CfnDsl::CloudFormationTemplate do
202
267
  end
203
268
  end
204
269
 
205
- context 'FnFormat', 'String' do
206
- it 'formats correctly' do
207
- func = subject.FnFormat('abc%0def%1ghi%%x', 'A', 'B')
208
- expect(func.to_json).to eq('{"Fn::Join":["",["abc","A","def","B","ghi","%","x"]]}')
209
- end
210
- end
211
-
212
- context 'FnFormat', 'Hash' do
213
- it 'formats correctly' do
214
- func = subject.FnFormat('abc%{first}def%{second}ghi%%x', first: 'A', second: 'B')
215
- expect(func.to_json).to eq('{"Fn::Join":["",["abc","A","def","B","ghi","%","x"]]}')
216
- end
217
- end
218
-
219
- context 'FnFormat', 'Multiline' do
220
- it 'formats correctly' do
221
- multiline = <<-EOF.gsub(/^ {10}/, '')
222
- This is the first line
223
- This is the %0 line
224
- This is a %% sign
225
- EOF
226
- func = subject.FnFormat(multiline, 'second')
227
- expect(func.to_json).to eq('{"Fn::Join":["",["This is the first line\nThis is the ","second"," line\nThis is a ","%"," sign\n"]]}')
228
- end
229
- end
230
-
231
- context 'FnFormat', 'Ref' do
270
+ context 'FnCidr', 'Array' do
232
271
  it 'formats correctly' do
233
- func = subject.FnFormat '123%{Test}456'
234
- expect(func.to_json).to eq('{"Fn::Join":["",["123",{"Ref":"Test"},"456"]]}')
272
+ func = subject.FnCidr('10.0.0.0', '256', '8')
273
+ expect(func.to_json).to eq('{"Fn::Cidr":["10.0.0.0","256","8"]}')
235
274
  end
236
275
  end
237
276
  end
data/spec/cli_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe 'cfndsl', type: :aruba do
@@ -6,18 +8,18 @@ describe 'cfndsl', type: :aruba do
6
8
  Usage: cfndsl [options] FILE
7
9
  -o, --output FILE Write output to file
8
10
  -y, --yaml FILE Import yaml file as local variables
9
- -r, --ruby FILE Evaluate ruby file before template
10
11
  -j, --json FILE Import json file as local variables
11
12
  -p, --pretty Pretty-format output JSON
12
13
  -f, --format FORMAT Specify the output format (JSON default)
13
14
  -D, --define "VARIABLE=VALUE" Directly set local VARIABLE as VALUE
14
15
  -v, --verbose Turn on verbose ouptut
15
- -b, --disable-binding Disable binding configuration
16
16
  -m, --disable-deep-merge Disable deep merging of yaml
17
17
  -s, --specification-file FILE Location of Cloudformation Resource Specification file
18
- -u, --update-specification Update the Cloudformation Resource Specification file
18
+ -u [VERSION], Update the Resource Specification file to latest, or specific version
19
+ --update-specification
19
20
  -g RESOURCE_TYPE,RESOURCE_LOGICAL_NAME,
20
21
  --generate Add resource type and logical name
22
+ -a, --assetversion Print out the specification version
21
23
  -l, --list List supported resources
22
24
  -h, --help Display this screen
23
25
  USAGE
@@ -33,20 +35,43 @@ describe 'cfndsl', type: :aruba do
33
35
 
34
36
  before(:each) { write_file('template.rb', template_content) }
35
37
 
36
- context 'cfndsl -u' do
38
+ # The known working version is the embedded version
39
+ WORKING_SPEC_VERSION = JSON.parse(File.read(CfnDsl::LOCAL_SPEC_FILE))['ResourceSpecificationVersion']
40
+
41
+ context "cfndsl -u #{WORKING_SPEC_VERSION}" do
37
42
  it 'updates the specification file' do
38
- run 'cfndsl -u'
43
+ run_command "cfndsl -u #{WORKING_SPEC_VERSION}"
39
44
  expect(last_command_started).to have_output_on_stderr(<<-OUTPUT.gsub(/^ {8}/, '').chomp)
40
45
  Updating specification file
41
- Specification successfully written to #{ENV['HOME']}/.cfndsl/resource_specification.json
46
+ Specification #{WORKING_SPEC_VERSION} successfully written to #{ENV['HOME']}/.cfndsl/resource_specification.json
42
47
  OUTPUT
43
48
  expect(last_command_started).to have_exit_status(0)
44
49
  end
45
50
  end
46
51
 
52
+ context 'cfndsl -u' do
53
+ it 'updates the specification file' do
54
+ run_command 'cfndsl -u'
55
+
56
+ expected = %r{Updating specification file
57
+ Specification ([0-9]+\.){2}[0-9]+ successfully written to #{ENV['HOME']}/.cfndsl/resource_specification.json}
58
+
59
+ expect(last_command_started).to have_output_on_stderr(expected)
60
+ expect(last_command_started).to have_exit_status(0)
61
+ end
62
+ end
63
+
64
+ context 'cfndsl -a' do
65
+ it 'prints out the specification file version' do
66
+ run_command 'cfndsl -a'
67
+ expect(last_command_started).to have_output_on_stderr(/([0-9]+\.){2}[0-9]+/)
68
+ expect(last_command_started).to have_exit_status(0)
69
+ end
70
+ end
71
+
47
72
  context 'cfndsl' do
48
73
  it 'displays the usage' do
49
- run 'cfndsl'
74
+ run_command'cfndsl'
50
75
  expect(last_command_started).to have_output(usage)
51
76
  expect(last_command_started).to have_exit_status(1)
52
77
  end
@@ -54,31 +79,21 @@ describe 'cfndsl', type: :aruba do
54
79
 
55
80
  context 'cfndsl --help' do
56
81
  it 'displays the usage' do
57
- run_simple 'cfndsl --help'
82
+ run_command_and_stop 'cfndsl --help'
58
83
  expect(last_command_started).to have_output(usage)
59
84
  end
60
85
  end
61
86
 
62
87
  context 'cfndsl FILE' do
63
- it 'gives a deprecation warning about bindings' do
64
- run_simple 'cfndsl template.rb'
65
- expect(last_command_started).to have_output_on_stderr(<<-WARN.gsub(/^ {8}/, '').chomp)
66
- The creation of constants as config is deprecated!
67
- Please switch to the #external_parameters method within your templates to access variables
68
- See https://github.com/stevenjack/cfndsl/issues/170
69
- Use the --disable-binding flag to suppress this message
70
- WARN
71
- end
72
-
73
88
  it 'generates a JSON CloudFormation template' do
74
- run_simple 'cfndsl template.rb'
89
+ run_command_and_stop 'cfndsl template.rb'
75
90
  expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"default"}')
76
91
  end
77
92
  end
78
93
 
79
94
  context 'cfndsl FILE --pretty' do
80
95
  it 'generates a pretty JSON CloudFormation template' do
81
- run_simple 'cfndsl template.rb --pretty'
96
+ run_command_and_stop 'cfndsl template.rb --pretty'
82
97
  expect(last_command_started).to have_output_on_stdout(<<-OUTPUT.gsub(/^ {8}/, '').chomp)
83
98
  {
84
99
  "AWSTemplateFormatVersion": "2010-09-09",
@@ -90,7 +105,7 @@ describe 'cfndsl', type: :aruba do
90
105
 
91
106
  context 'cfndsl FILE --output FILE' do
92
107
  it 'writes the JSON CloudFormation template to a file' do
93
- run_simple 'cfndsl template.rb --output template.json'
108
+ run_command_and_stop 'cfndsl template.rb --output template.json'
94
109
  expect(read('template.json')).to eq(['{"AWSTemplateFormatVersion":"2010-09-09","Description":"default"}'])
95
110
  end
96
111
  end
@@ -99,7 +114,7 @@ describe 'cfndsl', type: :aruba do
99
114
  before { write_file('params.yaml', 'DESC: yaml') }
100
115
 
101
116
  it 'interpolates the YAML file in the CloudFormation template' do
102
- run_simple 'cfndsl template.rb --yaml params.yaml'
117
+ run_command_and_stop 'cfndsl template.rb --yaml params.yaml'
103
118
  expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"yaml"}')
104
119
  end
105
120
  end
@@ -108,40 +123,29 @@ describe 'cfndsl', type: :aruba do
108
123
  before { write_file('params.json', '{"DESC":"json"}') }
109
124
 
110
125
  it 'interpolates the JSON file in the CloudFormation template' do
111
- run_simple 'cfndsl template.rb --json params.json'
126
+ run_command_and_stop 'cfndsl template.rb --json params.json'
112
127
  expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"json"}')
113
128
  end
114
129
  end
115
130
 
116
- context 'cfndsl FILE --ruby FILE' do
117
- let(:template_content) do
118
- <<-TEMPLATE.gsub(/^ {8}/, '')
119
- CloudFormation do
120
- DESC = 'default' unless defined? DESC
121
- Description DESC
122
- end
123
- TEMPLATE
124
- end
125
-
126
- before(:each) { write_file('params.rb', 'DESC = "ruby"') }
127
-
128
- it 'interpolates the Ruby file in the CloudFormation template' do
129
- run_simple 'cfndsl template.rb --ruby params.rb'
130
- expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"ruby"}')
131
+ context 'cfndsl FILE --define VARIABLE=VALUE' do
132
+ it 'interpolates the command line variables in the CloudFormation template' do
133
+ run_command "cfndsl template.rb --define \"DESC='cli'\""
134
+ expect(last_command_started).to have_output_on_stdout("{\"AWSTemplateFormatVersion\":\"2010-09-09\",\"Description\":\"'cli'\"}")
131
135
  end
136
+ end
132
137
 
133
- it 'gives a deprecation warning and does not interpolate if bindings are disabled' do
134
- run_simple 'cfndsl template.rb --ruby params.rb --disable-binding --verbose'
135
- deprecation_warning = /Interpreting Ruby files was disabled\. .*params.rb will not be read/
136
- expect(last_command_started).to have_output_on_stderr(deprecation_warning)
137
- expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"default"}')
138
+ context 'cfndsl FILE --define VARIABLE=true' do
139
+ it 'interpolates the command line variable with value true in the CloudFormation template ' do
140
+ run_command 'cfndsl template.rb --define "DESC=true"'
141
+ expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":true}')
138
142
  end
139
143
  end
140
144
 
141
- context 'cfndsl FILE --define VARIABLE=VALUE' do
142
- it 'interpolates the command line variables in the CloudFormation template' do
143
- run "cfndsl template.rb --define \"DESC='cli'\""
144
- expect(last_command_started).to have_output_on_stdout("{\"AWSTemplateFormatVersion\":\"2010-09-09\",\"Description\":\"'cli'\"}")
145
+ context 'cfndsl FILE --define VARIABLE=false' do
146
+ it 'interpolates the command line variable with value false in the CloudFormation template ' do
147
+ run_command 'cfndsl template.rb --define "DESC=false"'
148
+ expect(last_command_started).to have_output_on_stdout('{"AWSTemplateFormatVersion":"2010-09-09","Description":"default"}')
145
149
  end
146
150
  end
147
151
 
@@ -149,11 +153,10 @@ describe 'cfndsl', type: :aruba do
149
153
  before { write_file('params.yaml', 'DESC: yaml') }
150
154
 
151
155
  it 'displays the variables as they are interpolated in the CloudFormation template' do
152
- run_simple 'cfndsl template.rb --yaml params.yaml --verbose'
156
+ run_command_and_stop 'cfndsl template.rb --yaml params.yaml --verbose'
153
157
  verbose = /
154
158
  Using \s specification \s file .* \.json \n
155
159
  Loading \s YAML \s file \s .* params\.yaml \n
156
- Setting \s local \s variable \s DESC \s to \s yaml \n
157
160
  Loading \s template \s file \s .* template.rb \n
158
161
  Writing \s to \s STDOUT
159
162
  /x
@@ -1,5 +1,240 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe CfnDsl::CloudFormationTemplate do
4
6
  it_behaves_like 'an orchestration template'
7
+
8
+ context '#validate' do
9
+ it 'returns self if the template is empty' do
10
+ # TODO: Strictly Cloudformation requires at least one resource, but this is not validated yet.
11
+ expect(subject.validate).to equal(subject)
12
+ end
13
+
14
+ context 'resources' do
15
+ it 'returns self if there are valid Refs to parameters' do
16
+ subject.Parameter('TestParameter').Type('String')
17
+ r = subject.Resource(:TestResource)
18
+ r.Type('Custom-TestType')
19
+ r.Property(:AProperty, r.Ref(:TestParameter))
20
+ expect(subject.validate).to equal(subject)
21
+ end
22
+
23
+ it 'returns self if there are valid Refs to other resources' do
24
+ tr = subject.Resource(:TestResource)
25
+ tr.Type('Custom-TestType')
26
+ tr.Property(:AProperty, tr.Ref(:TestResource2))
27
+
28
+ t2 = subject.Resource('TestResource2')
29
+ t2.Type('Custom-TestType')
30
+ expect(subject.validate).to equal(subject)
31
+ end
32
+
33
+ it 'returns self if there are valid Fn::GetAtt references to other resources' do
34
+ tr = subject.Resource(:TestResource)
35
+ tr.Type('Custom-TestType')
36
+ tr.Property(:AProperty, tr.FnGetAtt(:TestResource2, :AnAttribute))
37
+
38
+ t2 = subject.Resource('TestResource2')
39
+ t2.Type('Custom-TestType')
40
+ expect(subject.validate).to equal(subject)
41
+ end
42
+
43
+ it 'returns self if there are valid DependsOn to other resources' do
44
+ tr = subject.Resource(:TestResource)
45
+ tr.Type('Custom-TestType')
46
+ tr.DependsOn(:TestResource2)
47
+
48
+ t2 = subject.Resource('TestResource2')
49
+ t2.Type('Custom-TestType')
50
+ expect(subject.validate).to equal(subject)
51
+ end
52
+
53
+ it 'raises CfnDsl::Error if references a non existent condition' do
54
+ tr = subject.Resource(:TestResource)
55
+ tr.Condition('NoCondition')
56
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*NoCondition/)
57
+ end
58
+
59
+ it 'raises CfnDsl::Error if there are invalid Refs' do
60
+ tr = subject.Resource(:TestResource)
61
+ tr.Type('Custom-TestType')
62
+ tr.Property(:AProperty, tr.Ref(:TestResource2))
63
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestResource.*TestResource2/)
64
+ end
65
+
66
+ it 'raises CfnDsl::Error if there are invalid Fn::GetAtt references' do
67
+ tr = subject.Resource(:TestResource)
68
+ tr.Type('Custom-TestType')
69
+ tr.Property(:AProperty, tr.FnGetAtt(:TestResource2, :AnAttribute))
70
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestResource.*TestResource2/)
71
+ end
72
+
73
+ it 'raises CfnDsl::Error if there are invalid Fn::Sub attribute references' do
74
+ tr = subject.Resource(:TestResource)
75
+ tr.Type('Custom-TestType')
76
+ tr.Property(:AProperty, tr.FnBase64(tr.FnSub('${TestResource2.AnAttribute}')))
77
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestResource.*TestResource2/)
78
+ end
79
+
80
+ it 'raises CfnDsl::Error if there are invalid Fn::Sub references' do
81
+ tr = subject.Resource(:TestResource)
82
+ tr.Type('Custom-TestType')
83
+ tr.Property(:AProperty, tr.FnBase64(tr.FnSub('${TestResource2}')))
84
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestResource.*TestResource2/)
85
+ end
86
+
87
+ it 'raises CfnDsl::Error if there are invalid DependsOn' do
88
+ tr = subject.Resource(:TestResource)
89
+ tr.Type('Custom-TestType')
90
+ tr.DependsOn(['TestResource2'])
91
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestResource.*TestResource2/)
92
+ end
93
+
94
+ it 'raises CfnDsl::Error if a resource explicitly DependsOn itself' do
95
+ tr = subject.Resource(:TestResource)
96
+ tr.Type('Custom-TestType')
97
+ tr.DependsOn(['TestResource'])
98
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*references itself/)
99
+ end
100
+
101
+ it 'raises CfnDsl::Error if a resource Refs itself' do
102
+ tr = subject.Resource(:TestResource)
103
+ tr.Type('Custom-TestType')
104
+ tr.Property(:AProperty, tr.Ref(:TestResource))
105
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*itself/i)
106
+ end
107
+
108
+ it 'raises CfnDsl::Error if a resource references itself in Fn::GetAtt' do
109
+ tr = subject.Resource(:TestResource)
110
+ tr.Type('Custom-TestType')
111
+ tr.Property(:AProperty, tr.FnGetAtt(:TestResource, :AnAttr))
112
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*itself/i)
113
+ end
114
+
115
+ it 'raises CfnDsl::Error if a resourcs references itself in Fn::Sub expression' do
116
+ tr = subject.Resource(:TestResource)
117
+ tr.Type('Custom-TestType')
118
+ tr.Property(:AProperty, tr.FnSub('${TestResource}'))
119
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*itself/i)
120
+ end
121
+
122
+ it 'raises CfnDsl::Error if there are cyclic DependsOn references' do
123
+ tr = subject.Resource(:ResourceOne)
124
+ tr.Type('Custom-TestType')
125
+ tr.DependsOn(:ResourceTwo)
126
+
127
+ t2 = subject.Resource('ResourceTwo')
128
+ t2.Type('Custom-TestType')
129
+ t2.DependsOn(:ResourceOne)
130
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /cyclic.*ResourceTwo.*ResourceOne/i)
131
+ end
132
+
133
+ it 'raises CfnDsl::Error if there are cyclic Refs' do
134
+ tr = subject.Resource(:TestResourceOne)
135
+ tr.Type('Custom-TestType')
136
+ tr.Property(:AProperty, tr.Ref(:TestResourceTwo))
137
+
138
+ t2 = subject.Resource('TestResourceTwo')
139
+ t2.Type('Custom-TestType')
140
+ t2.DependsOn('TestResourceOne')
141
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /cyclic.*Two.*One/i)
142
+ end
143
+
144
+ it 'raises CfnDsl::Error if there are cyclic Fn::GetAtt references' do
145
+ tr = subject.Resource(:TestResource1)
146
+ tr.Property(:AProperty, tr.Ref(:TestResource2))
147
+
148
+ t2 = subject.Resource('TestResource2')
149
+ t2.DependsOn('TestResource3')
150
+
151
+ t3 = subject.Resource('TestResource3')
152
+ t3.Property(:OtherProperty, subject.FnGetAtt('TestResource1', :OtherAttribute))
153
+
154
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /cyclic.*3.*2.*1/i)
155
+ end
156
+
157
+ it 'raises CfnDsl::Error if there are cyclic Fn::Sub references' do
158
+ subject.Resource(:TestResource1)
159
+
160
+ t2 = subject.Resource('TestResource2')
161
+ t2.DependsOn(%w[TestResource3 TestResource1])
162
+
163
+ t3 = subject.Resource('TestResource3')
164
+ t3.Property(:OtherProperty, subject.FnSub('SomeValue ${TestResource2}'))
165
+
166
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /cyclic*.3?.*2/i)
167
+ end
168
+ end
169
+
170
+ context 'conditions' do
171
+ it 'returns self if there are valid condition references' do
172
+ subject.Parameter('TestParameter').Type('String')
173
+ subject.Condition(:TestCondition, subject.FnEquals(subject.Ref(:TestParameter), 'testvalue'))
174
+ expect(subject.validate).to equal(subject)
175
+ end
176
+
177
+ it 'raises CfnDsl::Error if invalid ref in condition' do
178
+ subject.Condition(:TestCondition, subject.FnEquals(subject.Ref(:NoParam), 'testvalue'))
179
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /^Invalid Reference:.*TestCondition.*NoParam/)
180
+ end
181
+
182
+ it 'raises CfnDsl::Error if null value in Condition' do
183
+ subject.Condition(:TestCondition, subject.FnEquals(nil, 'testvalue'))
184
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Condition.*TestCondition.*null/)
185
+ end
186
+
187
+ it 'raises CfnDsl::Error if null value deep in Condition' do
188
+ subject.Condition(:TestCondition, subject.FnEquals({ Condition: nil }, 'testvalue'))
189
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Condition.*TestCondition.*null/)
190
+ end
191
+
192
+ # Note cycles in conditions tested in cfndsl_spec.rb
193
+ end
194
+
195
+ context 'outputs' do
196
+ it 'returns self if there are valid Refs to parameters' do
197
+ subject.Parameter('TestParameter').Type('String')
198
+ subject.Output('TestOutput').Value(subject.Ref(:TestParameter))
199
+ expect(subject.validate).to equal(subject)
200
+ end
201
+
202
+ it 'returns self if there are valid Refs to resources' do
203
+ subject.Resource(:TestResource)
204
+ subject.Output('TestResourceOutput').Value(subject.Ref(:TestResource))
205
+ expect(subject.validate).to equal(subject)
206
+ end
207
+
208
+ it 'returns self if there are valid Fn::GetAtt references to resources' do
209
+ subject.Resource(:TestResource)
210
+ subject.Output('TestResourceOutput').Value(subject.FnGetAtt(:TestResource, :AnAtt))
211
+ expect(subject.validate).to equal(subject)
212
+ end
213
+
214
+ it 'raises CfnDsl::Error if references a non existent condition' do
215
+ subject.Output(:TestOutput).Condition('NoCondition')
216
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestOutput.*NoCondition/)
217
+ end
218
+
219
+ it 'raises CfnDsl::Error if there are invalid Refs' do
220
+ subject.Output('TestResourceOutput').Value(subject.Ref(:TestResource))
221
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Outputs.*TestResource/)
222
+ end
223
+
224
+ it 'raises CfnDsl::Error if there are invalid Fn::GetAtt references' do
225
+ subject.Output('TestResourceOutput').Value(subject.FnGetAtt(:TestResource, :Attr))
226
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Outputs.*TestResource/)
227
+ end
228
+
229
+ it 'raises CfnDsl::Error if there are invalid Fn::Sub attribute references' do
230
+ subject.Output('TestResourceOutput').Value(subject.FnSub('prefix ${SomeRef.attr}suffix'))
231
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Outputs.*TestResource/)
232
+ end
233
+
234
+ it 'raises CfnDsl::Error if there are invalid Fn::Sub references' do
235
+ subject.Output('TestResourceOutput').Value(subject.FnSub('prefix ${SomeRef}suffix'))
236
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /Outputs.*TestResource/)
237
+ end
238
+ end
239
+ end
5
240
  end