Skip to content

Added SGR validation to cfn validate #1070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ include_trailing_comma = true
combine_as_imports = True
force_grid_wrap = 0
known_first_party = rpdk
known_third_party = boto3,botocore,cfn_tools,cfnlint,colorama,docker,hypothesis,jinja2,jsonschema,nested_lookup,ordered_set,pkg_resources,pytest,pytest_localserver,requests,setuptools,yaml
known_third_party = boto3,botocore,cfn_tools,cfnlint,colorama,docker,hypothesis,jinja2,jsonpatch,jsonschema,nested_lookup,ordered_set,pkg_resources,pytest,pytest_localserver,requests,setuptools,yaml

[tool:pytest]
# can't do anything about 3rd part modules, so don't spam us
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def find_version(*file_paths):
"boto3>=1.10.20",
"Jinja2>=3.1.2",
"markupsafe>=2.1.0",
"jsonpatch",
"jsonschema>=3.0.0,<=4.17.3",
"pytest>=4.5.0",
"pytest-random-order>=1.0.4",
Expand All @@ -56,6 +57,7 @@ def find_version(*file_paths):
"cfn_flip>=1.2.3",
"nested-lookup",
"botocore>=1.31.17",
"resource-schema-guard-rail>=0.0.12",
],
entry_points={
"console_scripts": ["cfn-cli = rpdk.core.cli:main", "cfn = rpdk.core.cli:main"]
Expand Down
2 changes: 1 addition & 1 deletion src/rpdk/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging

__version__ = "0.2.36"
__version__ = "0.2.38"

logging.getLogger(__name__).addHandler(logging.NullHandler())
1 change: 0 additions & 1 deletion src/rpdk/core/boto_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def inject_confused_deputy_headers(params, **kwargs):
params["headers"]["x-amz-source-arn"] = headers["source_arn"]

sts_client.meta.events.register("before-call", inject_confused_deputy_headers)
LOG.info(headers)
if role_arn:
session_name = f"CloudFormationContractTest-{datetime.now():%Y%m%d%H%M%S}"
try:
Expand Down
32 changes: 32 additions & 0 deletions src/rpdk/core/data/schema/base.definition.schema.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@
"AttributeList"
]
},
"relationshipRef": {
"$comment": "The relationshipRef relate a property in the resource to that in another resource",
"type": "object",
"properties": {
"typeName": {
"$comment": "Name of the related resource",
"type": "string",
"pattern": "^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$"
},
"propertyPath": {
"$comment": "Path of the property in the related resource schema",
"type": "string",
"pattern": "^(\/properties\/)[A-Za-z0-9]*$"
},
"publisherId": {
"$comment": "Id of the related third party resource publisher",
"type": "string",
"pattern": "[0-9a-zA-Z]{12,40}"
},
"majorVersion": {
"$comment": "Major version of the related resource",
"type": "integer",
"minimum": 1,
"maximum": 10000
}
},
"required": [
"typeName",
"propertyPath"
],
"additionalProperties": false
},
"$ref": {
"$ref": "http://json-schema.org/draft-07/schema#/properties/$ref"
},
Expand Down
51 changes: 50 additions & 1 deletion src/rpdk/core/data_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
import shutil
from copy import deepcopy
from io import TextIOWrapper
from pathlib import Path

Expand All @@ -12,6 +13,9 @@
from jsonschema.exceptions import RefResolutionError, ValidationError
from nested_lookup import nested_lookup

from rpdk.guard_rail.core.data_types import Stateful, Stateless
from rpdk.guard_rail.core.runner import exec_compliance

from .exceptions import InternalError, SpecValidationError
from .jsonutils.flattener import JsonSchemaFlattener
from .jsonutils.inliner import RefInliner
Expand All @@ -21,6 +25,7 @@

TIMEOUT_IN_SECONDS = 10
STDIN_NAME = "<stdin>"
MAX_CONFIGURATION_SCHEMA_LENGTH = 60 * 1024 # 60 KiB


def resource_stream(package_name, resource_name, encoding="utf-8"):
Expand Down Expand Up @@ -144,18 +149,45 @@ def get_file_base_uri(file):
return path.resolve().as_uri()


def load_resource_spec(resource_spec_file): # pylint: disable=R # noqa: C901
def sgr_stateless_eval(schema):
schema_copy = deepcopy(schema)
result = exec_compliance(Stateless([schema_copy], []))[0]
result.display()
if result.non_compliant.items() != {}:
LOG.warning("Issues detected: please see the schema compliance report above")


def sgr_stateful_eval(schema, original_schema):
result = exec_compliance(
Stateful(current_schema=schema, previous_schema=original_schema, rules=[])
)[0]
result.display()
if result.non_compliant.items() != {}:
LOG.warning("Issues detected: please see the schema compliance report above\n")


def load_resource_spec( # pylint: disable=R # noqa: C901
resource_spec_file, original_schema_raw=None
):
"""Load a resource provider definition from a file, and validate it."""
try:
resource_spec = json.load(resource_spec_file)

except ValueError as e:
LOG.debug("Resource spec decode failed", exc_info=True)
raise SpecValidationError(str(e)) from e

# check TypeConfiguration schema size
if len(json.dumps(resource_spec).encode("utf-8")) > MAX_CONFIGURATION_SCHEMA_LENGTH:
raise SpecValidationError(
"TypeConfiguration schema exceeds maximum length of 60 KiB"
)

validator = make_resource_validator()
additional_properties_validator = (
make_resource_validator_with_additional_properties_check()
)

try:
validator.validate(resource_spec)
except ValidationError as e:
Expand Down Expand Up @@ -382,6 +414,23 @@ def load_resource_spec(resource_spec_file): # pylint: disable=R # noqa: C901
LOG.debug("Inlined schema is no longer valid", exc_info=True)
raise InternalError() from e

LOG.warning(
"Resource schema metadata is valid. Running a schema compliance evaluation:\n"
)

# Run SGR checks once Schema Metadata is checked
original_resource_spec = None
if original_schema_raw:
print(
"Type Exists in CloudFormation Registry. "
"Evaluating Resource Schema Backward Compatibility Compliance",
)
original_resource_spec = json.loads(original_schema_raw)
sgr_stateful_eval(resource_spec, original_resource_spec)

print("Evaluating Resource Schema Compliance")
sgr_stateless_eval(resource_spec)

return inlined


Expand Down
11 changes: 9 additions & 2 deletions src/rpdk/core/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

def generate(args):
project = Project()
project.load()
project.load(args)
project.generate(
args.endpoint_url,
args.region,
Expand All @@ -20,7 +20,9 @@ def generate(args):
args.profile,
)
project.generate_docs()

project.generate_canary_files(
args.local_code_generation,
)
LOG.warning("Generated files for %s", project.type_name)


Expand All @@ -38,3 +40,8 @@ def setup_subparser(subparsers, parents):
"--target-schemas", help="Path to target schemas.", nargs="*", default=[]
)
parser.add_argument("--profile", help="AWS profile to use.")
parser.add_argument(
"--local-code-generation",
action="store_true",
help="Enable local code generation.",
)
Loading
Loading