cfndsl 0.12.11 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,22 @@
1
+ # Global variables to adjust CfnDsl behavior
1
2
  module CfnDsl
2
- # Set global variables
3
- class Globals
4
- class << self
5
- def reserved_items
6
- %w[Resource Parameter Output].freeze
7
- end
8
- end
3
+ module_function
4
+
5
+ def disable_binding
6
+ @disable_binding = true
7
+ end
8
+
9
+ def disable_binding?
10
+ @disable_binding
11
+ end
12
+
13
+ def specification_file(file = nil)
14
+ @specification_file = file if file
15
+ @specification_file ||= File.join(ENV['HOME'], '.cfndsl/resource_specification.json')
16
+ @specification_file
17
+ end
18
+
19
+ def reserved_items
20
+ %w[Resource Parameter Output].freeze
9
21
  end
10
22
  end
@@ -28,7 +28,7 @@ module CfnDsl
28
28
  resource_name = create_resource_def(resource, info)
29
29
  parts = resource.split('::')
30
30
  until parts.empty?
31
- break if Globals.reserved_items.include? parts.first
31
+ break if CfnDsl.reserved_items.include? parts.first
32
32
  abreve_name = parts.join('_')
33
33
  if accessors.key? abreve_name
34
34
  accessors.delete abreve_name # Delete potentially ambiguous names
@@ -0,0 +1,98 @@
1
+ module CfnDsl
2
+ # Module for handling inconsistencies in the published resource specification from AWS
3
+ module Patches
4
+ # Missing/malformed resources from the resource specification
5
+ # rubocop:disable Metrics/MethodLength
6
+ def self.resources
7
+ {
8
+ 'AWS::Serverless::Function' => {
9
+ 'Properties' => {
10
+ 'Handler' => { 'PrimitiveType' => 'String' },
11
+ 'Runtime' => { 'PrimitiveType' => 'String' },
12
+ 'CodeUri' => { 'PrimitiveType' => 'String' },
13
+ 'Description' => { 'PrimitiveType' => 'String' },
14
+ 'MemorySize' => { 'PrimitiveType' => 'Integer' },
15
+ 'Timeout' => { 'PrimitiveType' => 'Integer' },
16
+ 'Environment' => { 'PrimitiveType' => 'Json' },
17
+ 'Events' => { 'PrimitiveType' => 'Json' },
18
+ 'Policies' => { 'Type' => 'List', 'ItemType' => 'Policy' }
19
+ }
20
+ },
21
+ 'AWS::Serverless::Api' => {
22
+ 'Properties' => {
23
+ 'StageName' => { 'PrimitiveType' => 'String' },
24
+ 'DefinitionUri' => { 'PrimitiveType' => 'String' },
25
+ 'CacheClusterEnabled' => { 'PrimitiveType' => 'Boolean' },
26
+ 'CacheClusterSize' => { 'PrimitiveType' => 'String' },
27
+ 'Variables' => { 'PrimitiveType' => 'Json' }
28
+ }
29
+ },
30
+ 'AWS::Serverless::SimpleTable' => {
31
+ 'Properties' => {
32
+ 'PrimaryKey' => { 'Type' => 'PrimaryKey' },
33
+ 'ProvisionedThroughput' => { 'Type' => 'ProvisionedThroughput' }
34
+ }
35
+ },
36
+ 'AWS::SSM::Parameter' => {
37
+ 'Properties' => {
38
+ 'Name' => { 'PrimitiveType' => 'String' },
39
+ 'Description' => { 'PrimitiveType' => 'String' },
40
+ 'Type' => { 'PrimitiveType' => 'String' },
41
+ 'Value' => { 'PrimitiveType' => 'String' }
42
+ }
43
+ },
44
+ 'AWS::EC2::VPNGatewayConnection' => {
45
+ 'Properties' => {
46
+ 'Type' => { 'PrimitiveType' => 'String' },
47
+ 'Tags' => { 'Type' => 'List', 'ItemType' => 'Tag' }
48
+ }
49
+ },
50
+ 'AWS::EC2::EIPAssociation' => {
51
+ 'Properties' => {
52
+ 'AllocationId' => { 'PrimitiveType' => 'String' },
53
+ 'EIP' => { 'PrimitiveType' => 'String' },
54
+ 'InstanceId' => { 'PrimitiveType' => 'String' },
55
+ 'NetworkInterfaceId' => { 'PrimitiveType' => 'String' },
56
+ 'PrivateIpAddress' => { 'PrimitiveType' => 'String' }
57
+ }
58
+ },
59
+ 'AWS::Config::ConfigurationRecorder' => {
60
+ 'Properties' => {
61
+ 'Name' => { 'PrimitiveType' => 'String' },
62
+ 'RecordingGroup' => { 'Type' => 'RecordingGroup' },
63
+ 'RoleARN' => { 'PrimitiveType' => 'String' }
64
+ }
65
+ }
66
+ }
67
+ end
68
+
69
+ # Missing/malformed types from the resource specification
70
+ def self.types
71
+ {
72
+ 'AWS::Serverless::SimpleTable.PrimaryKey' => {
73
+ 'Properties' => {
74
+ 'Name' => { 'PrimitiveType' => 'String' },
75
+ 'Type' => { 'PrimitiveType' => 'String' }
76
+ }
77
+ },
78
+ 'AWS::Serverless::SimpleTable.ProvisionedThroughput' => {
79
+ 'Properties' => {
80
+ 'ReadCapacityUnits' => { 'PrimitiveType' => 'Integer' },
81
+ 'WriteCapacityUnits' => { 'PrimitiveType' => 'Integer' }
82
+ }
83
+ },
84
+ 'AWS::Serverless::Function.Policy' => {
85
+ 'Properties' => {
86
+ 'PolicyDocument' => { 'PrimitiveType' => 'Json' },
87
+ 'PolicyName' => { 'PrimitiveType' => 'String' }
88
+ }
89
+ },
90
+ 'AWS::Cognito::IdentityPoolRoleAttachment.RulesConfigurationType' => {
91
+ 'Properties' => {
92
+ 'Rules' => { 'Type' => 'List', 'ItemType' => 'MappingRule' }
93
+ }
94
+ }
95
+ }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,89 @@
1
+ module CfnDsl
2
+ # Helper module for bridging the gap between a static types file included in the repo
3
+ # and dynamically generating the types directly from the AWS specification
4
+ module Specification
5
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
6
+ def self.extract_resources(spec)
7
+ spec.each_with_object({}) do |(resource_name, resource_info), resources|
8
+ properties = resource_info['Properties'].each_with_object({}) do |(property_name, property_info), extracted|
9
+ # some json incorrectly labelled as Type -> Json instead of PrimitiveType
10
+ # also, AWS now has the concept of Map which cfndsl had never defined
11
+ if property_info['Type'] == 'Map' || property_info['Type'] == 'Json'
12
+ property_type = 'Json'
13
+ elsif property_info['PrimitiveType']
14
+ property_type = property_info['PrimitiveType']
15
+ elsif property_info['PrimitiveItemType']
16
+ property_type = Array(property_info['PrimitiveItemType'])
17
+ elsif property_info['ItemType']
18
+ # Tag is a reused type, but not quite primitive
19
+ # and not all resources use the general form
20
+ property_type = if property_info['ItemType'] == 'Tag'
21
+ ['Tag']
22
+ else
23
+ Array(resource_name.split('::').join + property_info['ItemType'])
24
+ end
25
+ elsif property_info['Type']
26
+ # Special types (defined below) are joined with their parent
27
+ # resource name for uniqueness and connection
28
+ property_type = resource_name.split('::').join + property_info['Type']
29
+ else
30
+ warn "could not extract type from #{resource_name}"
31
+ end
32
+ extracted[property_name] = property_type
33
+ extracted
34
+ end
35
+ resources[resource_name] = { 'Properties' => properties }
36
+ resources
37
+ end
38
+ end
39
+
40
+ def self.extract_types(spec)
41
+ primitive_types = {
42
+ 'String' => 'String',
43
+ 'Boolean' => 'Boolean',
44
+ 'Json' => 'Json',
45
+ 'Integer' => 'Integer',
46
+ 'Number' => 'Number',
47
+ 'Double' => 'Double',
48
+ 'Timestamp' => 'Timestamp',
49
+ 'Long' => 'Long'
50
+ }
51
+ spec.each_with_object(primitive_types) do |(property_name, property_info), types|
52
+ # In order to name things uniquely and allow for connections
53
+ # we extract the resource name from the property
54
+ # AWS::IAM::User.Policy becomes AWSIAMUserPolicy
55
+ root_resource = property_name.match(/(.*)\./)
56
+ root_resource_name = root_resource ? root_resource[1].gsub(/::/, '') : property_name
57
+ property_name = property_name.gsub(/::|\./, '')
58
+ next unless property_info['Properties']
59
+ properties = property_info['Properties'].each_with_object({}) do |(nested_prop_name, nested_prop_info), extracted|
60
+ if nested_prop_info['Type'] == 'Map' || nested_prop_info['Type'] == 'Json'
61
+ # The Map type and the incorrectly labelled Json type
62
+ nested_prop_type = 'Json'
63
+ elsif nested_prop_info['PrimitiveType']
64
+ nested_prop_type = nested_prop_info['PrimitiveType']
65
+ elsif nested_prop_info['PrimitiveItemType']
66
+ nested_prop_type = Array(nested_prop_info['PrimitiveItemType'])
67
+ elsif nested_prop_info['ItemType']
68
+ nested_prop_type = root_resource_name + nested_prop_info['ItemType']
69
+ elsif nested_prop_info['Type']
70
+ nested_prop_type = root_resource_name + nested_prop_info['Type']
71
+ else
72
+ warn "could not extract type from #{property_name}"
73
+ end
74
+ extracted[nested_prop_name] = nested_prop_type
75
+ extracted
76
+ end
77
+ types[property_name] = properties
78
+ types
79
+ end
80
+ end
81
+
82
+ def self.extract_from_resource_spec!
83
+ spec_file = JSON.parse File.read(CfnDsl.specification_file)
84
+ resources = extract_resources spec_file['ResourceTypes'].merge(Patches.resources)
85
+ types = extract_types spec_file['PropertyTypes'].merge(Patches.types)
86
+ { 'Resources' => resources, 'Types' => types }
87
+ end
88
+ end
89
+ end
@@ -10,9 +10,12 @@ module CfnDsl
10
10
  module Types
11
11
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
12
12
  def self.included(type_def)
13
- types_list = YAML.safe_load(File.open("#{File.dirname(__FILE__)}/#{type_def::TYPE_PREFIX}/types.yaml"))
13
+ types_list = if type_def::TYPE_PREFIX == 'aws'
14
+ Specification.extract_from_resource_spec!
15
+ else
16
+ YAML.safe_load(File.open("#{File.dirname(__FILE__)}/#{type_def::TYPE_PREFIX}/types.yaml"))
17
+ end
14
18
  type_def.const_set('Types_Internal', types_list)
15
-
16
19
  # Do a little sanity checking - all of the types referenced in Resources
17
20
  # should be represented in Types
18
21
  types_list['Resources'].keys.each do |resource_name|
@@ -1,3 +1,3 @@
1
1
  module CfnDsl
2
- VERSION = '0.12.11'.freeze
2
+ VERSION = '0.13.0'.freeze
3
3
  end
@@ -33,7 +33,7 @@ describe CfnDsl::CloudFormationTemplate do
33
33
  end
34
34
 
35
35
  it 'Serverless_Api' do
36
- template.Serverless_API(:Test) do
36
+ template.Serverless_Api(:Test) do
37
37
  StageName 'prod'
38
38
  DefinitionUri 'swagger.yml'
39
39
  CacheClusterEnabled false
@@ -89,7 +89,7 @@ describe CfnDsl::CloudFormationTemplate do
89
89
  PolicyDocument(a: 7)
90
90
  end
91
91
 
92
- expect(result2).to be_a(CfnDsl::AWS::Types::IAMEmbeddedPolicy)
92
+ expect(result2).to be_a(CfnDsl::AWS::Types::AWSIAMUserPolicy)
93
93
  expect(user.instance_variable_get('@Properties')['Policies'].value.length).to eq(2)
94
94
  end
95
95
 
@@ -13,6 +13,8 @@ describe 'cfndsl', type: :aruba do
13
13
  -D, --define "VARIABLE=VALUE" Directly set local VARIABLE as VALUE
14
14
  -v, --verbose Turn on verbose ouptut
15
15
  -b, --disable-binding Disable binding configuration
16
+ -s, --specification-file FILE Location of Cloudformation Resource Specification file
17
+ -u, --update-specification Update the Cloudformation Resource Specification file
16
18
  -h, --help Display this screen
17
19
  USAGE
18
20
  end
@@ -27,6 +29,17 @@ describe 'cfndsl', type: :aruba do
27
29
 
28
30
  before(:each) { write_file('template.rb', template_content) }
29
31
 
32
+ context 'cfndsl -u' do
33
+ it 'updates the specification file' do
34
+ run 'cfndsl -u'
35
+ expect(last_command_started).to have_output_on_stderr(<<-OUTPUT.gsub(/^ {8}/, '').chomp)
36
+ Updating specification file
37
+ Specification successfully written to #{ENV['HOME']}/.cfndsl/resource_specification.json
38
+ OUTPUT
39
+ expect(last_command_started).to have_exit_status(0)
40
+ end
41
+ end
42
+
30
43
  context 'cfndsl' do
31
44
  it 'displays the usage' do
32
45
  run 'cfndsl'
@@ -134,6 +147,7 @@ describe 'cfndsl', type: :aruba do
134
147
  it 'displays the variables as they are interpolated in the CloudFormation template' do
135
148
  run_simple 'cfndsl template.rb --yaml params.yaml --verbose'
136
149
  verbose = /
150
+ Using \s specification \s file .* \.json \n
137
151
  Loading \s YAML \s file \s .* params\.yaml \n
138
152
  Setting \s local \s variable \s DESC \s to \s yaml \n
139
153
  Loading \s template \s file \s .* template.rb \n
@@ -1 +1 @@
1
- {"AWSTemplateFormatVersion":"2010-09-09","Resources":{"Test":{"Properties":{"StageName":"prod","DefinitionUri":"swagger.yml","CacheClusterEnabled":false,"CacheClusterSize":"512M","Variables":{"Var1":"value1"}},"Type":"AWS::Serverless::API"}}}
1
+ {"AWSTemplateFormatVersion":"2010-09-09","Resources":{"Test":{"Properties":{"StageName":"prod","DefinitionUri":"swagger.yml","CacheClusterEnabled":false,"CacheClusterSize":"512M","Variables":{"Var1":"value1"}},"Type":"AWS::Serverless::Api"}}}
@@ -9,6 +9,9 @@ if ENV['CFNDSL_COV']
9
9
  end
10
10
  end
11
11
 
12
+ require 'cfndsl/globals'
13
+ CfnDsl.specification_file File.expand_path('../../lib/cfndsl/aws/resource_specification.json', __FILE__)
14
+ # use local fixture for tests
12
15
  require 'cfndsl'
13
16
 
14
17
  bindir = File.expand_path('../../bin', __FILE__)
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ # This is a somewhat temporary test class to compare functionality
4
+ # between the AWS, OS and new ways of defining types
5
+ RSpec.describe 'Type Definitions' do
6
+ aws_spec = YAML.load_file File.expand_path('../../lib/cfndsl/aws/types.yaml', __FILE__)
7
+ os_spec = YAML.load_file File.expand_path('../../lib/cfndsl/os/types.yaml', __FILE__)
8
+ new_spec = CfnDsl::Specification.extract_from_resource_spec!
9
+
10
+ { 'AWS' => aws_spec, 'OS' => os_spec, 'New' => new_spec }.each_pair do |cloud, specdef|
11
+ context cloud do
12
+ resources = specdef['Resources']
13
+ types = specdef['Types']
14
+
15
+ context 'Resources' do
16
+ resources.each do |name, info|
17
+ it "#{name} has all property types defined" do
18
+ properties = info['Properties']
19
+ properties.each do |_, type|
20
+ type = type.first if type.is_a?(Array)
21
+ expect(types).to have_key(type)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'Types' do
28
+ types.each do |name, type|
29
+ it "#{name} has all property types defined" do
30
+ type = type.first if type.is_a?(Array)
31
+ if type.is_a?(String)
32
+ expect(types).to have_key(type)
33
+ elsif type.is_a?(Hash)
34
+ type.values.flatten.each { |t| expect(types).to have_key(t) }
35
+ else
36
+ raise "A defined type should only be of the form String, Array or Hash, got #{type.class}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfndsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.11
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Jack
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-10 00:00:00.000000000 Z
12
+ date: 2017-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -46,13 +46,13 @@ files:
46
46
  - cfndsl.gemspec
47
47
  - lib/cfndsl.rb
48
48
  - lib/cfndsl/aws/cloud_formation_template.rb
49
+ - lib/cfndsl/aws/resource_specification.json
49
50
  - lib/cfndsl/aws/types.rb
50
51
  - lib/cfndsl/aws/types.yaml
51
52
  - lib/cfndsl/conditions.rb
52
53
  - lib/cfndsl/creation_policy.rb
53
54
  - lib/cfndsl/errors.rb
54
55
  - lib/cfndsl/external_parameters.rb
55
- - lib/cfndsl/generate_types.rb
56
56
  - lib/cfndsl/globals.rb
57
57
  - lib/cfndsl/json_serialisable_object.rb
58
58
  - lib/cfndsl/jsonable.rb
@@ -65,11 +65,13 @@ files:
65
65
  - lib/cfndsl/os/types.yaml
66
66
  - lib/cfndsl/outputs.rb
67
67
  - lib/cfndsl/parameters.rb
68
+ - lib/cfndsl/patches.rb
68
69
  - lib/cfndsl/plurals.rb
69
70
  - lib/cfndsl/properties.rb
70
71
  - lib/cfndsl/rake_task.rb
71
72
  - lib/cfndsl/ref_check.rb
72
73
  - lib/cfndsl/resources.rb
74
+ - lib/cfndsl/specification.rb
73
75
  - lib/cfndsl/types.rb
74
76
  - lib/cfndsl/update_policy.rb
75
77
  - lib/cfndsl/version.rb
@@ -114,6 +116,7 @@ files:
114
116
  - spec/spec_helper.rb
115
117
  - spec/support/shared_examples/orchestration_template.rb
116
118
  - spec/transform_spec.rb
119
+ - spec/types_definition_spec.rb
117
120
  homepage: https://github.com/stevenjack/cfndsl
118
121
  licenses:
119
122
  - MIT
@@ -165,3 +168,4 @@ test_files:
165
168
  - spec/spec_helper.rb
166
169
  - spec/support/shared_examples/orchestration_template.rb
167
170
  - spec/transform_spec.rb
171
+ - spec/types_definition_spec.rb
@@ -1,154 +0,0 @@
1
- require 'yaml'
2
- require 'cfndsl/jsonable'
3
- require 'cfndsl/plurals'
4
- require 'cfndsl/names'
5
-
6
- module CfnDsl
7
- # Type generation helper
8
- module GenerateTypes
9
- # declare classes for all of the types with named methods for setting the values
10
- class Type < JSONable
11
- end
12
-
13
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
14
- def generate_types(filename)
15
- types = YAML.safe_load(File.open(filename))
16
- const_set('Types_Internal', types)
17
-
18
- validate_types(types)
19
-
20
- classes = {}
21
-
22
- # Go through and declare all of the types first
23
- types['Types'].each_key do |typename|
24
- if !const_defined?(typename)
25
- klass = const_set(typename, Class.new(self))
26
- classes[typename] = klass
27
- else
28
- classes[typename] = const_get(typename)
29
- end
30
- end
31
-
32
- # Now go through them again and define attribute setter methods
33
- classes.each_pair do |typename, type|
34
- typeval = types['Types'][typename]
35
- next unless typeval.respond_to?(:each_pair)
36
- typeval.each_pair do |attr_name, attr_type|
37
- if attr_type.is_a?(Array)
38
- klass = const_get(attr_type[0])
39
- variable = "@#{attr_name}".to_sym
40
-
41
- method = CfnDsl::Plurals.singularize(attr_name)
42
- methods = attr_name
43
- all_methods = CfnDsl.method_names(method) + CfnDsl.method_names(methods)
44
- type.class_eval do
45
- all_methods.each do |method_name|
46
- define_method(method_name) do |value = nil, *rest, &block|
47
- existing = instance_variable_get(variable)
48
- # For no-op invocations, get out now
49
- return existing if value.nil? && rest.empty? && !block
50
-
51
- # We are going to modify the value in some
52
- # way, make sure that we have an array to mess
53
- # with if we start with nothing
54
- existing = instance_variable_set(variable, []) unless existing
55
-
56
- # special case for just a block, no args
57
- if value.nil? && rest.empty? && block
58
- val = klass.new
59
- existing.push val
60
- value.instance_eval(&block(val))
61
- return existing
62
- end
63
-
64
- # Glue all of our parameters together into
65
- # a giant array - flattening one level deep, if needed
66
- array_params = []
67
- if value.is_a?(Array)
68
- value.each { |x| array_params.push x }
69
- else
70
- array_params.push value
71
- end
72
- unless rest.empty?
73
- rest.each do |v|
74
- if v.is_a?(Array)
75
- array_params += rest
76
- else
77
- array_params.push v
78
- end
79
- end
80
- end
81
-
82
- # Here, if we were given multiple arguments either
83
- # as method [a,b,c], method(a,b,c), or even
84
- # method( a, [b], c) we end up with
85
- # array_params = [a,b,c]
86
- #
87
- # array_params will have at least one item
88
- # unless the user did something like pass in
89
- # a bunch of empty arrays.
90
- if block
91
- # TODO: Is this a bug? We don't do anything with the array conetns
92
- array_params.each do |_array_params_value|
93
- value = klass.new
94
- existing.push value
95
- value.instance_eval(&block(val)) if block
96
- end
97
- else
98
- # List of parameters with no block -
99
- # hope that the user knows what he is
100
- # doing and stuff them into our existing
101
- # array
102
- # TODO: Is this a bug? We don't do anything with the array conetns
103
- array_params.each do |_array_params_value|
104
- existing.push value
105
- end
106
- end
107
- return existing
108
- end
109
- end
110
- end
111
- else
112
- klass = const_get(attr_type)
113
- variable = "@#{attr_name}".to_sym
114
-
115
- type.class_eval do
116
- CfnDsl.method_names(attr_name) do |inner_method|
117
- define_method(inner_method) do |value = nil, *_rest, &block|
118
- value ||= klass.new
119
- instance_variable_set(variable, value)
120
- value.instance_eval(&block) if block
121
- value
122
- end
123
- end
124
- end
125
- end
126
- end
127
- end
128
- end
129
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
130
-
131
- private
132
-
133
- # Do a little sanity checking - all of the types referenced in Resources
134
- # should be represented in Types
135
- def validate_types(types)
136
- types['Resources'].values.each do |resource|
137
- resource.values.each do |thing|
138
- thing.values.flatten.each do |type|
139
- puts "unknown type #{type}" unless types['Types'].key?(type)
140
- end
141
- end
142
- end
143
-
144
- # All of the type values should also be references
145
- types['Types'].values do |type|
146
- next unless type.respond_to?(:values)
147
-
148
- type.values.each do |tv|
149
- puts "unknown type #{tv}" unless types['Types'].key?(tv)
150
- end
151
- end
152
- end
153
- end
154
- end