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,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 autoscaling group update policy objects for Resources
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CfnDsl
2
- VERSION = '0.15.0'.freeze
4
+ VERSION = '1.0.0'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # rubocop:disable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity, Metrics/MethodLength
2
4
  # rubocop:disable Metrics/ModuleLength, Metrics/PerceivedComplexity, Style/IfInsideElse, Style/Semicolon
3
5
  #
@@ -5,7 +7,7 @@
5
7
  module DeepMerge
6
8
  class InvalidParameter < StandardError; end
7
9
 
8
- DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'.freeze
10
+ DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'
9
11
 
10
12
  # Deep Merge core documentation.
11
13
  # deep_merge! method permits merging of arbitrary child elements. The two top level
@@ -85,6 +87,7 @@ module DeepMerge
85
87
  knockout_prefix = options[:knockout_prefix] || nil
86
88
  raise InvalidParameter, 'knockout_prefix cannot be an empty string in deep_merge!' if knockout_prefix == ''
87
89
  raise InvalidParameter, 'overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!' if knockout_prefix && !overwrite_unmergeable
90
+
88
91
  # if present: we will split and join arrays on this char before merging
89
92
  array_split_char = options[:unpack_arrays] || false
90
93
  # request that we avoid merging arrays
@@ -103,6 +106,7 @@ module DeepMerge
103
106
  di = options[:debug_indent] || ''
104
107
  # do nothing if source is nil
105
108
  return dest if !merge_nil_values && source.nil?
109
+
106
110
  # if dest doesn't exist, then simply copy source to it
107
111
  if !dest && overwrite_unmergeable
108
112
  dest = source; return dest
@@ -146,9 +150,7 @@ module DeepMerge
146
150
  if array_split_char
147
151
  puts "#{di} split/join on source: #{source.inspect}" if merge_debug
148
152
  source = source.join(array_split_char).split(array_split_char)
149
- if dest.is_a?(Array)
150
- dest = dest.join(array_split_char).split(array_split_char)
151
- end
153
+ dest = dest.join(array_split_char).split(array_split_char) if dest.is_a?(Array)
152
154
  end
153
155
  # if there's a naked knockout_prefix in source, that means we are to truncate dest
154
156
  if knockout_prefix && source.index(knockout_prefix)
@@ -204,7 +206,7 @@ module DeepMerge
204
206
  end
205
207
  puts "#{di}Returning #{dest.inspect}" if merge_debug
206
208
  dest
207
- end # deep_merge!
209
+ end
208
210
 
209
211
  # allows deep_merge! to uniformly handle overwriting of unmergeable entities
210
212
  def self.overwrite_unmergeables(source, dest, options)
@@ -241,4 +243,6 @@ module DeepMerge
241
243
  end
242
244
  obj
243
245
  end
244
- end # module DeepMerge
246
+ end
247
+ # rubocop:enable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity, Metrics/MethodLength
248
+ # rubocop:enable Metrics/ModuleLength, Metrics/PerceivedComplexity, Style/IfInsideElse, Style/Semicolon
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Totally borrowed from https://github.com/danielsdeleo/deep_merge
2
4
  require 'deep_merge/core'
3
5
 
@@ -9,7 +11,7 @@ module DeepMerge
9
11
  default_opts = { preserve_unmergeables: false }
10
12
  DeepMerge.deep_merge!(source, self, default_opts.merge(options))
11
13
  end
12
- end # DeepMergeHashExt
14
+ end
13
15
  end
14
16
 
15
17
  # Extends hash with deep merge
data/sample/autoscale.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  # We start things off by calling the CloudFormation function.
3
4
  CloudFormation do
@@ -15,7 +16,6 @@ availability zones in a region. The instances are load balanced with a
15
16
  simple health check. The web site is available on port 80, however,
16
17
  the instances can be configured to listen on any port (8888 by
17
18
  default).
18
-
19
19
  **WARNING** This template creates one or more Amazon EC2
20
20
  instances. You will be billed for the AWS resources used if you create
21
21
  a stack from this template.
data/sample/autoscale2.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We start things off by calling the CloudFormation function.
2
4
  CloudFormation do
3
5
  # Declare the template format version
@@ -14,7 +16,6 @@ availability zones in a region. The instances are load balanced with a
14
16
  simple health check. The web site is available on port 80, however,
15
17
  the instances can be configured to listen on any port (8888 by
16
18
  default).
17
-
18
19
  **WARNING** This template creates one or more Amazon EC2
19
20
  instances. You will be billed for the AWS resources used if you create
20
21
  a stack from this template.
@@ -89,8 +90,8 @@ a stack from this template.
89
90
  AutoScalingGroup('WebServerGroup') do
90
91
  UpdatePolicy('AutoScalingRollingUpdate',
91
92
  'MinInstancesInService' => '1',
92
- 'MaxBatchSize' => '1',
93
- 'PauseTime' => 'PT15M')
93
+ 'MaxBatchSize' => '1',
94
+ 'PauseTime' => 'PT15M')
94
95
  AvailabilityZones FnGetAZs('')
95
96
  LaunchConfigurationName Ref('LaunchConfig')
96
97
  MinSize 1
data/sample/circular.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  AWSTemplateFormatVersion '2010-09-09'
3
5
 
data/sample/codedeploy.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
- DESCRIPTION ||= 'CodeDeploy description'.freeze
4
+ DESCRIPTION ||= 'CodeDeploy description'
3
5
 
4
6
  Description DESCRIPTION
5
7
 
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  AWSTemplateFormatVersion '2010-09-09'
3
5
 
4
6
  Description 'Creates SNS, SQS, S3 bucket and enables AWS Config.'
5
7
 
6
- Queue('ConfigServiceQueue') do
8
+ SQS_Queue('ConfigServiceQueue') do
7
9
  QueueName 'ConfigServiceQueue'
8
10
  end
9
11
 
10
12
  Bucket('ConfigServiceBucket') do
11
13
  end
12
14
 
13
- Policy('ConfigServiceS3BucketAccessPolicy') do
15
+ IAM_Policy('ConfigServiceS3BucketAccessPolicy') do
14
16
  PolicyName 'ConfigServiceS3BucketAccessPolicy'
15
17
  PolicyDocument(
16
18
  'Version' => '2012-10-17',
@@ -66,7 +68,7 @@ CloudFormation do
66
68
  }]
67
69
  end
68
70
 
69
- Policy('ConfigServiceSNSTopicAccessPolicy') do
71
+ IAM_Policy('ConfigServiceSNSTopicAccessPolicy') do
70
72
  PolicyName 'ConfigServiceSNSTopicAccessPolicy'
71
73
  PolicyDocument(
72
74
  'Version' => '2012-10-17',
data/sample/ecs.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
- DESCRIPTION ||= 'ecs description'.freeze
4
+ DESCRIPTION ||= 'ecs description'
3
5
 
4
6
  Description DESCRIPTION
5
7
 
data/sample/export.rb CHANGED
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  EC2_SecurityGroup(:webSecurityGroup) do
3
5
  GroupDescription 'Allow incoming HTTP traffic from anywhere'
4
6
  SecurityGroupIngress [
5
7
  {
6
- 'CidrIp' => '0.0.0.0/0',
8
+ 'CidrIp' => '0.0.0.0/0',
7
9
  'IpProtocol' => 'tcp',
8
- 'FromPort' => 80,
9
- 'ToPort' => 80
10
+ 'FromPort' => 80,
11
+ 'ToPort' => 80
10
12
  }
11
13
  ]
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  AWSTemplateFormatVersion '2010-09-09'
3
5
 
data/sample/import.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  EC2_SecurityGroup(:databaseSecurityGroup) do
3
5
  GroupDescription 'Allow access from only web instances'
@@ -5,8 +7,8 @@ CloudFormation do
5
7
  {
6
8
  'SourceSecurityGroupId' => FnImportValue(:webSecurityGroupId),
7
9
  'IpProtocol' => 'tcp',
8
- 'FromPort' => 7777,
9
- 'ToPort' => 7777
10
+ 'FromPort' => 7777,
11
+ 'ToPort' => 7777
10
12
  }
11
13
  ]
12
14
  end
data/sample/lambda.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
- DESCRIPTION ||= 'lambda description'.freeze
4
+ DESCRIPTION ||= 'lambda description'
3
5
 
4
6
  Description DESCRIPTION
5
7
 
data/sample/s3.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  S3_Bucket('Bucket') do
3
5
  BucketName 'MyBucket'
data/sample/t1.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  CloudFormation do
2
4
  description = external_parameters.fetch(:description, 'default description')
3
5
  machines = external_parameters.fetch(:machines, 1).to_i
@@ -8,7 +10,7 @@ CloudFormation do
8
10
  name = "machine#{i}"
9
11
  EC2_Instance(name) do
10
12
  ImageId 'ami-12345678'
11
- Type 't1.micro'
13
+ InstanceType 't1.micro'
12
14
  end
13
15
  end
14
16
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cfndsl'
2
4
 
3
5
  CloudFormation do
@@ -40,7 +42,7 @@ CloudFormation do
40
42
  RouteTableId Ref(route_table)
41
43
  end
42
44
 
43
- Route(subnet + 'GatewayRoute') do
45
+ EC2_Route(subnet + 'GatewayRoute') do
44
46
  DependsOn :GatewayToInternet
45
47
  RouteTableId Ref(route_table)
46
48
  DestinationCidrBlock '0.0.0.0/0'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cfndsl'
2
4
 
3
5
  CloudFormation do
@@ -58,7 +60,7 @@ CloudFormation do
58
60
  RouteTableId Ref(route_table)
59
61
  end
60
62
 
61
- Route(subnet + 'GatewayRoute') do
63
+ EC2_Route(subnet + 'GatewayRoute') do
62
64
  DependsOn :GatewayToInternet
63
65
  RouteTableId Ref(route_table)
64
66
  DestinationCidrBlock '0.0.0.0/0'
@@ -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
  describe CfnDsl::CloudFormationTemplate do
@@ -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
  describe CfnDsl::CloudFormationTemplate do
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe CfnDsl::CloudFormationTemplate do
6
+ subject(:template) { described_class.new }
7
+
8
+ describe 'Bogus Properties that are type List' do
9
+ it 'fixes Resource property type to a List of its item type' do
10
+ template.LakeFormation_DataLakeSettings('lake') do
11
+ Admin { DataLakePrincipalIdentifier 'principal' }
12
+ end
13
+ expect(template.to_json).to include('"Type":"AWS::LakeFormation::DataLakeSettings","Properties":{"Admins":[{"DataLakePrincipalIdentifier":"principal"')
14
+ end
15
+
16
+ it 'fixes Subtype property Tags to a list of Tags' do
17
+ template.AppSync_GraphQLApi('api') do
18
+ Tag do
19
+ Key 'tagkey'
20
+ Value 'tagvalue'
21
+ end
22
+ end
23
+ expect(template.to_json).to include('"Properties":{"Tags":[{"Key":"tagkey","Value":"tagvalue"}]}}}')
24
+ end
25
+
26
+ it 'fixes Subtype property type to a List of its item type' do
27
+ template.Glue_SecurityConfiguration('glue') do
28
+ EncryptionConfiguration do
29
+ S3Encryption { S3EncryptionMode 'mode' }
30
+ end
31
+ end
32
+ expect(template.to_json).to include('"EncryptionConfiguration":{"S3Encryptions":[{"S3EncryptionMode":"mode"}]}}}}}')
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe CfnDsl::CloudFormationTemplate do
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe CfnDsl::CloudFormationTemplate do
6
+ subject(:template) { described_class.new }
7
+
8
+ describe 'Nested_Arrays' do
9
+ it 'ensure nested arrays are not duplicated' do
10
+ template.DirectoryService_SimpleAD(:Test) do
11
+ VpcSettings do
12
+ SubnetId %w[subnet-a subnet-b]
13
+ end
14
+ end
15
+
16
+ expect(template.to_json).to include('"SubnetIds":["subnet-a","subnet-b"]}')
17
+ expect(template.to_json).not_to include('"SubnetIds":[["subnet-a","subnet-b"],["subnet-a","subnet-b"]]')
18
+ end
19
+
20
+ it 'appends entries with multiple invocations' do
21
+ template.DirectoryService_SimpleAD(:Test) do
22
+ VpcSettings do
23
+ SubnetId 'subnet-a'
24
+ SubnetId 'subnet-b'
25
+ end
26
+ end
27
+
28
+ expect(template.to_json).to include('"SubnetIds":["subnet-a","subnet-b"]}')
29
+ end
30
+
31
+ it 'appends entries with multiple array invocations' do
32
+ template.DirectoryService_SimpleAD(:Test) do
33
+ VpcSettings do
34
+ SubnetId %w[subnet-a subnet-b]
35
+ SubnetId %w[subnet-c subnet-d]
36
+ end
37
+ end
38
+
39
+ expect(template.to_json).to include('"SubnetIds":["subnet-a","subnet-b","subnet-c","subnet-d"]')
40
+ end
41
+
42
+ # Previous behaviour produces valid result, but appends rather than replaces (inconsistent with top level item)
43
+ it 'appends entries when plural form is used' do
44
+ template.DirectoryService_SimpleAD(:Test) do
45
+ VpcSettings do
46
+ SubnetId 'subnet-x'
47
+ SubnetIds %w[subnet-a subnet-b]
48
+ end
49
+ end
50
+
51
+ expect(template.to_json).to include('"SubnetIds":["subnet-a","subnet-b"]}')
52
+ end
53
+
54
+ it 'plural form accepts a Ref' do
55
+ template.EC2_LaunchTemplate(:test) do
56
+ LaunchTemplateData do
57
+ SecurityGroupIds Ref('AListParam')
58
+ end
59
+ end
60
+ expect(template.to_json).to include('"LaunchTemplateData":{"SecurityGroupIds":{"Ref":"AListParam"}')
61
+ end
62
+ end
63
+
64
+ describe 'Nested Arrays With Subtype items' do
65
+ it 'can add a subtype to a list' do
66
+ template.EC2_SpotFleet('SpotFleet') do
67
+ SpotFleetRequestConfigData do
68
+ LaunchSpecification do
69
+ ImageId 'ami-1234'
70
+ end
71
+ end
72
+ end
73
+ expect(template.to_json).to include('"LaunchSpecifications":[{"ImageId":"ami-1234"}]')
74
+ end
75
+
76
+ it 'can conditionally add a subtype to a list' do
77
+ template.EC2_SpotFleet('SpotFleet') do
78
+ SpotFleetRequestConfigData do
79
+ # We want all the DSL goodness
80
+ LaunchSpecification fn_if: 'ACondition' do
81
+ ImageId 'ami-1234'
82
+ end
83
+ end
84
+ end
85
+ json = template.to_json
86
+ expect(json).to include('"LaunchSpecifications":[{"Fn::If":["ACondition",{"ImageId":"ami-1234"},{"Ref":"AWS::NoValue"}]}]')
87
+ end
88
+ end
89
+
90
+ describe 'Top level Lists' do
91
+ it 'appends item if singular form != plural form is passed a single item' do
92
+ template.AutoScaling_AutoScalingGroup('ASG') do
93
+ AvailabilityZone 'region-2a'
94
+ AvailabilityZone 'region-2b'
95
+ end
96
+ expect(template.to_json).to include('"AvailabilityZones":["region-2a","region-2b"]')
97
+ end
98
+
99
+ # Change from prior behaviour which produced an invalid result
100
+ it 'appends multiple items if singular form != plural form is passed an array' do
101
+ template.AutoScaling_AutoScalingGroup('ASG') do
102
+ AvailabilityZone %w[region-2a region-2b]
103
+ AvailabilityZone ['region-2c']
104
+ end
105
+ expect(template.to_json).to include('"AvailabilityZones":["region-2a","region-2b","region-2c"]')
106
+ end
107
+
108
+ it 'replaces items if plural form is passed an array' do
109
+ template.AutoScaling_AutoScalingGroup('ASG') do
110
+ AvailabilityZones ['region-2z']
111
+ AvailabilityZones ['region-2a']
112
+ end
113
+ expect(template.to_json).to include('"AvailabilityZones":["region-2a"]')
114
+ end
115
+
116
+ it 'accepts a Ref in plural form' do
117
+ template.AutoScaling_AutoScalingGroup('ASG') do
118
+ AvailabilityZones Ref('AListParam')
119
+ end
120
+ expect(template.to_json).to include('"AvailabilityZones":{"Ref":"AListParam"}')
121
+ end
122
+
123
+ # This produces an invalid result (item not encapsulated as a List), so could be changed
124
+ # later
125
+ it 'replaces items if plural form is passed a single item' do
126
+ template.AutoScaling_AutoScalingGroup('ASG') do
127
+ AvailabilityZones 'region-xx'
128
+ AvailabilityZones 'region-2a'
129
+ end
130
+ expect(template.to_json).to include('"AvailabilityZones":"region-2a"')
131
+ end
132
+
133
+ it 'replaces items if list attribute is singlar, and plural form is passed an array' do
134
+ template.AutoScaling_AutoScalingGroup('ASG') do
135
+ VPCZoneIdentifiers ['subnet-9999']
136
+ VPCZoneIdentifiers ['subnet-1234']
137
+ end
138
+ expect(template.to_json).to include('"VPCZoneIdentifier":["subnet-1234"]')
139
+ end
140
+
141
+ it 'replaces items if list attribute is singlar, and plural form is passed an array' do
142
+ template.AutoScaling_AutoScalingGroup('ASG') do
143
+ VPCZoneIdentifier 'subnet-9999'
144
+ VPCZoneIdentifiers ['subnet-1234']
145
+ end
146
+ expect(template.to_json).to include('"VPCZoneIdentifier":["subnet-1234"]')
147
+ end
148
+
149
+ it 'appends items if list attribute is singlar and passed arrays' do
150
+ template.AutoScaling_AutoScalingGroup('ASG') do
151
+ VPCZoneIdentifier ['subnet-9999']
152
+ VPCZoneIdentifier ['subnet-1234']
153
+ end
154
+ expect(template.to_json).to include('"VPCZoneIdentifier":["subnet-9999","subnet-1234"]')
155
+ end
156
+
157
+ it 'appends items if plural form == singular form and passed a single item' do
158
+ template.AutoScaling_AutoScalingGroup('ASG') do
159
+ VPCZoneIdentifier 'subnet-9999'
160
+ VPCZoneIdentifier 'subnet-1234'
161
+ end
162
+ expect(template.to_json).to include('"VPCZoneIdentifier":["subnet-9999","subnet-1234"]')
163
+ end
164
+
165
+ it 'does not create plural method if both singlular and plural forms are legitimate properties' do
166
+ template.CodePipeline_Pipeline('pipeline') do
167
+ ArtifactStore do
168
+ Location 'abucketname'
169
+ Type 'S3'
170
+ end
171
+ ArtifactStores do
172
+ ArtifactStore do
173
+ Location 'a different bucket'
174
+ Type 'S3'
175
+ end
176
+ Region 'ax-eastwest-5'
177
+ end
178
+ end
179
+ json = template.to_json
180
+ expect(json).to include('"ArtifactStore":{"Location":"abucketname"')
181
+ expect(json).to include('"ArtifactStores":[{"ArtifactStore":{')
182
+ end
183
+
184
+ it 'can conditionally add a subtype to a list property' do
185
+ template.SSM_MaintenanceWindowTask('Task') do
186
+ # We want all the DSL goodness
187
+ Target fn_if: 'ACondition' do
188
+ Key 'AKey'
189
+ end
190
+ end
191
+ expect(template.to_json).to include('"Targets":[{"Fn::If":["ACondition",{"Key":"AKey"},{"Ref":"AWS::NoValue"}]}]')
192
+ end
193
+ end
194
+ end