Skip to content

Adding cfn-lint as optional parameter for SAM validate command - #4444

Merged
sriram-mv merged 22 commits into
aws:developfrom
cdavidxu-hub:develop
Dec 14, 2022
Merged

Adding cfn-lint as optional parameter for SAM validate command#4444
sriram-mv merged 22 commits into
aws:developfrom
cdavidxu-hub:develop

Conversation

@cdavidxu-hub

@cdavidxu-hub cdavidxu-hub commented Nov 25, 2022

Copy link
Copy Markdown
Contributor

Why is this change necessary?

To provide linting validation to SAM cli and fulfill feature and customer requests

How does it address the issue?

Cfn-lint will be added as an optional parameter to SAM validate. When run, the linter will parse and provide detailed error messages and codes to the customer along with suggestions for potential fixes.

What side effects does this change have?

This should not pose any side-effects either to core functionality of SAM validate or to CI/CD processes as it remains an optional parameter.

Mandatory Checklist

PRs will only be reviewed after checklist is complete

  • Add input/output type hints to new functions/methods
  • Write design document if needed (Do I need to write a design document?)
  • Write/update unit tests
  • Write/update integration tests
  • Write/update functional tests if needed
  • make pr passes
  • make update-reproducible-reqs if dependencies were changed
  • Write documentation

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@cdavidxu-hub
cdavidxu-hub requested a review from a team as a code owner November 25, 2022 18:11
@github-actions github-actions Bot added area/validate sam validate command pr/external stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. labels Nov 25, 2022
Comment thread samcli/commands/validate/validate.py Outdated
import boto3
from botocore.exceptions import NoCredentialsError
import click
import cfnlint.core # type: ignore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The number of imports at the top level should be as small as possible, if we really need it we should move it within cli method. Also what is the reason behind the #type: ignore , I'd rather not have us skip any lint checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Have moved below to method below. It seems cfn-lint does not have a py.typed file/type hints and we likely will not be adding them before release. Is it alright to suppress for now?

Comment thread samcli/commands/validate/validate.py Outdated
help="Run linting validation on template through cfn-lint. "
"For more information, see: https://github.com/aws-cloudformation/cfn-lint",
cls=ClickMutex,
incompatible_params=["config-env", "config-file", "profile", "region"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are these incompatible? you could very well have the template file be passed in through a configuration parameter within the 'validate' space such that it is default.validate.parameters, which has a template specified.

I see that cfn-lint has a way to pass in regions to its command line. what does that mapping mean here?

Does cfn-lint have any environment variables that it obeys? can they be used in conjunction through sam validate --lint ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, I see fair point - missed that. Would setting the template in the config-file also pass the template file path to the do_cli method? As for regions, I can include that as an option in the _lint method as well.

Comment thread samcli/commands/validate/validate.py
Comment thread samcli/commands/validate/validate.py Outdated
with open(args.output_file, 'w', encoding='utf-8') as output_file:
output_file.write(matches_output)
else:
print(matches_output)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should use click's functionality for outputting information instead of using a direct print.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will look into it

@sriram-mv

Copy link
Copy Markdown
Contributor

I also wonder if this should be its own command instead, i.e sam lint. Open Q

Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated

try:
if ctx.debug:
(args, filenames, formatter) = cfnlint.core.get_args_filenames([template, "--debug"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we add details why we need special handling of --debug flag here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need a way to pass the --debug flag to cfn-lint since cfn-lint also takes in a `--debug- optional parameter as well. Do you mean you would want some comments in the code explaining why?

Comment thread samcli/commands/validate/validate.py Outdated
matches_output = formatter.print_matches(matches, rules, filenames)

if matches_output:
if args.output_file:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where this output_file comes from and how users can set it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is an cfn-lint optional parameter which we cannot specify for sam validate so it will never output to this file. Will remove in next commit. Optionally we can have it always print to an output file but we would need to have that be explicitly stated to the customer

Comment thread samcli/commands/validate/validate.py Outdated
Comment on lines +152 to +153
LOGGER.error(str(e))
return e.exit_code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Raise a UserException which will handle the error message and exit code, rather than manually returning the exit_code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will look into it, sounds good

Comment thread samcli/commands/validate/validate.py Outdated

return sam_template

def _lint(ctx, template):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We are trying to add more typing into our repository, so can you please add typing for the parts that you have added?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will look into it and add

if matches_output:
click.secho(matches_output)

except cfnlint.core.InvalidRegionException as e:

@cdavidxu-hub cdavidxu-hub Dec 2, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just to add some more context on why there are two region exceptions. Cfn-lint can catch invalid regions in commands like samdev —lint —region us-southeast-2 and throw a cfnlint.core.InvalidRegionException. However, if the customer forgets to specify a region and then uses another optional parameter, like samdev —lint —region —debug, the cfn-lint help page will be outputted as it won’t detect any arguments for —region. Cfn-lint uses argparse and is configured to display the help page upon errors which we don’t want exposed to customers right now. I’ve added the botocore.util.validate_region_name method to check the format of the region string which will catch cases when the customer forgets to specify a region and uses another parameter. Let me know if this approach is acceptable or if there is a better way to raise these exceptions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

However, if the customer forgets to specify a region and then uses another optional parameter, like samdev —lint —region —debug, the cfn-lint help page will be outputted as it won’t detect any arguments for —region

Curious, we are using cfn-lint as a library here, why does argparse matter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The cfn-lint libraries make use of argparse so when specific errors occur the behaviour defaults to the argparse behaviour, like in this case with InvalidRegionExceptions.

@torresxb1 torresxb1 removed the stage/needs-triage Automatically applied to new issues and PRs, indicating they haven't been looked at. label Dec 3, 2022
@praneetap

Copy link
Copy Markdown
Contributor

I also wonder if this should be its own command instead, i.e sam lint. Open Q

curious why you think that Sriram. Making it a new command would make discovery an issue, and sam validate is already plugged into sam deploy, which is a nice benefit here??

@cdavidxu-hub

Copy link
Copy Markdown
Contributor Author

I also wonder if this should be its own command instead, i.e sam lint. Open Q

curious why you think that Sriram. Making it a new command would make discovery an issue, and sam validate is already plugged into sam deploy, which is a nice benefit here??

Sorry, missed these comments - I think in speaking with the design team, awareness was certainly an issue and since linting is a familiar term and form of validation we decided to keep it as an optional parameter for sam validate.

@sriram-mv

Copy link
Copy Markdown
Contributor

@praneetap @cdavidxu-hub The thinking of sam lint was behind the fact we can make linting a first class citizen rather than a flag. I agree on the discoverability aspect though. sam validate --lint to me is unclear directly. Am I validating and linting at the same time? Am I just linting? In this case we are running cfn-lint alone if sam validate --lint is specified.

This goes against other commands, eg: sam build --use-container I'm still building (doing the same exact building logic) but within the container.

Comment thread samcli/commands/validate/validate.py Outdated
) from e

click.secho(
"{} can be transformed to a Cloudformation template. "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we still add "is a valid SAM Template" back and then continue to add the rest? There could be tools that are scrapping our output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can see why that is a concern, however, the point of adding lint was that SAM validate was allowing false positives and outputting that a template was a valid SAM template when in fact it was not.

if matches_output:
click.secho(matches_output)

except cfnlint.core.InvalidRegionException as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

However, if the customer forgets to specify a region and then uses another optional parameter, like samdev —lint —region —debug, the cfn-lint help page will be outputted as it won’t detect any arguments for —region

Curious, we are using cfn-lint as a library here, why does argparse matter?

Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread requirements/base.txt
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread tests/integration/validate/test_validate_command.py
Comment thread samcli/commands/validate/validate.py Outdated
Comment thread samcli/commands/validate/validate.py Outdated
cfn_lint_logger.propagate = False

try:
validate_region_name(ctx.region)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lets move this entire check to within the cli validator for region

from samcli.commands.exceptions import RegionError

    def callback(ctx, param, value):
        state = ctx.ensure_object(Context)
        from botocore import exceptions, utils

        try:
            utils.validate_region_name(value)
        except exceptions.InvalidRegionError as ex:
            raise RegionError(
                message=f"Provided region: {value} doesn't match a supported format", wrapped_from=ex.__class__.__name__
            )
        state.region = value
        return value

    return click.option(
        "--region", expose_value=False, help="Set the AWS Region of the service (e.g. us-east-1).", callback=callback
    )(f)

at https://github.com/aws/aws-sam-cli/blob/develop/samcli/cli/options.py#L41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@cdavidxu-hub I think it was a misunderstanding that I didn't want to keep as it is, instead I was suggesting to move this validation to a more generic place or removing it completely. Let's continue with what @sriram-mv suggested above.

--hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \
--hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0
# via cryptography
cfn-lint==0.72.2 \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are there any dependencies which is dynamically imported? If so we might need to add them to hidden imports information for pyinstaller.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread samcli/commands/validate/validate.py Outdated
cfn_lint_logger.propagate = False

try:
validate_region_name(ctx.region)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@cdavidxu-hub I think it was a misunderstanding that I didn't want to keep as it is, instead I was suggesting to move this validation to a more generic place or removing it completely. Let's continue with what @sriram-mv suggested above.

with self.assertRaises(UserException):
_lint(ctx=ctx_lint_mock(debug=False, region="region"), template=template_path)

@patch("botocore.utils.validate_region_name")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why we have removed this test, can we add it back with more generic solution that you have implemented?

Comment thread tests/unit/cli/test_main.py Outdated
result = runner.invoke(cli, ["local", "generate-event", "s3"])
self.assertEqual(result.exit_code, 0)

def test_cli_with_no_region_arg_validate(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

    def test_cli_with_non_standard_format_region(self):
        mock_cfg = Mock()
        with patch("samcli.cli.main.GlobalConfig", mock_cfg):
            runner = CliRunner()
            for command in ["validate", "deploy"]:
                result = runner.invoke(cli, [command, "--region", "--non-standard-format"])
                self.assertEqual(result.exit_code, 1)
                self.assertIn("Error: Provided region: --non-standard-format doesn't match a supported format",
                              result.output)

You could do a assertRaises too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Conversely if you wanted to test that region being set to a standard format went ok.

    @patch("samcli.commands.validate.validate.do_cli")
    def test_cli_with_valid_region(self, mock_do_cli):
        mock_cfg = Mock()
        with patch("samcli.cli.main.GlobalConfig", mock_cfg):
            runner = CliRunner()
            result = runner.invoke(cli, ["validate", "--region", "us-west-2"])
            self.assertEqual(result.exit_code, 0)
        self.assertTrue(mock_do_cli.called)
        self.assertEqual(mock_do_cli.call_count, 1)

Comment thread tests/unit/cli/test_main.py Outdated
runner.invoke(cli, ["validate", "--region", "--debug"])
self.assertRaises(RegionError)

def test_cli_with_no_region_arg_deploy(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't quite get this test.

why does deploy use use-container False?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm I took the parameters from a separate test, but ultimately I just want to test the region behaviour. Would it work with "deploy --region --debug"? If so, I will just remove the use-container parameter

Comment thread tests/unit/cli/test_main.py Outdated
mock_cfg = Mock()
with patch("samcli.cli.main.GlobalConfig", mock_cfg):
runner = CliRunner()
runner.invoke(cli, ["deploy", "use-container" "False", "--region", "--debug"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like both tests are using an empty region since another flag is provided right after --region? I would rather have;

  • A test with a valid region
  • A test with an invalid region
  • A test with empty region

Comment thread tests/unit/cli/test_main.py Outdated
result = runner.invoke(cli, ["local", "generate-event", "s3"])
self.assertEqual(result.exit_code, 0)

def test_cli_with_no_region_argument(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not a no region argument test. its a non standard format test.

for a no region argument, it would look like this.

    def test_cli_with_empty_region(self):
        mock_cfg = Mock()
        with patch("samcli.cli.main.GlobalConfig", mock_cfg):
            runner = CliRunner()
            for command in ["validate", "deploy"]:
                result = runner.invoke(cli, [command, "--region"])
                self.assertEqual(result.exit_code, 2)
                self.assertIn("Error: Option '--region' requires an argument",
                              result.output)

@sriram-mv
sriram-mv self-requested a review December 14, 2022 03:16
@sriram-mv
sriram-mv enabled auto-merge (squash) December 14, 2022 03:16
@sriram-mv
sriram-mv merged commit 2fd533f into aws:develop Dec 14, 2022
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 14, 2022
)

* Adding cfn-lint optional parameter to SAM validate command

* Cfn-lint optional parameter SAM validate make pr changes

* Fix make pr and broken tests

* Add Click error handling and allow config params

* Add unit and integration test

* fix formatting and region exceptions

* Add integ unhappy path integ test and fix comments

* Fix: remove local path string from test

* generalize region validation and add license info

* Fix licensing and dynamic installer

* Added generalized region validation tests

* Fix unit tests and change output string

* Add valid region test, fix formatting

* Add no region test

Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com>
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 16, 2022
mndeveci added a commit that referenced this pull request Dec 17, 2022
* Revert "fix: `hooks` data imports for pyinstaller (#4491)"

This reverts commit bea3bc0.

* Revert "fix: Update expected message read validate lint integration test (#4488)"

This reverts commit abd7c03.

* Revert "Update lint helpand output message (#4489)"

This reverts commit 3304955.

* Revert "fix: `pyinstaller` binaries (#4486)"

This reverts commit b8a939d.

* Revert "fix:  Fix validate command integration tests console output missmatch and update pyyaml version requirement (#4479)"

This reverts commit ce7143c.

* Revert "Adding cfn-lint as optional parameter for SAM validate command (#4444)"

This reverts commit 2fd533f.
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 19, 2022
* Revert "fix: `hooks` data imports for pyinstaller (aws#4491)"

This reverts commit bea3bc0.

* Revert "fix: Update expected message read validate lint integration test (aws#4488)"

This reverts commit abd7c03.

* Revert "Update lint helpand output message (aws#4489)"

This reverts commit 3304955.

* Revert "fix: `pyinstaller` binaries (aws#4486)"

This reverts commit b8a939d.

* Revert "fix:  Fix validate command integration tests console output missmatch and update pyyaml version requirement (aws#4479)"

This reverts commit ce7143c.

* Revert "Adding cfn-lint as optional parameter for SAM validate command (aws#4444)"

This reverts commit 2fd533f.
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 19, 2022
* Revert "fix: `hooks` data imports for pyinstaller (aws#4491)"

This reverts commit bea3bc0.

* Revert "fix: Update expected message read validate lint integration test (aws#4488)"

This reverts commit abd7c03.

* Revert "Update lint helpand output message (aws#4489)"

This reverts commit 3304955.

* Revert "fix: `pyinstaller` binaries (aws#4486)"

This reverts commit b8a939d.

* Revert "fix:  Fix validate command integration tests console output missmatch and update pyyaml version requirement (aws#4479)"

This reverts commit ce7143c.

* Revert "Adding cfn-lint as optional parameter for SAM validate command (aws#4444)"

This reverts commit 2fd533f.
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 19, 2022
* Revert "fix: `hooks` data imports for pyinstaller (aws#4491)"

This reverts commit bea3bc0.

* Revert "fix: Update expected message read validate lint integration test (aws#4488)"

This reverts commit abd7c03.

* Revert "Update lint helpand output message (aws#4489)"

This reverts commit 3304955.

* Revert "fix: `pyinstaller` binaries (aws#4486)"

This reverts commit b8a939d.

* Revert "fix:  Fix validate command integration tests console output missmatch and update pyyaml version requirement (aws#4479)"

This reverts commit ce7143c.

* Revert "Adding cfn-lint as optional parameter for SAM validate command (aws#4444)"

This reverts commit 2fd533f.
mndeveci added a commit to mndeveci/aws-sam-cli that referenced this pull request Dec 19, 2022
* Revert "fix: `hooks` data imports for pyinstaller (aws#4491)"

This reverts commit bea3bc0.

* Revert "fix: Update expected message read validate lint integration test (aws#4488)"

This reverts commit abd7c03.

* Revert "Update lint helpand output message (aws#4489)"

This reverts commit 3304955.

* Revert "fix: `pyinstaller` binaries (aws#4486)"

This reverts commit b8a939d.

* Revert "fix:  Fix validate command integration tests console output missmatch and update pyyaml version requirement (aws#4479)"

This reverts commit ce7143c.

* Revert "Adding cfn-lint as optional parameter for SAM validate command (aws#4444)"

This reverts commit 2fd533f.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/validate sam validate command pr/external

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants