Skip to content

Commit

Permalink
Merge upstream changes
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong committed Oct 10, 2022
1 parent a313f07 commit 9d68a0e
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 91 deletions.
17 changes: 13 additions & 4 deletions src/cfnlint/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,8 @@ def get_url_content(url, caching=False):

return content

def get_url_retrieve(url: str, caching: bool=False) -> str:

def get_url_retrieve(url: str, caching: bool = False) -> str:
"""Get the contents of a zip file and returns
a string representing the file
Expand All @@ -338,7 +339,7 @@ def get_url_retrieve(url: str, caching: bool=False) -> str:
# Load in all existing values
metadata = load_metadata(metadata_filename)
metadata['etag'] = res.info().get('ETag')
metadata['url'] = url # To make it obvious which url the Tag relates to
metadata['url'] = url # To make it obvious which url the Tag relates to
save_metadata(metadata, metadata_filename)

fileobject, _ = urlretrieve(url)
Expand Down Expand Up @@ -467,6 +468,7 @@ def initialize_specs():
for reg in REGIONS:
RESOURCE_SPECS[reg] = load_resource(CloudSpecs, filename=(f'{reg}.json'))


def initialize_provider_spec_modules() -> None:
"""Initialize the provider spec variables and modules.
To be dynamic we import the modules from the regions variable
Expand All @@ -480,7 +482,10 @@ def initialize_provider_spec_modules() -> None:
PROVIDER_SCHEMAS_AWS[reg] = {}
# provider specs don't exist for me-central-1 yet
if reg != 'me-central-1':
__provider_schema_modules[reg] = __import__(f'cfnlint.data.ProviderSchemas.{reg}', fromlist=[''])
__provider_schema_modules[reg] = __import__(
f'cfnlint.data.ProviderSchemas.{reg}', fromlist=['']
)


def get_provider_schema(region: str, resource_type: str) -> dict:
"""Get the provider resource shcema and cache it to speed up future lookups
Expand All @@ -494,13 +499,17 @@ def get_provider_schema(region: str, resource_type: str) -> dict:
rt = resource_type.replace('::', '-').lower()
schema = PROVIDER_SCHEMAS_AWS[region].get(resource_type)
if schema is None:
PROVIDER_SCHEMAS_AWS[region][resource_type] = load_resource(__provider_schema_modules[region], filename=(f'{rt}.json'))
PROVIDER_SCHEMAS_AWS[region][resource_type] = load_resource(
__provider_schema_modules[region], filename=(f'{rt}.json')
)
return PROVIDER_SCHEMAS_AWS[region][resource_type]
return schema


initialize_specs()
initialize_provider_spec_modules()


def format_json_string(json_string):
"""Format the given JSON string"""

Expand Down
34 changes: 25 additions & 9 deletions src/cfnlint/maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def update_resource_specs(force: bool = False):
# Patch from registry schema
pool_tuple = [(k, v, schema_cache, force) for k, v in SPEC_REGIONS.items()]
pool.starmap(update_resource_spec, pool_tuple)
provider_pool_tuple = [(k, force) for k, v in SPEC_REGIONS.items() if k != 'us-east-1']
provider_pool_tuple = [
(k, force) for k, v in SPEC_REGIONS.items() if k != 'us-east-1'
]
pool.starmap(update_provider_schema, provider_pool_tuple)
except AttributeError:
# Do it the long, slow way
Expand Down Expand Up @@ -153,7 +155,7 @@ def search_and_replace_botocore_types(obj):
json.dump(spec, f, indent=1, sort_keys=True, separators=(',', ': '))


def update_provider_schema(region, force: bool=False) -> None:
def update_provider_schema(region, force: bool = False) -> None:
"""Update the provider schemas from the AWS websites
Args:
Expand All @@ -166,7 +168,9 @@ def update_provider_schema(region, force: bool=False) -> None:
suffix = '.cn' if region in ['cn-north-1', 'cn-northwest-1'] else ''
url = f'https://schema.cloudformation.{region}.amazonaws.com{suffix}/CloudformationSchema.zip'

directory = os.path.join(os.path.dirname(cfnlint.__file__), f'data/ProviderSchemas/{region}/')
directory = os.path.join(
os.path.dirname(cfnlint.__file__), f'data/ProviderSchemas/{region}/'
)

multiprocessing_logger = multiprocessing.log_to_stderr()

Expand All @@ -184,18 +188,30 @@ def update_provider_schema(region, force: bool=False) -> None:
# if the region is not us-east-1 compare the files to those in us-east-1
# symlink if the files are the same
if region != 'us-east-1':
directory_us_east_1 = os.path.join(os.path.dirname(cfnlint.__file__), 'data/ProviderSchemas/us-east-1/')
directory_us_east_1 = os.path.join(
os.path.dirname(cfnlint.__file__), 'data/ProviderSchemas/us-east-1/'
)
for filename in os.listdir(directory):
if filename != '__init__.py':
try:
if filecmp.cmp(f'{directory}{filename}', f'{directory_us_east_1}{filename}'):
if filecmp.cmp(
f'{directory}{filename}', f'{directory_us_east_1}{filename}'
):
os.remove(f'{directory}{filename}')
os.symlink(f'{directory_us_east_1}{filename}', f'{directory}{filename}')
except Exception as e: #pylint: disable=broad-except
os.symlink(
f'{directory_us_east_1}{filename}',
f'{directory}{filename}',
)
except Exception as e: # pylint: disable=broad-except
# Exceptions will typically be the file doesn't exist in us-east-1
multiprocessing_logger.debug('Issuing comparing %s into %s: %s', f'{directory}{filename}', f'{directory_us_east_1}{filename}', e)
multiprocessing_logger.debug(
'Issuing comparing %s into %s: %s',
f'{directory}{filename}',
f'{directory_us_east_1}{filename}',
e,
)

except Exception as e: #pylint: disable=broad-except
except Exception as e: # pylint: disable=broad-except
multiprocessing_logger.debug('Issuing updating specs for %s', region)


Expand Down
52 changes: 0 additions & 52 deletions src/cfnlint/rules/resources/ResourceSchema.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class RetentionPeriodOnResourceTypesWithAutoExpiringContent(CloudFormationLintRu
tags = ['resources', 'retentionperiod']

def _check_ref(self, value, parameters, resources, path): # pylint: disable=W0613
print(value)
pass

def match(self, cfn):
"""Check for RetentionPeriod"""
Expand Down
51 changes: 41 additions & 10 deletions src/cfnlint/rules/resources/properties/JsonSchema.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
from cfnlint.rules import CloudFormationLintRule
from cfnlint.rules import RuleMatch
import cfnlint.helpers
from cfnlint.helpers import REGEX_DYN_REF, PSEUDOPARAMS, FN_PREFIX, UNCONVERTED_SUFFIXES, REGISTRY_SCHEMAS
from cfnlint.helpers import (
REGEX_DYN_REF,
PSEUDOPARAMS,
FN_PREFIX,
UNCONVERTED_SUFFIXES,
REGISTRY_SCHEMAS,
)

LOGGER = logging.getLogger('cfnlint.rules.resources.properties.JsonSchema')

# pylint: disable=too-many-instance-attributes
class RuleSet(object):

def __init__(self):
self.additionalProperties = 'E3002'
self.required = 'E3003'
Expand All @@ -34,8 +39,10 @@ def __init__(self):
self.pattern = 'E3031'
self.oneOf = 'E2523'


class JsonSchema(CloudFormationLintRule):
"""Check Base Resource Configuration"""

id = 'E3000'
shortdesc = 'Parent rule for doing JSON schema validation'
description = 'Making sure that resources properties comply with their JSON schema'
Expand All @@ -62,7 +69,7 @@ def __init__(self):
self.rules = RuleSet()
self.config_definition = {'enabled': {'default': False, 'type': 'boolean'}}

#pylint: disable=unused-argument
# pylint: disable=unused-argument
def validate_required(self, validator, required, instance, schema):
if not validator.is_type(instance, 'object'):
return
Expand Down Expand Up @@ -119,7 +126,13 @@ def json_schema_validate(self, validator, properties, path):
if hasattr(e, 'extra_args'):
kwargs = getattr(e, 'extra_args')
matches.append(
RuleMatch(path + list(e.path), e.message, rule=self.child_rules[getattr(self.rules, e.validator)], **kwargs))
RuleMatch(
path + list(e.path),
e.message,
rule=self.child_rules[getattr(self.rules, e.validator)],
**kwargs,
)
)

return matches

Expand Down Expand Up @@ -160,14 +173,27 @@ def match(self, cfn):

for schema in REGISTRY_SCHEMAS:
resource_type = schema['typeName']
for resource_name, resource_values in cfn.get_resources([resource_type]).items():
for resource_name, resource_values in cfn.get_resources(
[resource_type]
).items():
properties = resource_values.get('Properties', {})
# ignoring resources with CloudFormation template syntax in Properties
if not re.match(REGEX_DYN_REF, str(properties)) and not any(x in str(properties) for x in PSEUDOPARAMS + UNCONVERTED_SUFFIXES) and FN_PREFIX not in str(properties):
if (
not re.match(REGEX_DYN_REF, str(properties))
and not any(
x in str(properties)
for x in PSEUDOPARAMS + UNCONVERTED_SUFFIXES
)
and FN_PREFIX not in str(properties)
):
try:
jsonschema.validate(properties, schema)
except jsonschema.ValidationError as e:
matches.append(RuleMatch(['Resources', resource_name, 'Properties'], e.message))
matches.append(
RuleMatch(
['Resources', resource_name, 'Properties'], e.message
)
)

if not self.config.get('enabled'):
return matches
Expand All @@ -191,9 +217,14 @@ def match(self, cfn):
schema = self.clense_schema(deepcopy(schema))
cfn_validator = self.validator(schema)
path = ['Resources', n, 'Properties']
for scenario in cfn.get_object_without_nested_conditions(p, path):
matches.extend(self.json_schema_validate(
cfn_validator, scenario.get('Object'), path))
for scenario in cfn.get_object_without_nested_conditions(
p, path
):
matches.extend(
self.json_schema_validate(
cfn_validator, scenario.get('Object'), path
)
)

return matches

Expand Down
31 changes: 23 additions & 8 deletions src/cfnlint/rules/resources/properties/ValuePrimitiveType.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,10 @@ def match_resource_properties(self, properties, resource_type, path, cfn):

return matches

#pylint: disable=too-many-return-statements
def _schema_value_check(self, value: Any, item_type: str, strict_check: bool) -> bool:
# pylint: disable=too-many-return-statements
def _schema_value_check(
self, value: Any, item_type: str, strict_check: bool
) -> bool:
"""Checks non strict"""
if not strict_check:
try:
Expand Down Expand Up @@ -296,7 +298,7 @@ def _schema_check_primitive_type(self, value: Any, types: List[str]) -> bool:

return result

#pylint: disable=unused-argument
# pylint: disable=unused-argument
def validate(self, validator, types, instance, schema):
types = ensure_list(types)
reprs = ', '.join(repr(type) for type in types)
Expand All @@ -308,29 +310,42 @@ def validate(self, validator, types, instance, schema):
for t in types:
if t == 'array':
return
yield ValidationError(f'{instance!r} is not of type {reprs}', extra_args={})
yield ValidationError(
f'{instance!r} is not of type {reprs}', extra_args={}
)
elif k in FUNCTIONS:
for t in types:
if t in ['string', 'integer', 'boolane']:
return
yield ValidationError(f'{instance!r} is not of type {reprs}', extra_args={})
yield ValidationError(
f'{instance!r} is not of type {reprs}', extra_args={}
)
else:
yield ValidationError(f'{instance!r} is not of type {reprs}', extra_args={})
yield ValidationError(
f'{instance!r} is not of type {reprs}', extra_args={}
)
if not self._schema_check_primitive_type(instance, types):
extra_args = {
'actual_type': type(instance).__name__,
'expected_type': reprs,
}
yield ValidationError(f'{instance!r} is not of type {reprs}', extra_args=extra_args)
yield ValidationError(
f'{instance!r} is not of type {reprs}', extra_args=extra_args
)


class ValidationError(jsonschema.ValidationError):
def __init__(self, message, extra_args):
super(ValidationError, self).__init__(message)
self.extra_args = extra_args


def ensure_list(thing):
"""
Wrap ``thing`` in a list if it's a single str.
Otherwise, return it unchanged.
"""
"""

if isinstance(thing, str):
return [thing]
return thing
1 change: 0 additions & 1 deletion src/cfnlint/rules/resources/properties/ValueRefGetAtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ def check_value_getatt(self, value, path, **kwargs):
)
)
elif '.'.join(map(str, resource_attribute)) != specs[resource_type]:
print(resource_type)
message = (
'Property "{0}" can Fn::GetAtt to a resource attribute "{1}" at {2}'
)
Expand Down
5 changes: 1 addition & 4 deletions src/cfnlint/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,6 @@ def get_location_yaml(self, text, path):
"""
LOGGER.debug('Get location of path %s', path)
result = None
error = False
if not path:
result = self._loc(text)
elif len(path) > 1:
Expand All @@ -622,18 +621,16 @@ def get_location_yaml(self, text, path):
try:
result = self._loc(text[path[0]])
except AttributeError as err:
error = True
LOGGER.debug(err)
else:
try:
for key in text:
if key == path[0]:
result = self._loc(key)
except AttributeError as err:
error = True
LOGGER.debug(err)

return result, error
return result

def check_resource_property(
self,
Expand Down
Loading

0 comments on commit 9d68a0e

Please sign in to comment.