From 188c14025431666299fa44162dd6e4088f6ab0ef Mon Sep 17 00:00:00 2001
From: tal66 <77445020+tal66@users.noreply.github.com>
Date: Tue, 11 Apr 2023 17:20:52 +0300
Subject: [PATCH 1/3] New rule for Lambda zip deployment
---
.../lmbd/ZipPackageRequiredProperties.py | 64 +++++++++++++++++++
.../lambda/zipfile_required_properties.yaml | 32 ++++++++++
.../resources/lambda/required_properties.yaml | 31 +++++++++
.../test_zip_package_required_properties.py | 29 +++++++++
4 files changed, 156 insertions(+)
create mode 100644 src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
create mode 100644 test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml
create mode 100644 test/fixtures/templates/good/resources/lambda/required_properties.yaml
create mode 100644 test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
diff --git a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
new file mode 100644
index 0000000000..681eda189c
--- /dev/null
+++ b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
@@ -0,0 +1,64 @@
+"""
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+from cfnlint.rules import CloudFormationLintRule
+from cfnlint.rules import RuleMatch
+
+
+class ZipPackageRequiredProperties(CloudFormationLintRule):
+ id = "W2533"
+ shortdesc = (
+ "Check required properties for Lambda if the deployment package is a .zip file"
+ )
+ description = (
+ "When the package type is Zip, "
+ "you must also specify the `handler` and `runtime` properties."
+ )
+ source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html"
+ tags = ["resources", "lambda"]
+
+ def match(self, cfn):
+ """Basic Rule Matching"""
+ matches = []
+ required_properties = [
+ "Handler",
+ "Runtime",
+ ] # required if package is a .zip file
+
+ resources = cfn.get_resources(
+ ["AWS::Lambda::Function", "AWS::Serverless::Function"]
+ )
+ for resource_name, resource in resources.items():
+ properties = resource.get("Properties")
+ if not properties:
+ continue
+
+ path = ["Resources", resource_name, "Properties"]
+
+ is_zip_deployment = True
+ if properties.get("PackageType") == "Zip":
+ path.append("PackageType")
+ elif properties.get("Code", {}).get("ZipFile") or properties.get(
+ "Code", {}
+ ).get("S3Key", "").endswith(".zip"):
+ path.append("Code")
+ else:
+ is_zip_deployment = False
+
+ if not is_zip_deployment:
+ continue
+
+ # check required properties for zip deployment
+ missing_properties = []
+ for p in required_properties:
+ if properties.get(p) is None:
+ missing_properties.append(p)
+
+ if len(missing_properties) > 0:
+ message = (
+ "Missing required properties for Lambda zip file deployment: {0}"
+ )
+ matches.append(RuleMatch(path, message.format(missing_properties)))
+
+ return matches
diff --git a/test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml b/test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml
new file mode 100644
index 0000000000..806ac99f80
--- /dev/null
+++ b/test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml
@@ -0,0 +1,32 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Missing properties.
+Resources:
+ Function:
+ Type: AWS::Lambda::Function
+ Properties:
+# Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ S3Bucket: my-bucket
+ S3Key: function.zip
+# Runtime: nodejs16.x
+ Function2:
+ Type: AWS::Lambda::Function
+ Properties:
+# Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ S3Bucket: my-bucket
+ S3Key: function.zip
+ Runtime: python3.9
+ PackageType: Zip
+ Function3:
+ Type: AWS::Lambda::Function
+ Properties:
+ Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ ZipFile: |
+ var aws = require('aws-sdk')
+ exports.handler = function(event, context) {}
+# Runtime: nodejs16.x
\ No newline at end of file
diff --git a/test/fixtures/templates/good/resources/lambda/required_properties.yaml b/test/fixtures/templates/good/resources/lambda/required_properties.yaml
new file mode 100644
index 0000000000..8ae54fd04a
--- /dev/null
+++ b/test/fixtures/templates/good/resources/lambda/required_properties.yaml
@@ -0,0 +1,31 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Properties.
+Resources:
+ Function1:
+ Type: AWS::Lambda::Function
+ Properties:
+ Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ S3Bucket: my-bucket
+ S3Key: function.zip
+ Runtime: python3.9
+ Function2:
+ Type: AWS::Lambda::Function
+ Properties:
+ Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ ZipFile: |
+ var aws = require('aws-sdk')
+ exports.handler = function(event, context) {}
+ Runtime: nodejs16.x
+ Function3:
+ Type: AWS::Lambda::Function
+ Properties:
+ Handler: index.handler
+ Role: arn:aws:iam::123456789012:role/lambda-role
+ Code:
+ ImageUri : 111122223333.dkr.ecr.us-east-1.amazonaws.com/hello-world:latest
+ Runtime: nodejs16.x
+ PackageType: Image
\ No newline at end of file
diff --git a/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
new file mode 100644
index 0000000000..23613937d5
--- /dev/null
+++ b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
@@ -0,0 +1,29 @@
+"""
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: MIT-0
+"""
+from test.unit.rules import BaseRuleTestCase
+
+from cfnlint.rules.resources.lmbd.ZipPackageRequiredProperties import (
+ ZipPackageRequiredProperties,
+)
+
+
+class TestZipPackageRequiredProperties(BaseRuleTestCase):
+ """Test template parameter configurations"""
+
+ def setUp(self):
+ super(TestZipPackageRequiredProperties, self).setUp()
+ self.collection.register(ZipPackageRequiredProperties())
+ self.success_templates = [
+ "test/fixtures/templates/good/resources/lambda/required_properties.yaml"
+ ]
+
+ def test_file_positive(self):
+ self.helper_file_positive()
+
+ def test_file_negative(self):
+ self.helper_file_negative(
+ "test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml",
+ err_count=3,
+ )
From a9d28e77543f2ff76183390b4702b3ea795f53b2 Mon Sep 17 00:00:00 2001
From: tal66 <77445020+tal66@users.noreply.github.com>
Date: Wed, 3 May 2023 03:01:22 +0300
Subject: [PATCH 2/3] code rev
---
.../lmbd/ZipPackageRequiredProperties.py | 65 +++++++++++--------
...operties.yaml => required_properties.yaml} | 2 +-
.../resources/lambda/required_properties.yaml | 10 ++-
.../test_zip_package_required_properties.py | 4 +-
4 files changed, 45 insertions(+), 36 deletions(-)
rename test/fixtures/templates/bad/resources/lambda/{zipfile_required_properties.yaml => required_properties.yaml} (98%)
diff --git a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
index 681eda189c..b59cfaa6eb 100644
--- a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
+++ b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
@@ -19,46 +19,57 @@ class ZipPackageRequiredProperties(CloudFormationLintRule):
tags = ["resources", "lambda"]
def match(self, cfn):
- """Basic Rule Matching"""
matches = []
required_properties = [
"Handler",
"Runtime",
] # required if package is a .zip file
- resources = cfn.get_resources(
- ["AWS::Lambda::Function", "AWS::Serverless::Function"]
- )
+ resources = cfn.get_resources(["AWS::Lambda::Function"])
+
for resource_name, resource in resources.items():
properties = resource.get("Properties")
- if not properties:
+ if not isinstance(properties, dict):
continue
- path = ["Resources", resource_name, "Properties"]
+ for scenario in cfn.get_object_without_conditions(
+ properties, ["PackageType", "Code", "Handler", "Runtime"]
+ ):
+ props = scenario.get("Object")
+ path = ["Resources", resource_name, "Properties"]
- is_zip_deployment = True
- if properties.get("PackageType") == "Zip":
- path.append("PackageType")
- elif properties.get("Code", {}).get("ZipFile") or properties.get(
- "Code", {}
- ).get("S3Key", "").endswith(".zip"):
- path.append("Code")
- else:
- is_zip_deployment = False
+ # check is zip deployment
+ is_zip_deployment = True
+ code = props.get("Code")
- if not is_zip_deployment:
- continue
+ if props.get("PackageType") == "Zip":
+ path.append("PackageType")
+ elif isinstance(code, dict) and (code.get("ZipFile") or code.get("S3Key")):
+ path.append("Code")
+ else:
+ is_zip_deployment = False
+
+ if not is_zip_deployment:
+ continue
- # check required properties for zip deployment
- missing_properties = []
- for p in required_properties:
- if properties.get(p) is None:
- missing_properties.append(p)
+ # check required properties for zip deployment
+ missing_properties = []
+ for p in required_properties:
+ if props.get(p) is None:
+ missing_properties.append(p)
- if len(missing_properties) > 0:
- message = (
- "Missing required properties for Lambda zip file deployment: {0}"
- )
- matches.append(RuleMatch(path, message.format(missing_properties)))
+ if len(missing_properties) > 0:
+ message = "Properties {0} missing for zip file deployment at {1}"
+ matches.append(
+ RuleMatch(
+ path,
+ message.format(
+ missing_properties,
+ "/".join(
+ map(str, ["Resources", resource_name, "Properties"])
+ ),
+ ),
+ )
+ )
return matches
diff --git a/test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml b/test/fixtures/templates/bad/resources/lambda/required_properties.yaml
similarity index 98%
rename from test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml
rename to test/fixtures/templates/bad/resources/lambda/required_properties.yaml
index 806ac99f80..c087056343 100644
--- a/test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml
+++ b/test/fixtures/templates/bad/resources/lambda/required_properties.yaml
@@ -1,7 +1,7 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: Missing properties.
Resources:
- Function:
+ Function1:
Type: AWS::Lambda::Function
Properties:
# Handler: index.handler
diff --git a/test/fixtures/templates/good/resources/lambda/required_properties.yaml b/test/fixtures/templates/good/resources/lambda/required_properties.yaml
index 8ae54fd04a..36823a575f 100644
--- a/test/fixtures/templates/good/resources/lambda/required_properties.yaml
+++ b/test/fixtures/templates/good/resources/lambda/required_properties.yaml
@@ -1,7 +1,7 @@
AWSTemplateFormatVersion: '2010-09-09'
-Description: Properties.
+Description: Required Properties.
Resources:
- Function1:
+ Function1: # S3 key to deployment package
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
@@ -10,7 +10,7 @@ Resources:
S3Bucket: my-bucket
S3Key: function.zip
Runtime: python3.9
- Function2:
+ Function2: # source inline (zipped by CloudFormation)
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
@@ -20,12 +20,10 @@ Resources:
var aws = require('aws-sdk')
exports.handler = function(event, context) {}
Runtime: nodejs16.x
- Function3:
+ Function3: # image deployment
Type: AWS::Lambda::Function
Properties:
- Handler: index.handler
Role: arn:aws:iam::123456789012:role/lambda-role
Code:
ImageUri : 111122223333.dkr.ecr.us-east-1.amazonaws.com/hello-world:latest
- Runtime: nodejs16.x
PackageType: Image
\ No newline at end of file
diff --git a/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
index 23613937d5..409b41beea 100644
--- a/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
+++ b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py
@@ -10,7 +10,7 @@
class TestZipPackageRequiredProperties(BaseRuleTestCase):
- """Test template parameter configurations"""
+ """Test required properties"""
def setUp(self):
super(TestZipPackageRequiredProperties, self).setUp()
@@ -24,6 +24,6 @@ def test_file_positive(self):
def test_file_negative(self):
self.helper_file_negative(
- "test/fixtures/templates/bad/resources/lambda/zipfile_required_properties.yaml",
+ "test/fixtures/templates/bad/resources/lambda/required_properties.yaml",
err_count=3,
)
From 0b9183d2989368ed4a22a883af7c2cf831aa683a Mon Sep 17 00:00:00 2001
From: tal66 <77445020+tal66@users.noreply.github.com>
Date: Fri, 5 May 2023 14:13:05 +0300
Subject: [PATCH 3/3] style, --update-documentation
---
docs/rules.md | 3 ++-
.../rules/resources/lmbd/ZipPackageRequiredProperties.py | 7 ++++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/docs/rules.md b/docs/rules.md
index be054ba619..42f0b7c160 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -46,7 +46,7 @@ To include these rules, use the `-e/include-experimental` argument when running
## Rules
(_This documentation is generated by running `cfn-lint --update-documentation`, do not alter this manually_)
-The following **152** rules are applied by this linter:
+The following **153** rules are applied by this linter:
| Rule ID | Title | Description | Config
(Name:Type:Default) | Source | Tags |
| -------- | ----- | ----------- | ---------- | ------ | ---- |
@@ -191,6 +191,7 @@ The following **152** rules are applied by this linter:
| [W2510](../src/cfnlint/rules/parameters/LambdaMemorySize.py) | Parameter Memory Size attributes should have max and min | Check if a parameter that is used for Lambda memory size should have a min and max size that matches Lambda constraints | | [Source](https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-MemorySize) | `parameters`,`lambda` |
| [W2511](../src/cfnlint/rules/resources/iam/PolicyVersion.py) | Check IAM Resource Policies syntax | See if the elements inside an IAM Resource policy are configured correctly. | | [Source](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html) | `properties`,`iam` |
| [W2531](../src/cfnlint/rules/resources/lmbd/DeprecatedRuntimeEol.py) | Check if EOL Lambda Function Runtimes are used | Check if an EOL Lambda Runtime is specified and give a warning if used. | | [Source](https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html) | `resources`,`lambda`,`runtime` |
+| [W2533](../src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py) | Check required properties for Lambda if the deployment package is a .zip file | When the package type is Zip, you must also specify the `handler` and `runtime` properties. | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | `resources`,`lambda` |
| [W3002](../src/cfnlint/rules/resources/properties/PropertiesTemplated.py) | Warn when properties are configured to only work with the package command | Some properties can be configured to only work with the CloudFormationpackage command. Warn when this is the case so user is aware. | | [Source](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html) | `resources` |
| [W3005](../src/cfnlint/rules/resources/DependsOnObsolete.py) | Check obsolete DependsOn configuration for Resources | Check if DependsOn is specified if not needed. A Ref or a Fn::GetAtt already is an implicit dependency. | | [Source](https://aws.amazon.com/blogs/devops/optimize-aws-cloudformation-templates/) | `resources`,`dependson`,`ref`,`getatt` |
| [W3010](../src/cfnlint/rules/resources/properties/AvailabilityZone.py) | Availability Zone Parameters should not be hardcoded | Check if an Availability Zone property is hardcoded. | | [Source](https://github.com/aws-cloudformation/cfn-python-lint) | `parameters`,`availabilityzone` |
diff --git a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
index b59cfaa6eb..6254b82f28 100644
--- a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
+++ b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py
@@ -2,8 +2,7 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""
-from cfnlint.rules import CloudFormationLintRule
-from cfnlint.rules import RuleMatch
+from cfnlint.rules import CloudFormationLintRule, RuleMatch
class ZipPackageRequiredProperties(CloudFormationLintRule):
@@ -44,7 +43,9 @@ def match(self, cfn):
if props.get("PackageType") == "Zip":
path.append("PackageType")
- elif isinstance(code, dict) and (code.get("ZipFile") or code.get("S3Key")):
+ elif isinstance(code, dict) and (
+ code.get("ZipFile") or code.get("S3Key")
+ ):
path.append("Code")
else:
is_zip_deployment = False