Skip to content
Merged
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
7 changes: 4 additions & 3 deletions .github/workflows/stacklet-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ jobs:
persist-credentials: false

# This repo only ever ships prereleases stamped with a +HASH local
# version segment (…aN+HASH). A version without both is a misconfiguration
# version segment (…aN+HASH, or …aN.postM+HASH for a hotfix on a
# release branch). A version without both is a misconfiguration
# — fail loudly before building or publishing anything.
- name: Assert prerelease version
shell: bash
run: |
set -euo pipefail
version=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
if [[ ! "$version" =~ a[0-9]+[+][0-9a-zA-Z.]+$ ]]; then
echo "::error::cloud-custodian-prerelease must ship a stamped prerelease version (…aN+HASH); got '$version'"
if [[ ! "$version" =~ a[0-9]+(\.post[0-9]+)?[+][0-9a-zA-Z.]+$ ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Release guard remains untested

The new .postM version grammar has no automated coverage, even though this manually dispatched guard previously blocked releases after an untested regex change. This is non-blocking, but tests should cover ordinary stamped prereleases, stamped post releases, missing prerelease or local segments, and .post without digits to prevent another publishing failure.

Knowledge Base Used: Require the release version hash stamp

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/stacklet-release.yml
Line: 37

Comment:
**Release guard remains untested**

The new `.postM` version grammar has no automated coverage, even though this manually dispatched guard previously blocked releases after an untested regex change. This is non-blocking, but tests should cover ordinary stamped prereleases, stamped post releases, missing prerelease or local segments, and `.post` without digits to prevent another publishing failure.

**Knowledge Base Used:** [Require the release version hash stamp](https://app.greptile.com/stacklet/-/custom-context/knowledge-base/stacklet/cloud-custodian-prerelease/-/reverts/incident-mitigation_10-20260821-prerelease-hash-guard-b5ad5bf.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid, but not fixing on the release branch will fix on mainline (stacklet/intergration)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That makes sense. Since this PR is limited to the release-branch hotfix, keeping the release-guard coverage follow-up on stacklet/integration avoids expanding the backport. No further change is needed here.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@cubic-dev-ai cubic-dev-ai Bot Sep 4, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Add automated coverage for this release guard's accepted and rejected version forms, including .postM, missing prerelease or local segments, and .post without digits. Without tests, a future regex edit can block or allow an invalid release unnoticed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/stacklet-release.yml, line 37:

<comment>Add automated coverage for this release guard's accepted and rejected version forms, including `.postM`, missing prerelease or local segments, and `.post` without digits. Without tests, a future regex edit can block or allow an invalid release unnoticed.</comment>

<file context>
@@ -26,15 +26,16 @@ jobs:
           version=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
-          if [[ ! "$version" =~ a[0-9]+[+][0-9a-zA-Z.]+$ ]]; then
-            echo "::error::cloud-custodian-prerelease must ship a stamped prerelease version (…aN+HASH); got '$version'"
+          if [[ ! "$version" =~ a[0-9]+(\.post[0-9]+)?[+][0-9a-zA-Z.]+$ ]]; then
+            echo "::error::cloud-custodian-prerelease must ship a stamped prerelease version (…aN+HASH or …aN.postM+HASH); got '$version'"
             exit 1
</file context>
Fix with cubic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid, but not fixing on the release branch will fix on mainline (stacklet/intergration)

echo "::error::cloud-custodian-prerelease must ship a stamped prerelease version (…aN+HASH or …aN.postM+HASH); got '$version'"
exit 1
fi
echo "Releasing prerelease version $version"
Expand Down
10 changes: 8 additions & 2 deletions c7n/filters/waf.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,14 @@ def _legacy_match(self, r_acl):
# based on the scope
def _get_web_acls(self, scope):
if self._cached_web_acls is None:
self._cached_web_acls = self.manager.get_resource_manager('wafv2').resources(
query=dict(Scope=scope),
# Scope must be supplied as resource manager data rather than a query
# kwarg. The wafv2 manager derives both the API Scope and the region
# used for the call from its own data (CLOUDFRONT web acls are global
# and must be addressed via us-east-1), and a Scope passed via
# resources(query=...) is overwritten by the manager's own value.
wafv2 = self.manager.get_resource_manager(
'wafv2', {'query': [{'Scope': scope}]})
self._cached_web_acls = wafv2.resources(
# required to get the additional detail needed for this filter (e.g. Rules), but
# the legacy mode does not require additional detail
augment=(not self._is_legacy)
Expand Down
9 changes: 9 additions & 0 deletions c7n/resources/waf.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,15 @@ def get_permissions(self):
# set REGIONAL for Scope as default
def get_query_params(self, query_params):
query_params = query_params or {}
# Scope is owned by the resource manager, since it also determines which
# region the wafv2 API is called in. Fail loudly rather than silently
# discarding a conflicting Scope supplied by the caller.
requested_scope = query_params.get('Scope')
if requested_scope is not None and requested_scope != self.manager.scope:
raise PolicyValidationError(
"wafv2 Scope conflict: %s was requested but the resource manager "
"scope is %s. Pass Scope via the resource manager's 'query' data."
% (requested_scope, self.manager.scope))
# Parse query from policy data
queries = self.manager.data.get('query', [])
for q in queries:
Expand Down
2 changes: 1 addition & 1 deletion c7n/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Generated via tools/dev/devpkg.py
version = "0.9.52a5+a83dda5c9"
version = "0.9.52a5.post1+a83dda5c9"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies = [
"urllib3>2",
]
name = "c7n"
version = "0.9.52a5+a83dda5c9"
version = "0.9.52a5.post1+a83dda5c9"
description = "Cloud Custodian - Policy Rules Engine"
readme = "README.md"
classifiers = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"status_code": 200,
"data": {
"ResponseMetadata": {},
"DistributionList": {
"Marker": "",
"MaxItems": 100,
"IsTruncated": false,
"Quantity": 1,
"Items": [
{
"Id": "E1EXAMPLE1234",
"ARN": "arn:aws:cloudfront::123456789012:distribution/E1EXAMPLE1234",
"Status": "Deployed",
"LastModifiedTime": {
"hour": 0,
"microsecond": 0,
"second": 0,
"year": 2024,
"month": 1,
"day": 1,
"minute": 0
},
"DomainName": "d111111abcdef8.cloudfront.net",
"Aliases": {
"Quantity": 0,
"Items": []
},
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3-example",
"DomainName": "example-bucket.s3.amazonaws.com",
"OriginPath": ""
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-example",
"ViewerProtocolPolicy": "redirect-to-https"
},
"CacheBehaviors": {
"Quantity": 0,
"Items": []
},
"CustomErrorResponses": {
"Quantity": 0,
"Items": []
},
"Comment": "",
"PriceClass": "PriceClass_All",
"Enabled": true,
"ViewerCertificate": {
"CloudFrontDefaultCertificate": true,
"MinimumProtocolVersion": "TLSv1",
"CertificateSource": "cloudfront"
},
"Restrictions": {
"GeoRestriction": {
"RestrictionType": "none",
"Quantity": 0
}
},
"WebACLId": "arn:aws:wafv2:us-east-1:123456789012:global/webacl/example-cloudfront-acl/11111111-2222-3333-4444-555555555555",
"HttpVersion": "http2",
"IsIPV6Enabled": true
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"status_code": 200,
"data": {
"ResponseMetadata": {},
"PaginationToken": "",
"ResourceTagMappingList": [
{
"ResourceARN": "arn:aws:cloudfront::123456789012:distribution/E1EXAMPLE1234",
"Tags": []
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"status_code": 200,
"data": {
"ResponseMetadata": {},
"NextMarker": "",
"WebACLs": [
{
"Name": "example-cloudfront-acl",
"Id": "11111111-2222-3333-4444-555555555555",
"Description": "",
"LockToken": "22222222-3333-4444-5555-666666666666",
"ARN": "arn:aws:wafv2:us-east-1:123456789012:global/webacl/example-cloudfront-acl/11111111-2222-3333-4444-555555555555"
}
]
}
}
42 changes: 42 additions & 0 deletions tests/test_cloudfront.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Copyright The Cloud Custodian Authors.
# SPDX-License-Identifier: Apache-2.0
from .common import BaseTest, event_data
from c7n.exceptions import PolicyValidationError
from c7n.resources.aws import shape_validate
from c7n.resources.waf import WAFV2, DescribeWafV2
from c7n.utils import local_session, jmespath_compile
from unittest.mock import MagicMock, patch
from botocore.exceptions import ClientError
Expand Down Expand Up @@ -1157,3 +1159,43 @@ def test_wafv2_cloudfront_scope_uses_us_east_1(self):
manager = WAFV2(ctx, {})
manager.get_client()
mock_session.client.assert_called_once_with('wafv2', region_name='us-west-2')

def test_wafv2_filter_uses_cloudfront_scope(self):
"""The wafv2-enabled filter must list CLOUDFRONT-scoped web acls.

A distribution with a CLOUDFRONT WebACL attached must not be reported by
`state: false`. If the filter's requested scope is dropped in favor of the
REGIONAL default, the distribution's CLOUDFRONT WebACL ARN is never found
and the distribution is incorrectly reported as unprotected.
"""
factory = self.replay_flight_data("test_distribution_wafv2_filter_scope")
p = self.load_policy(
{
"name": "wafv2-cloudfront-scope",
"resource": "distribution",
"filters": [{"type": "wafv2-enabled", "state": False}],
},
session_factory=factory,
)

scopes = []
original = DescribeWafV2.get_query_params

def record(self, query):
params = original(self, query)
scopes.append(params.get('Scope'))
return params

with patch.object(DescribeWafV2, 'get_query_params', record):
resources = p.run()

self.assertEqual(scopes, ['CLOUDFRONT'])
self.assertEqual(len(resources), 0)

def test_wafv2_filter_scope_conflict_raises(self):
"""A Scope passed via resources(query=) that conflicts with the resource
manager's own scope is an error rather than being silently ignored."""
p = self.load_policy({"name": "wafv2", "resource": "distribution"})
mgr = WAFV2(p.ctx, {'query': [{'Scope': 'REGIONAL'}]})
with self.assertRaises(PolicyValidationError):
mgr.source.get_query_params({'Scope': 'CLOUDFRONT'})
2 changes: 1 addition & 1 deletion tools/c7n_awscc/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [
"jsonpatch<2.0,>=1.32",
]
name = "c7n_awscc"
version = "0.1.37a5+a83dda5c9"
version = "0.1.37a5.post1+a83dda5c9"
readme = "readme.md"
description = "Cloud Custodian - AWS Cloud Control Provider"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_azure/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ dependencies = [
"requests<3.0.0,>=2.22.0",
]
name = "c7n_azure"
version = "0.7.51a5+a83dda5c9"
version = "0.7.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Azure Support"
readme = "readme.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_gcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies = [
"retrying<2.0.0,>=1.3.3",
]
name = "c7n_gcp"
version = "0.4.51a5+a83dda5c9"
version = "0.4.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Google Cloud Provider"
readme = "readme.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_kube/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [
"kubernetes>=10.0.1",
]
name = "c7n_kube"
version = "0.2.51a5+a83dda5c9"
version = "0.2.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Kubernetes Provider"
readme = "readme.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_left/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies = [
"tfparse<1.0,>=0.6",
]
name = "c7n_left"
version = "0.3.38a5+a83dda5c9"
version = "0.3.38a5.post1+a83dda5c9"
description = "Custodian policies for IAAC definitions"
readme = "README.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_mailer/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies = [
"types-six<2.0.0,>=1.16.10",
]
name = "c7n_mailer"
version = "0.6.51a5+a83dda5c9"
version = "0.6.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Reference Mailer"
readme = "README.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_oci/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies = [
"pyopenssl>=24"
]
name = "c7n_oci"
version = "0.1.26a5+a83dda5c9"
version = "0.1.26a5.post1+a83dda5c9"
description = "Cloud Custodian - OCI Support"
readme = "readme.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_openstack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies = [
"openstacksdk==0.52.0",
]
name = "c7n_openstack"
version = "0.1.42a5+a83dda5c9"
version = "0.1.42a5.post1+a83dda5c9"
description = "Cloud Custodian - OpenStack Provider"
readme = "readme.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_org/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies = [
"click>=8.0",
]
name = "c7n_org"
version = "0.6.51a5+a83dda5c9"
version = "0.6.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Parallel Execution"
readme = "README.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_policystream/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies = [
"requests<3.0.0,>=2.22.0",
]
name = "c7n_policystream"
version = "0.4.51a5+a83dda5c9"
version = "0.4.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Git Commits as Logical Policy Changes"
readme = "README.md"
classifiers = [
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_sphinxext/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies = [
"sphinx-rtd-theme>=1.0.0",
]
name = "c7n_sphinxext"
version = "1.1.51a5+a83dda5c9"
version = "1.1.51a5.post1+a83dda5c9"
description = "Cloud Custodian - Sphinx Extensions"
classifiers = [
"License :: OSI Approved :: Apache Software License",
Expand Down
2 changes: 1 addition & 1 deletion tools/c7n_tencentcloud/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies = [
"tencentcloud-sdk-python<4.0.0,>=3.0.783",
]
name = "c7n_tencentcloud"
version = "0.1.34a5+a83dda5c9"
version = "0.1.34a5.post1+a83dda5c9"
description = "Cloud Custodian - Tencent Cloud Provider"
readme = "readme.md"

Expand Down
Loading