Skip to content
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

Call cfn-lint for module fragments #644

Merged
merged 4 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ cfn test --enforce-timeout 60 #set the RL handler timeout to 60 seconds and CUD
cfn test --enforce-timeout 60 -- -k contract_delete_update # combine args
```

### Command: validate

To validate the schema, use the `validate` command.

This command is automatically run whenever one attempts to submit a resource or module. Any module fragments will be automatically validated via [`cfn-lint`](https://github.com/aws-cloudformation/cfn-python-lint/), however any warnings or errors detected by [`cfn-lint`](https://github.com/aws-cloudformation/cfn-python-lint/) will not cause this step to fail.

```bash
cfn validate
```

### Command: build-image

To build an image for a resource type. This image provides a minimalistic execution environment for the resource handler that does not depend on AWS Lambda in anyway. This image can be used during cfn invoke and cfn test instead of using sam cli.
Expand Down
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,colorama,docker,hypothesis,jinja2,jsonschema,ordered_set,pkg_resources,pytest,pytest_localserver,setuptools,yaml
known_third_party = boto3,botocore,cfnlint,colorama,docker,hypothesis,jinja2,jsonschema,ordered_set,pkg_resources,pytest,pytest_localserver,setuptools,yaml

[tool:pytest]
# can't do anything about 3rd part modules, so don't spam us
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def find_version(*file_paths):
"colorama>=0.4.1",
"docker>=4.3.1",
"ordered-set>=4.0.2",
"cfn-lint>=0.43.0",
],
entry_points={
"console_scripts": ["cfn-cli = rpdk.core.cli:main", "cfn = rpdk.core.cli:main"]
Expand Down
36 changes: 36 additions & 0 deletions src/rpdk/core/fragment/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import os
from pathlib import Path

import cfnlint.config
import cfnlint.core
import yaml

from rpdk.core.data_loaders import resource_json
Expand Down Expand Up @@ -75,11 +77,45 @@ def validate_fragments(self):
self.__validate_no_transforms_present(raw_fragments)
self.__validate_outputs(raw_fragments)
self.__validate_mappings(raw_fragments)
lint_warnings = self.__validate_fragment_through_cfn_lint(raw_fragments)
if not lint_warnings:
LOG.warning("Module fragment is valid.")
else:
LOG.warning(
"Module fragment is probably valid, but there are "
"warnings/errors from cfn-lint "
"(https://github.com/aws-cloudformation/cfn-python-lint):"
)
for lint_warning in lint_warnings:
print(
"\t{} (from rule {})".format(lint_warning.message, lint_warning.rule),
)
miparnisari marked this conversation as resolved.
Show resolved Hide resolved

def __validate_outputs(self, raw_fragments):
self.__validate_no_exports_present(raw_fragments)
self.__validate_output_limit(raw_fragments)

@staticmethod
def __validate_fragment_through_cfn_lint(raw_fragment):
filename = "temporary_fragment.json"

with open(filename, "w") as outfile:
json.dump(raw_fragment, outfile, indent=4)

template = cfnlint.decode.cfn_json.load(filename)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use the yaml loads() so we don't have to write to a temporary file: https://github.com/aws-cloudformation/cfn-python-lint/blob/master/src/cfnlint/decode/cfn_yaml.py#L183?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that cfnlint.core.run_checks needs a filename to run. I haven't seen an api that validates a template in memory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is filename being used anywhere?

if it's really necessary recommend: https://docs.python.org/3/library/tempfile.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jotompki yep, it's used by cfnlint.core.run_checks. I tried using NamedTemporaryFile, I don't see the benefit, I have to write the file, then read it back, and then manually remove it?


# Initialize the ruleset to be applied (no overrules, no excludes)
# Runs Warning and Error rules
rules = cfnlint.core.get_rules([], [], [], [], False, [])

regions = ["us-east-1"]
MalikAtalla-AWS marked this conversation as resolved.
Show resolved Hide resolved

matches = cfnlint.core.run_checks(filename, template, rules, regions)

os.remove(filename)

return matches

@staticmethod
def __validate_no_exports_present(raw_fragments):
if "Outputs" in raw_fragments:
Expand Down
1 change: 1 addition & 0 deletions src/rpdk/core/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ def load(self):
LOG.info("Validating your resource specification...")
try:
self.load_schema()
LOG.warning("Resource schema is valid.")
except FileNotFoundError as e:
self._raise_invalid_project("Resource specification not found.", e)
except SpecValidationError as e:
Expand Down
2 changes: 0 additions & 2 deletions src/rpdk/core/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ def validate(_args):
project = Project()
project.load()

LOG.warning("Resource schema for %s is valid", project.type_name)


def setup_subparser(subparsers, parents):
parser = subparsers.add_parser("validate", description=__doc__, parents=parents)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain",
miparnisari marked this conversation as resolved.
Show resolved Hide resolved
"Properties": {
"VersioningConfiguration": {
"Status": "Enabled"
Expand Down
11 changes: 11 additions & 0 deletions tests/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,17 @@ def test_load_module_project_succeeds(project):
project.load()


def test_load_resource_succeeds(project):
project.artifact_type = "Resource"
project.type_name = "Unit::Test::Resource"
patch_load_settings = patch.object(
project, "load_settings", return_value={"artifact_type": "RESOURCE"}
)
project._write_example_schema()
with patch_load_settings:
project.load()


def test_load_module_project_with_invalid_fragments(project):
project.artifact_type = "MODULE"
project.type_name = "Unit::Test::Malik::MODULE"
Expand Down