Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: aws-cloudformation/cfn-lint
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.29.1
Choose a base ref
...
head repository: aws-cloudformation/cfn-lint
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.30.0
Choose a head ref
  • 9 commits
  • 40 files changed
  • 1 contributor

Commits on Mar 11, 2025

  1. Allow conditions in foreach (#4009)

    kddejong authored Mar 11, 2025
    Copy the full SHA
    0ac4309 View commit details
  2. Allow _ in condition names (#4008)

    * Allow _ in condition names
    kddejong authored Mar 11, 2025
    Copy the full SHA
    3cc4012 View commit details

Commits on Mar 12, 2025

  1. Remove experimental from W3037 (#3680)

    kddejong authored Mar 12, 2025
    Copy the full SHA
    cf5aad3 View commit details
  2. Add in pattern for CodePipeline action names (#4012)

    kddejong authored Mar 12, 2025
    Copy the full SHA
    fe59771 View commit details
  3. Support GetAtts for nested stacks and outputs (#4011)

    * Support GetAtts for nested stacks and outputs
    kddejong authored Mar 12, 2025
    Copy the full SHA
    e3b60ec View commit details
  4. Add rule I2003 to validate AllowedPattern (#4013)

    kddejong authored Mar 12, 2025
    Copy the full SHA
    633ff85 View commit details
  5. A set of cleanup fixes (#4014)

    * A set of cleanup fixes
    * Limit where github actions run
    kddejong authored Mar 12, 2025
    Copy the full SHA
    6513670 View commit details

Commits on Mar 13, 2025

  1. Validate identity base SIDs (#4016)

    kddejong authored Mar 13, 2025
    Copy the full SHA
    6cd4bc1 View commit details
  2. Release v1.30.0 (#4017)

    kddejong authored Mar 13, 2025
    Copy the full SHA
    d467bb9 View commit details
Showing with 670 additions and 174 deletions.
  1. +1 −0 .github/workflows/cd-pypi.yaml
  2. +1 −0 .github/workflows/cd-release.yaml
  3. +1 −0 .github/workflows/maintenance-v0.yaml
  4. +1 −0 .github/workflows/maintenance-v1.yaml
  5. +12 −0 CHANGELOG.md
  6. +2 −2 README.md
  7. +5 −1 scripts/update_schemas_manually.py
  8. +1 −1 src/cfnlint/context/__init__.py
  9. +54 −6 src/cfnlint/context/context.py
  10. +1 −1 src/cfnlint/data/schemas/other/conditions/conditions.json
  11. +1 −0 src/cfnlint/data/schemas/other/iam/policy.json
  12. +2 −1 src/cfnlint/data/schemas/other/iam/policy_identity.json
  13. +6 −1 src/cfnlint/data/schemas/patches/extensions/all/aws_codepipeline_pipeline/manual.json
  14. +2 −1 src/cfnlint/data/schemas/providers/ap_southeast_5/aws-codepipeline-pipeline.json
  15. +2 −1 src/cfnlint/data/schemas/providers/ap_southeast_7/aws-codepipeline-pipeline.json
  16. +2 −1 src/cfnlint/data/schemas/providers/ca_west_1/aws-codepipeline-pipeline.json
  17. +2 −1 src/cfnlint/data/schemas/providers/us_east_1/aws-codepipeline-pipeline.json
  18. +1 −1 src/cfnlint/rules/conditions/Used.py
  19. +45 −0 src/cfnlint/rules/parameters/AllowedPattern.py
  20. +2 −4 src/cfnlint/rules/parameters/Configuration.py
  21. +3 −0 src/cfnlint/rules/resources/iam/IdentityPolicy.py
  22. +6 −2 src/cfnlint/rules/resources/iam/Permissions.py
  23. +16 −0 src/cfnlint/rules/resources/iam/ResourcePolicy.py
  24. +2 −3 src/cfnlint/schema/_getatts.py
  25. +6 −7 src/cfnlint/template/getatts.py
  26. +2 −1 src/cfnlint/template/template.py
  27. +23 −0 src/cfnlint/template/transforms/_language_extensions.py
  28. +1 −1 src/cfnlint/version.py
  29. +1 −0 test/integration/test_schema_files.py
  30. +67 −0 test/unit/module/context/test_resource.py
  31. +15 −6 test/unit/module/template/test_getatts.py
  32. +66 −53 test/unit/module/template/test_template.py
  33. +20 −0 test/unit/module/template/transforms/test_language_extensions.py
  34. +1 −1 test/unit/rules/conditions/test_conditions.py
  35. +36 −60 test/unit/rules/parameters/test_allowed_pattern.py
  36. +71 −0 test/unit/rules/parameters/test_value_pattern.py
  37. +32 −0 test/unit/rules/resources/iam/test_iam_permissions_sam.py
  38. +44 −1 test/unit/rules/resources/iam/test_identity_policy.py
  39. +114 −12 test/unit/rules/resources/iam/test_resource_policy.py
  40. +0 −5 test/unit/rules/resources/route53/test_recordsets.py
1 change: 1 addition & 0 deletions .github/workflows/cd-pypi.yaml
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
if: github.repository == 'aws-cloudformation/cfn-lint'
steps:
- uses: actions/checkout@v4
- name: Set up Python
1 change: 1 addition & 0 deletions .github/workflows/cd-release.yaml
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'aws-cloudformation/cfn-lint'
steps:
- name: Checkout
uses: actions/checkout@v4
1 change: 1 addition & 0 deletions .github/workflows/maintenance-v0.yaml
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ on:
jobs:
job:
runs-on: ubuntu-latest
if: github.repository == 'aws-cloudformation/cfn-lint'
steps:
- uses: actions/checkout@v4
with:
1 change: 1 addition & 0 deletions .github/workflows/maintenance-v1.yaml
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ on:
jobs:
job:
runs-on: ubuntu-latest
if: github.repository == 'aws-cloudformation/cfn-lint'
steps:
- uses: actions/checkout@v4
with:
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
### v1.30.0
## What's Changed
* Allow conditions in foreach by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4009
* Allow `_` in condition names by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4008
* Remove experimental from [W3037](https://github.com/aws-cloudformation/cfn-python-lint/blob/main/docs/rules.md#W3037) by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/3680
* Add in `pattern` for CodePipeline action names by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4012
* Support GetAtts for nested stacks and outputs by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4011
* Add rule [I2003](https://github.com/aws-cloudformation/cfn-python-lint/blob/main/docs/rules.md#I2003) to validate `AllowedPattern` by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4013
* Validate identity base SIDs by @kddejong in https://github.com/aws-cloudformation/cfn-lint/pull/4016

**Full Changelog**: https://github.com/aws-cloudformation/cfn-lint/compare/v1.29.1...v1.30.0

### v1.29.1
## What's Changed
* Update CloudFormation schemas to `2025-03-10` by @github-actions in https://github.com/aws-cloudformation/cfn-lint/pull/3999
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -342,7 +342,7 @@ If you'd like cfn-lint to be run automatically when making changes to files in y
```yaml
repos:
- repo: https://github.com/aws-cloudformation/cfn-lint
rev: v1.29.1 # The version of cfn-lint to use
rev: v1.30.0 # The version of cfn-lint to use
hooks:
- id: cfn-lint
files: path/to/cfn/dir/.*\.(json|yml|yaml)$
@@ -353,7 +353,7 @@ If you are using a `.cfnlintrc` and specifying the `templates` or `ignore_templa
```yaml
repos:
- repo: https://github.com/aws-cloudformation/cfn-lint
rev: v1.29.1 # The version of cfn-lint to use
rev: v1.30.0 # The version of cfn-lint to use
hooks:
- id: cfn-lint-rc
```
6 changes: 5 additions & 1 deletion scripts/update_schemas_manually.py
Original file line number Diff line number Diff line change
@@ -593,9 +593,13 @@
path="/definitions/StageDeclaration/properties/Actions",
),
Patch(
values={"pattern": "^[0-9A-Za-z_-]{1,9}$"},
values={"pattern": "^[0-9A-Za-z_\-]{1,9}$"},
path="/definitions/ActionTypeId/properties/Version",
),
Patch(
values={"pattern": "^[A-Za-z0-9.@\-_]{1,100}$"},
path="/definitions/ActionDeclaration/properties/Name",
),
],
),
ResourcePatch(
2 changes: 1 addition & 1 deletion src/cfnlint/context/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__all__ = ["Context", "create_context_for_template"]

from cfnlint.context.context import Context, Path, create_context_for_template
from cfnlint.context.context import Context, Path, Resource, create_context_for_template
60 changes: 54 additions & 6 deletions src/cfnlint/context/context.py
Original file line number Diff line number Diff line change
@@ -5,11 +5,12 @@

from __future__ import annotations

import os
from abc import ABC, abstractmethod
from collections import deque
from dataclasses import InitVar, dataclass, field, fields
from functools import lru_cache
from typing import Any, Deque, Iterator, Sequence, Set, Tuple
from typing import TYPE_CHECKING, Any, Deque, Iterator, Sequence, Set, Tuple

from cfnlint.context._mappings import Mappings
from cfnlint.context.conditions._conditions import Conditions
@@ -22,6 +23,9 @@
)
from cfnlint.schema import PROVIDER_SCHEMA_MANAGER, AttributeDict

if TYPE_CHECKING:
from cfnlint.template import Template

_PSEUDOPARAMS_NON_REGION = ["AWS::AccountId", "AWS::NoValue", "AWS::StackName"]


@@ -384,6 +388,37 @@ def is_ssm_parameter(self) -> bool:
return self.type.startswith("AWS::SSM::Parameter::")


def _nested_stack_get_atts(filename, template_url):
if (
template_url.startswith("http://")
or template_url.startswith("https://")
or template_url.startswith("s3://")
):
return None

base_dir = os.path.dirname(os.path.abspath(filename))
template_path = os.path.normpath(os.path.join(base_dir, template_url))

try:
from cfnlint.decode import decode

(tmp, matches) = decode(template_path)
except Exception: # noqa: E722
return None
if matches:
return None

outputs = AttributeDict()

tmp_outputs = tmp.get("Outputs")
if not isinstance(tmp_outputs, dict):
return outputs

for name, _ in tmp_outputs.items():
outputs[f"Outputs.{name}"] = "/properties/CfnLintStringType"
return outputs


@dataclass
class Resource(_Ref):
"""
@@ -393,8 +428,10 @@ class Resource(_Ref):
type: str = field(init=False)
condition: str | None = field(init=False, default=None)
resource: InitVar[Any]
filename: InitVar[str | None] = field(default=None)
_nested_stack_get_atts: AttributeDict | None = field(init=False, default=None)

def __post_init__(self, resource) -> None:
def __post_init__(self, resource: Any, filename: str | None) -> None:
if not isinstance(resource, dict):
raise ValueError("Resource must be a object")
t = resource.get("Type")
@@ -409,7 +446,18 @@ def __post_init__(self, resource) -> None:
raise ValueError("Condition must be a string")
self.condition = c

if self.type == "AWS::CloudFormation::Stack":
properties = resource.get("Properties")
if isinstance(properties, dict):
template_url = properties.get("TemplateURL")
if isinstance(template_url, str):
self._nested_stack_get_atts = _nested_stack_get_atts(
filename, template_url
)

def get_atts(self, region: str = REGION_PRIMARY) -> AttributeDict:
if self._nested_stack_get_atts is not None:
return self._nested_stack_get_atts
return PROVIDER_SCHEMA_MANAGER.get_type_getatts(self.type, region)

def ref(self, region: str = REGION_PRIMARY) -> dict[str, Any]:
@@ -433,13 +481,13 @@ def _init_parameters(parameters: Any) -> dict[str, Parameter]:
return obj


def _init_resources(resources: Any) -> dict[str, Resource]:
def _init_resources(resources: Any, filename: str | None = None) -> dict[str, Resource]:
obj = {}
if not isinstance(resources, dict):
raise ValueError("Resource must be a object")
for k, v in resources.items():
try:
obj[k] = Resource(v)
obj[k] = Resource(v, filename)
except ValueError:
pass
return obj
@@ -451,7 +499,7 @@ def _init_transforms(transforms: Any) -> Transforms:
return Transforms([])


def create_context_for_template(cfn):
def create_context_for_template(cfn: Template) -> "Context":
parameters = {}
try:
parameters = _init_parameters(cfn.template.get("Parameters", {}))
@@ -460,7 +508,7 @@ def create_context_for_template(cfn):

resources = {}
try:
resources = _init_resources(cfn.template.get("Resources", {}))
resources = _init_resources(cfn.template.get("Resources", {}), cfn.filename)
except (ValueError, AttributeError):
pass

2 changes: 1 addition & 1 deletion src/cfnlint/data/schemas/other/conditions/conditions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9&]{1,255}$": {
"^[a-zA-Z0-9&_]{1,255}$": {
"type": "boolean"
}
},
1 change: 1 addition & 0 deletions src/cfnlint/data/schemas/other/iam/policy.json
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
"items": {
"type": "string"
},
"minItems": 1,
"type": [
"string",
"array"
3 changes: 2 additions & 1 deletion src/cfnlint/data/schemas/other/iam/policy_identity.json
Original file line number Diff line number Diff line change
@@ -43,7 +43,8 @@
"$ref": "policy#/definitions/Resource"
},
"Sid": {
"$ref": "policy#/definitions/Statement/properties/Sid"
"$ref": "policy#/definitions/Statement/properties/Sid",
"pattern": "^[A-Za-z0-9]+$"
}
}
}
Original file line number Diff line number Diff line change
@@ -7,10 +7,15 @@
"ArtifactStores"
]
},
{
"op": "add",
"path": "/definitions/ActionDeclaration/properties/Name/pattern",
"value": "^[A-Za-z0-9.@\\-_]{1,100}$"
},
{
"op": "add",
"path": "/definitions/ActionTypeId/properties/Version/pattern",
"value": "^[0-9A-Za-z_-]{1,9}$"
"value": "^[0-9A-Za-z_\\-]{1,9}$"
},
{
"op": "add",
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
"uniqueItems": true
},
"Name": {
"pattern": "^[A-Za-z0-9.@\\-_]{1,100}$",
"type": "string"
},
"Namespace": {
@@ -75,7 +76,7 @@
"type": "string"
},
"Version": {
"pattern": "^[0-9A-Za-z_-]{1,9}$",
"pattern": "^[0-9A-Za-z_\\-]{1,9}$",
"type": "string"
}
},
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
"uniqueItems": true
},
"Name": {
"pattern": "^[A-Za-z0-9.@\\-_]{1,100}$",
"type": "string"
},
"Namespace": {
@@ -75,7 +76,7 @@
"type": "string"
},
"Version": {
"pattern": "^[0-9A-Za-z_-]{1,9}$",
"pattern": "^[0-9A-Za-z_\\-]{1,9}$",
"type": "string"
}
},
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
"uniqueItems": true
},
"Name": {
"pattern": "^[A-Za-z0-9.@\\-_]{1,100}$",
"type": "string"
},
"Namespace": {
@@ -75,7 +76,7 @@
"type": "string"
},
"Version": {
"pattern": "^[0-9A-Za-z_-]{1,9}$",
"pattern": "^[0-9A-Za-z_\\-]{1,9}$",
"type": "string"
}
},
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
"uniqueItems": true
},
"Name": {
"pattern": "^[A-Za-z0-9.@\\-_]{1,100}$",
"type": "string"
},
"Namespace": {
@@ -99,7 +100,7 @@
"type": "string"
},
"Version": {
"pattern": "^[0-9A-Za-z_-]{1,9}$",
"pattern": "^[0-9A-Za-z_\\-]{1,9}$",
"type": "string"
}
},
2 changes: 1 addition & 1 deletion src/cfnlint/rules/conditions/Used.py
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ def match(self, cfn: Template) -> RuleMatches:
conditions = cfn.template.get("Conditions", {})
if conditions:
# Get all "If's" that reference a Condition
iftrees = cfn.search_deep_keys("Fn::If")
iftrees = cfn.transform_pre["Fn::If"]

for iftree in iftrees:
if isinstance(iftree[-1], list):
45 changes: 45 additions & 0 deletions src/cfnlint/rules/parameters/AllowedPattern.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from typing import Any

import regex as re

from cfnlint.jsonschema import ValidationError, ValidationResult, Validator
from cfnlint.rules.jsonschema.CfnLintKeyword import CfnLintKeyword


class AllowedPattern(CfnLintKeyword):

id = "I2003"
shortdesc = "Validate AllowedPattern is a valid regexs"
description = (
"Validate the pattern defined in a AllowedPattern. "
"This is informational as the service side regex library is "
"different than the Python one"
)
source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html"
tags = ["parameters", "allowed pattern"]

def __init__(self) -> None:
super().__init__(
keywords=[
"Parameters/*/AllowedPattern",
]
)

def validate(
self, validator: Validator, keywords: Any, instance: Any, schema: dict[str, Any]
) -> ValidationResult:

if not validator.is_type(instance, "string"):
return

try:
re.compile(instance)
except Exception as e:
yield ValidationError(
f"{instance!r} could not be compiled ({str(e)})", rule=self
)
6 changes: 2 additions & 4 deletions src/cfnlint/rules/parameters/Configuration.py
Original file line number Diff line number Diff line change
@@ -48,12 +48,10 @@ def __init__(self):
def _pattern_properties(
self, validator: Validator, aP: Any, instance: Any, schema: Any
):
# We have to rework pattern properties
# to re-add the keyword or we will have an
# infinite loop
# Flip back on add cfn-lint keyword
validator = validator.evolve(
function_filter=validator.function_filter.evolve(
add_cfn_lint_keyword=False,
add_cfn_lint_keyword=True,
)
)
yield from patternProperties(validator, aP, instance, schema)
Loading