Repository navigation
chore: merge upstream cloud-custodian and stamp 0.9.54a1+0fa7d19f5 (ENG-9376) - #27
Conversation
cloud-custodian#10972) * aws - add s3files file-system, mount-target and access-point resources * exempt s3files-mount-target from arn info meta lint * address review feedback: drop redundant policy_attribute init, remove access-point-account filter, align parent annotation * drop redundant permission_prefix matching service default * use universal tagging for file-system and access-point resources --------- Co-authored-by: Matthew Heidelbaugh <42586165+mattheidelbaugh@users.noreply.github.com>
…#10973) * aws - add s3tables table-bucket and table resources * Using resource_type arn for tag augment * drop redundant policy_attribute * drop redundant permission_prefix matching service default * add replication filter for table buckets * "use universal tagging for table-bucket and table resources --------- Co-authored-by: Matthew Heidelbaugh <42586165+mattheidelbaugh@users.noreply.github.com>
…#11007) * adding event-connection resource * Update test_policy.py --------- Co-authored-by: Matthew Heidelbaugh <42586165+mattheidelbaugh@users.noreply.github.com>
…ch baselines, maintenance windows, and associations (cloud-custodian#11093) * ssm - add tagging support for managed instances, ops items, patch baselines, maintenance windows, and associations * Update ssm.py
…ud-custodian#10883) The ssm-compliance filter advertises a schema default of ['NON_COMPLIANT'] for the optional 'states' field, but process() read self.data['states'] with a hard subscript. Custodian's Draft7Validator does not inject schema defaults into the policy data, so a policy that omits 'states' passed validation and then raised KeyError: 'states' at runtime. Read the value defensively so the advertised default is applied instead of crashing. Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> Co-authored-by: Matthew Heidelbaugh <42586165+mattheidelbaugh@users.noreply.github.com>
…#11015) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resources * qbusiness-application (top-level) * qbusiness-index, qbusiness-plugin, qbusiness-data-accessor (children of application) * qbusiness-data-source (enumerated across each application/index pair) Filters / Actions * cross-account filter on qbusiness-application * Universal tagging on all resources
- iot-policy: parses policyDocument in augment, has-statement and attached filters, delete action - iot-certificate: describe_certificate detail with creationDate for age based filtering, set-inactive action - iot-ota-update: unsigned filter over otaUpdateFiles codeSigning - iot: tag support for ownership attribution - account: iot-logging filter for GetV2LoggingOptions
…todian#11063) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…todian#11116) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dian#11137) --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
--------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d-custodian#11168) QueryResourceManager.get_cache_key keyed the resource cache on the source type and query alone, so every Kubernetes resource type mapped to the same cache entry. With caching enabled, the first policy to run populated it and every later policy within the cache period was handed that policy's resources instead of querying its own: a k8s.deployment policy run after a k8s.pod policy filtered the cluster's pods, matched nothing, and reported an empty result without error. Key on the resource manager's class as well, as c7n.query's QueryResourceManager does. Custom resources were already distinct, since their group, version and plural are part of the query. --------- Co-authored-by: AJ Kerrigan <aj@stacklet.io> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
--------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
--------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
--------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
--------- Co-authored-by: Caleb Syring <caleb.syring@sixfeetup.com> Co-authored-by: Kody Mullins <kody.mullins@sixfeetup.com>
--------- Co-authored-by: AJ Kerrigan <aj@stacklet.io> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dian#11215) --------- Co-authored-by: Sayak Satpathi <170233065+sayaksatpathi@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#11187) Co-authored-by: Usman Qureshi <usman.nja.14@gmail.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: AJ Kerrigan <aj@stacklet.io>
Co-authored-by: AJ Kerrigan <aj@speckledmonkey.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…an#11194) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: AJ Kerrigan <aj@speckledmonkey.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…ains (cloud-custodian#11226) Signed-off-by: Srinivas Chippagiri <cvas22@gmail.com>
…sources (cloud-custodian#11144) --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…logs (cloud-custodian#10650) --------- Co-authored-by: Daniel Lindsley <daniel@sixfeetup.com> Co-authored-by: Kody Mullins <kody.mullins@sixfeetup.com>
…an#10642) --------- Co-authored-by: Daniel Lindsley <daniel@sixfeetup.com> Co-authored-by: Caleb Syring <caleb.syring@sixfeetup.com> Co-authored-by: Kody Mullins <kody.mullins@sixfeetup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* k8s - add api call stats and metrics Kubernetes policies fell back to the null api stats collector, so metadata.json carried no api-stats and no ApiCalls metric was emitted. Count each kubernetes client call by method name for the duration of a policy execution, recording the counts in the api-stats metadata in the same shape as aws, and emit ApiCalls and ApiTime metrics. Failed calls are counted too. * core - share api call stats between providers, add aws call timing Move api call counting, timing, metadata and metrics into a core base class, so a provider only needs to hook its client to record calls. Providers whose clients have no event hooks of their own record calls through a shared function. Move k8s and aws onto it. aws keeps its call names, metadata and interface, and now times each call between botocore's before-call and after-call events, emitting an ApiTime metric alongside ApiCalls. * core - lock api call stats updates, check aws stats cleanup Filters and actions make api calls from thread pools, so serialise updates to the call counts and total time. Check that an aws execution removes its event handlers using a client created from the cached session afterwards; a client keeps its own copy of the session's handlers, so one created during the execution can't show it. * core - round ApiTime metric, record aws transport errors Round the ApiTime metric to milliseconds, as full float precision is noise in metric output. Record aws calls that fail in transport, such as connection errors. botocore emits after-call-error rather than after-call for those, without the operation model, so before-call now keeps the call's name alongside its start time in the request context.
* gcp - add api call stats and metrics gcp policies fell back to the null api stats collector, so metadata.json carried no api-stats and no ApiCalls metric was emitted. Record each gcp api call by its method id, such as iam.roles.list, with its duration, on the shared api call stats base. A call is recorded once however many attempts its retries take, and failed calls are counted too. * gcp - rename api stats test module to a unique name pytest can't import two test modules with the same basename in one run, and c7n_kube already has a test_api_stats.py.
azure policies fell back to the null api stats collector, so metadata.json carried no api-stats and no ApiCalls metric was emitted. Record each azure sdk request attempt, on the shared api call stats base, named by http method and resource type with resource names left out, such as GET Microsoft.Compute/virtualMachines; requests outside resource manager are named by host, leaving out the account or vault name. Clients get a custom hook policy that times each attempt, including ones that fail without a response, and still runs the existing hooks. The send override for legacy clients records each attempt in its retry loop.
# Conflicts: # c7n/version.py # pyproject.toml # tools/c7n_awscc/pyproject.toml # tools/c7n_azure/pyproject.toml # tools/c7n_gcp/pyproject.toml # tools/c7n_kube/pyproject.toml # tools/c7n_left/pyproject.toml # tools/c7n_mailer/pyproject.toml # tools/c7n_oci/pyproject.toml # tools/c7n_openstack/pyproject.toml # tools/c7n_org/pyproject.toml # tools/c7n_policystream/pyproject.toml # tools/c7n_sphinxext/pyproject.toml # tools/c7n_tencentcloud/pyproject.toml # uv.lock
Upstream released 0.9.53.0, so the base moves to 0.9.54 and the alpha series restarts at a1; the workspace tools each tick one patch. Drop the tfparse exclude-newer-package override. It was added in #3 to admit tfparse 0.6.20 past the 7-day cooldown, but as a fixed date it now caps tfparse at releases before 2026-06-02. Without it the lock resolves tfparse 0.6.22, matching upstream. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
21 issues found across 961 files
Confidence score: 2/5
tools/c7n_azure/c7n_azure/utils.pycan send its bearer token to anhttp://endpoint when the Run History URL comes fromdiscoveryUrl. Require HTTPS before sending authorization headers.tools/c7n_gcp/c7n_gcp/actions/iampolicy.pycan remove IAM Conditions when audit-config and binding updates are combined. Request policy version 3 before fetching bindings to write.tools/c7n_gcp/c7n_gcp/resources/workspace.pychecks only the first assignment page, so it can miss users on later pages and report MFA fields that may not protect them. Useexecute_paged_queryfor the check.tools/c7n_azure/c7n_azure/filters.pyappendsself.filterwithout grouping, so anorbranch can bypass the generated dimension clauses. Parenthesizeself.filterbefore joining it withand.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tools/c7n_azure/c7n_azure/utils.py">
<violation number="1" location="tools/c7n_azure/c7n_azure/utils.py:635">
P2: This session bypasses the API statistics collector, so ML history requests are silently omitted from `api-stats` and `ApiTime`. Instrument each adapter attempt, including retry and exception paths, or record these requests at the caller.</violation>
<violation number="2" location="tools/c7n_azure/c7n_azure/utils.py:636">
P1: Custom agent: **Flag Security Vulnerabilities**
This session sends its default `Authorization` bearer token to `http://` URLs as well as HTTPS. The Run History caller derives its endpoint from `discoveryUrl` without checking the scheme; reject non-HTTPS endpoints before posting.</violation>
</file>
<file name="tools/c7n_gcp/c7n_gcp/utils.py">
<violation number="1" location="tools/c7n_gcp/c7n_gcp/utils.py:330">
P1: `_canonicalize_value` turns Cloud Logging typed and wildcard values into quoted strings, so filters with different routing semantics can be grouped as identical and reported as overlapping sinks. Preserve these literals with operator-aware canonicalization instead of quoting every unquoted word.</violation>
</file>
<file name="docs/source/azure/examples/metrics-general.rst">
<violation number="1" location="docs/source/azure/examples/metrics-general.rst:71">
P2: The prose overstates parent-scope selection: resources without `c7n:parent-id` fall back to their own ID even when `dimensions` is set. Qualify this sentence with the annotation requirement so users do not assume every dimensions query targets a parent.</violation>
</file>
<file name="tests/data/placebo/test_iot_certificate_age/iot.DescribeCertificate_1.json">
<violation number="1" location="tests/data/placebo/test_iot_certificate_age/iot.DescribeCertificate_1.json:13">
P2: This response is inconsistent with the paired list response and certificate validity, causing the age test to select a certificate that the list data says is only weeks old. Synchronize both placebo responses and the PEM/validity dates, or use a consistently old certificate fixture for the age case.</violation>
</file>
<file name="tools/c7n_azure/c7n_azure/resources/network_security_group.py">
<violation number="1" location="tools/c7n_azure/c7n_azure/resources/network_security_group.py:504">
P2: Omitting a direction currently defaults it to `matched`, so `ingress: all` also deletes any egress rules annotated by an egress filter on the same policy. Treat an omitted direction as a no-op (or add an explicit opt-in) so the action cannot remove rules outside the requested direction.</violation>
</file>
<file name="c7n/resources/s3files.py">
<violation number="1" location="c7n/resources/s3files.py:48">
P2: This resource declares `tag:GetResources` as mandatory even though `DescribeFileSystem` reads tags from the S3 Files response directly and does not call the tagging API. Least-privilege roles running any FileSystem policy will therefore require an unused permission; separate universal tag registration from source permission discovery or omit this permission for the custom source.</violation>
</file>
<file name="tools/c7n_gcp/c7n_gcp/resources/logging.py">
<violation number="1" location="tools/c7n_gcp/c7n_gcp/resources/logging.py:182">
P2: This truthiness check drops enabled exclusions with an empty filter, even though an empty Cloud Logging filter matches every entry. Such a sink should have no effective routing, but the filter currently treats it as routing its base filter and can report false overlap or duplicate matches; handle empty/comment-only exclusions as match-all exclusions instead of skipping them.</violation>
</file>
<file name="tools/c7n_gcp/c7n_gcp/resources/workspace.py">
<violation number="1" location="tools/c7n_gcp/c7n_gcp/resources/workspace.py:79">
P1: This guard checks only the first assignment page, so an assignment returned on a later page is missed and the policy reports MFA fields that may not protect those users. Iterate with `execute_paged_query` and reject any page containing assignments.</violation>
</file>
<file name="tools/c7n_azure/c7n_azure/filters.py">
<violation number="1" location="tools/c7n_azure/c7n_azure/filters.py:218">
P2: `dimensions` is now accepted for Cosmos database and collection metric filters, but both subclasses discard it in their `get_filter` overrides. Such policies validate while silently ignoring the requested dimension restriction; compose dimensions in those overrides or reject the option in their schemas.</violation>
<violation number="2" location="tools/c7n_azure/c7n_azure/filters.py:341">
P1: Appending `self.filter` without grouping lets an `or` branch bypass every generated dimension clause. Wrap `self.filter` in parentheses before joining it with `and`.</violation>
</file>
<file name="tools/c7n_azure/c7n_azure/actions/firewall.py">
<violation number="1" location="tools/c7n_azure/c7n_azure/actions/firewall.py:45">
P2: Sorting append-mode bypass rules discards the policy’s explicit order. Preserve first-seen order while deduplicating, otherwise `test_bypass_network_rules_action` and consumers relying on stable serialization receive `AzureServices,Metrics` instead of `Metrics,AzureServices`.</violation>
</file>
<file name="c7n/resources/kafka.py">
<violation number="1" location="c7n/resources/kafka.py:77">
P2: The unconditional append exposes non-broker `ListNodes` entries as brokers. Append only entries containing `BrokerNodeInfo`, so controller or other node records do not produce resources with a missing ID and invalid metrics dimensions.</violation>
<violation number="2" location="c7n/resources/kafka.py:91">
P1: This child query calls `ListNodes` for every Kafka cluster, including serverless clusters. Skip serverless parents before enumeration; otherwise an account containing one can make the entire `kafka-broker` policy fail instead of returning only provisioned brokers.</violation>
<violation number="3" location="c7n/resources/kafka.py:94">
P1: Using `BrokerId` as the resource ID breaks identity-based retrieval because the child query filters raw nodes before augmentation, where that key does not exist. Use the raw unique `NodeARN` as `id` or override the child lookup to augment before filtering.</violation>
</file>
<file name="tools/c7n_azure/c7n_azure/resources/machine_learning_compute_cluster.py">
<violation number="1" location="tools/c7n_azure/c7n_azure/resources/machine_learning_compute_cluster.py:274">
P2: The filter ignores `uniqueChildRunComputeTargets` and `compute.target` when `run.target` is null. Include those fields so active pipeline parents cannot cause their compute cluster to be selected as inactive.</violation>
</file>
<file name="Makefile">
<violation number="1" location="Makefile:53">
P3: The no-test branch points to nonexistent `make diff-coverage-full` and `TEST_PATH` interfaces, so developers cannot follow its recovery instructions. Point to an existing command or implement those interfaces.</violation>
</file>
<file name="c7n/output.py">
<violation number="1" location="c7n/output.py:221">
P2: The process-global recorder list mixes API calls between overlapping policy executions. Scope the active recorder to its execution while preserving propagation to the worker threads used by filters and actions.</violation>
</file>
<file name="c7n/resources/aiops.py">
<violation number="1" location="c7n/resources/aiops.py:45">
P2: AIOps queries use `DescribeWithResourceTags`, which calls the Resource Groups Tagging API, but this resource does not declare `tag:GetResources`; generated execution roles can therefore fail tag augmentation with `AccessDenied`. Set `universal_taggable = object()` on the resource type or use a source that fetches tags through `aiops:ListTagsForResource`.</violation>
</file>
<file name="c7n/resources/vpc.py">
<violation number="1" location="c7n/resources/vpc.py:898">
P2: This unconditionally adds `MaxResults` to queries containing `GroupIds`, which EC2 rejects. Only set the pagination parameter when `GroupIds` is absent.</violation>
</file>
<file name="tools/c7n_gcp/c7n_gcp/actions/iampolicy.py">
<violation number="1" location="tools/c7n_gcp/c7n_gcp/actions/iampolicy.py:305">
P1: Combined audit-config and binding updates can remove IAM Conditions because the action writes bindings fetched without requesting policy version 3. Request version 3 from `getIamPolicy` before writing bindings, including for the resource-specific argument overrides.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Re-trigger cubic
| token = azure_session.credentials.get_token(token_scope) | ||
|
|
||
| session = requests.Session() | ||
| session.headers['Authorization'] = f'Bearer {token.token}' |
There was a problem hiding this comment.
P1: Custom agent: Flag Security Vulnerabilities
This session sends its default Authorization bearer token to http:// URLs as well as HTTPS. The Run History caller derives its endpoint from discoveryUrl without checking the scheme; reject non-HTTPS endpoints before posting.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tools/c7n_azure/c7n_azure/utils.py, line 636:
<comment>This session sends its default `Authorization` bearer token to `http://` URLs as well as HTTPS. The Run History caller derives its endpoint from `discoveryUrl` without checking the scheme; reject non-HTTPS endpoints before posting.</comment>
<file context>
@@ -608,6 +617,42 @@ def get_keyvault_auth_endpoint(cloud_endpoints):
+ token = azure_session.credentials.get_token(token_scope)
+
+ session = requests.Session()
+ session.headers['Authorization'] = f'Bearer {token.token}'
+
+ if max_retries:
</file context>
| token_type, value = token | ||
| if token_type == "STRING" and value[0] == "'" and '"' not in value: | ||
| value = f'"{value[1:-1]}"' | ||
| elif token_type == "WORD" and not set(value) & set("\"'\\"): |
There was a problem hiding this comment.
P1: _canonicalize_value turns Cloud Logging typed and wildcard values into quoted strings, so filters with different routing semantics can be grouped as identical and reported as overlapping sinks. Preserve these literals with operator-aware canonicalization instead of quoting every unquoted word.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tools/c7n_gcp/c7n_gcp/utils.py, line 330:
<comment>`_canonicalize_value` turns Cloud Logging typed and wildcard values into quoted strings, so filters with different routing semantics can be grouped as identical and reported as overlapping sinks. Preserve these literals with operator-aware canonicalization instead of quoting every unquoted word.</comment>
<file context>
@@ -1,3 +1,684 @@
+ token_type, value = token
+ if token_type == "STRING" and value[0] == "'" and '"' not in value:
+ value = f'"{value[1:-1]}"'
+ elif token_type == "WORD" and not set(value) & set("\"'\\"):
+ value = f'"{value}"'
+ return value.lower() if lower else value
</file context>
| assignments = client.execute_query( | ||
| 'list', | ||
| {'filter': 'customer=="customers/%s"' % get_workspace_customer()}) | ||
| if assignments.get('inboundSsoAssignments'): | ||
| raise PolicyExecutionError( | ||
| "Organizations with SSO assignments aren't currently" | ||
| " supported by gcp.workspace-user.") | ||
|
|
There was a problem hiding this comment.
P1: This guard checks only the first assignment page, so an assignment returned on a later page is missed and the policy reports MFA fields that may not protect those users. Iterate with execute_paged_query and reject any page containing assignments.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tools/c7n_gcp/c7n_gcp/resources/workspace.py, line 79:
<comment>This guard checks only the first assignment page, so an assignment returned on a later page is missed and the policy reports MFA fields that may not protect those users. Iterate with `execute_paged_query` and reject any page containing assignments.</comment>
<file context>
@@ -0,0 +1,103 @@
+ 'cloudidentity', 'v1beta1', 'inboundSsoAssignments',
+ scopes=SSO_ASSIGNMENT_SCOPES)
+ # The quoting is required, though the api's own example omits it.
+ assignments = client.execute_query(
+ 'list',
+ {'filter': 'customer=="customers/%s"' % get_workspace_customer()})
</file context>
| assignments = client.execute_query( | |
| 'list', | |
| {'filter': 'customer=="customers/%s"' % get_workspace_customer()}) | |
| if assignments.get('inboundSsoAssignments'): | |
| raise PolicyExecutionError( | |
| "Organizations with SSO assignments aren't currently" | |
| " supported by gcp.workspace-user.") | |
| for page in client.execute_paged_query( | |
| 'list', | |
| {'filter': 'customer=="customers/%s"' % get_workspace_customer()}): | |
| if page.get('inboundSsoAssignments'): | |
| raise PolicyExecutionError( | |
| "Organizations with SSO assignments aren't currently" | |
| " supported by gcp.workspace-user.") |
| self.manager.ctx.policy.name, d['name'], resource.get('id'))) | ||
| clauses.append("%s eq '%s'" % (d['name'], str(value).replace("'", "''"))) | ||
| dim_filter = " and ".join(clauses) | ||
| return "%s and %s" % (self.filter, dim_filter) if self.filter else dim_filter |
There was a problem hiding this comment.
P1: Appending self.filter without grouping lets an or branch bypass every generated dimension clause. Wrap self.filter in parentheses before joining it with and.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tools/c7n_azure/c7n_azure/filters.py, line 341:
<comment>Appending `self.filter` without grouping lets an `or` branch bypass every generated dimension clause. Wrap `self.filter` in parentheses before joining it with `and`.</comment>
<file context>
@@ -262,11 +312,33 @@ def get_metric_data(self, resource):
+ self.manager.ctx.policy.name, d['name'], resource.get('id')))
+ clauses.append("%s eq '%s'" % (d['name'], str(value).replace("'", "''")))
+ dim_filter = " and ".join(clauses)
+ return "%s and %s" % (self.filter, dim_filter) if self.filter else dim_filter
def _write_metric_to_resource(self, resource, metrics_data, m):
</file context>
| return "%s and %s" % (self.filter, dim_filter) if self.filter else dim_filter | |
| return "(%s) and %s" % (self.filter, dim_filter) if self.filter else dim_filter |
| parent_spec = ('kafka', 'ClusterArn', True) | ||
| enum_spec = ('list_nodes', 'NodeInfoList', None) | ||
| permissions_enum = ('kafka:ListClustersV2', 'kafka:ListNodes') | ||
| name = id = 'BrokerId' |
There was a problem hiding this comment.
P1: Using BrokerId as the resource ID breaks identity-based retrieval because the child query filters raw nodes before augmentation, where that key does not exist. Use the raw unique NodeARN as id or override the child lookup to augment before filtering.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At c7n/resources/kafka.py, line 94:
<comment>Using `BrokerId` as the resource ID breaks identity-based retrieval because the child query filters raw nodes before augmentation, where that key does not exist. Use the raw unique `NodeARN` as `id` or override the child lookup to augment before filtering.</comment>
<file context>
@@ -54,6 +58,85 @@ class resource_type(TypeInfo):
+ parent_spec = ('kafka', 'ClusterArn', True)
+ enum_spec = ('list_nodes', 'NodeInfoList', None)
+ permissions_enum = ('kafka:ListClustersV2', 'kafka:ListNodes')
+ name = id = 'BrokerId'
+ arn = False
+ dimension = 'Broker ID'
</file context>
| name = id = 'BrokerId' | |
| name = 'BrokerId' | |
| id = 'NodeARN' |
| target = run.get('target') | ||
| if isinstance(target, str) and target: | ||
| targets.add(target.lower()) | ||
| return targets |
There was a problem hiding this comment.
P2: The filter ignores uniqueChildRunComputeTargets and compute.target when run.target is null. Include those fields so active pipeline parents cannot cause their compute cluster to be selected as inactive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tools/c7n_azure/c7n_azure/resources/machine_learning_compute_cluster.py, line 274:
<comment>The filter ignores `uniqueChildRunComputeTargets` and `compute.target` when `run.target` is null. Include those fields so active pipeline parents cannot cause their compute cluster to be selected as inactive.</comment>
<file context>
@@ -0,0 +1,357 @@
+ {'filter': f'endTimeUtc ge {cutoff_text}'},
+ ))
+ for run in runs:
+ target = run.get('target')
+ if isinstance(target, str) and target:
+ targets.add(target.lower())
</file context>
| target = run.get('target') | |
| if isinstance(target, str) and target: | |
| targets.add(target.lower()) | |
| return targets | |
| run_targets = [run.get('target')] | |
| run_targets.extend(run.get('uniqueChildRunComputeTargets') or []) | |
| compute = run.get('compute') or {} | |
| if isinstance(compute, dict): | |
| run_targets.append(compute.get('target')) | |
| targets.update( | |
| target.lower() | |
| for target in run_targets | |
| if isinstance(target, str) and target | |
| ) |
| self.api_time += elapsed | ||
|
|
||
| def __enter__(self): | ||
| _api_call_recorders.append(self) |
There was a problem hiding this comment.
P2: The process-global recorder list mixes API calls between overlapping policy executions. Scope the active recorder to its execution while preserving propagation to the worker threads used by filters and actions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At c7n/output.py, line 221:
<comment>The process-global recorder list mixes API calls between overlapping policy executions. Scope the active recorder to its execution while preserving propagation to the worker threads used by filters and actions.</comment>
<file context>
@@ -177,6 +179,55 @@ def __exit__(self, exc_type=None, exc_value=None, exc_traceback=None):
+ self.api_time += elapsed
+
+ def __enter__(self):
+ _api_call_recorders.append(self)
+ self.push_snapshot()
+
</file context>
| 'InternalServerException', | ||
| ))) | ||
|
|
||
| source_mapping = {'describe': DescribeWithResourceTags} |
There was a problem hiding this comment.
P2: AIOps queries use DescribeWithResourceTags, which calls the Resource Groups Tagging API, but this resource does not declare tag:GetResources; generated execution roles can therefore fail tag augmentation with AccessDenied. Set universal_taggable = object() on the resource type or use a source that fetches tags through aiops:ListTagsForResource.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At c7n/resources/aiops.py, line 45:
<comment>AIOps queries use `DescribeWithResourceTags`, which calls the Resource Groups Tagging API, but this resource does not declare `tag:GetResources`; generated execution roles can therefore fail tag augmentation with `AccessDenied`. Set `universal_taggable = object()` on the resource type or use a source that fetches tags through `aiops:ListTagsForResource`.</comment>
<file context>
@@ -0,0 +1,130 @@
+ 'InternalServerException',
+ )))
+
+ source_mapping = {'describe': DescribeWithResourceTags}
+
+
</file context>
| # and leaves by-id lookups alone since EC2 rejects MaxResults alongside | ||
| # GroupIds. | ||
| query = dict(query or {}) | ||
| query.setdefault('MaxResults', 1000) |
There was a problem hiding this comment.
P2: This unconditionally adds MaxResults to queries containing GroupIds, which EC2 rejects. Only set the pagination parameter when GroupIds is absent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At c7n/resources/vpc.py, line 898:
<comment>This unconditionally adds `MaxResults` to queries containing `GroupIds`, which EC2 rejects. Only set the pagination parameter when `GroupIds` is absent.</comment>
<file context>
@@ -886,6 +886,19 @@ def load_resource(self, item):
+ # and leaves by-id lookups alone since EC2 rejects MaxResults alongside
+ # GroupIds.
+ query = dict(query or {})
+ query.setdefault('MaxResults', 1000)
+ return super().resources(query)
+
</file context>
| query.setdefault('MaxResults', 1000) | |
| if 'GroupIds' not in query: | |
| query.setdefault('MaxResults', 1000) |
| @CHANGED_TEST_FILES=$$(git diff --name-only origin/main | grep -E "test_.*\.py$$" || true); \ | ||
| if [ -z "$$CHANGED_TEST_FILES" ]; then \ | ||
| echo "Error: No test files changed compared to origin/main"; \ | ||
| echo "Use 'make diff-coverage-full' to run all tests for a provider, or specify TEST_PATH=<path>"; \ |
There was a problem hiding this comment.
P3: The no-test branch points to nonexistent make diff-coverage-full and TEST_PATH interfaces, so developers cannot follow its recovery instructions. Point to an existing command or implement those interfaces.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Makefile, line 53:
<comment>The no-test branch points to nonexistent `make diff-coverage-full` and `TEST_PATH` interfaces, so developers cannot follow its recovery instructions. Point to an existing command or implement those interfaces.</comment>
<file context>
@@ -45,6 +45,49 @@ test-coverage:
+ @CHANGED_TEST_FILES=$$(git diff --name-only origin/main | grep -E "test_.*\.py$$" || true); \
+ if [ -z "$$CHANGED_TEST_FILES" ]; then \
+ echo "Error: No test files changed compared to origin/main"; \
+ echo "Use 'make diff-coverage-full' to run all tests for a provider, or specify TEST_PATH=<path>"; \
+ exit 1; \
+ fi; \
</file context>
| echo "Use 'make diff-coverage-full' to run all tests for a provider, or specify TEST_PATH=<path>"; \ | |
| echo "Run 'make test-coverage' for full coverage, or invoke 'uv run pytest <path>' directly"; \ |
ENG-9376
what
Merges upstream cloud-custodian
bf0b50dbc..0fa7d19f5intostacklet/integrationand stamps a new alpha series:c7nbecomes0.9.54a1+0fa7d19f5, and each workspace tool ticks one patch (e.g.c7n-azure0.7.53a1,c7n-left0.3.40a1).Two commits: the upstream merge on its own, then the stamp. The merge's
combined diff (
git show --cc) is empty, so there is no hand-writtencontent in it to review.
Conflicts were the
versionfields inpyproject.toml, the twelvetools/*/pyproject.tomlandc7n/version.py, the root[tool.uv]cooldown block, and
uv.lock. The versions and the 7-day cooldown keepthe branch's side, and
uv.locktakes upstream's. Upstream's otheredits to those files (an
azure-mgmt-eventgridfloor bump and newGOOGLE_WORKSPACE_*test env unsets) merged cleanly and are kept. Nosource file conflicted: #24 landing as a merge commit left the merge
base at
bf0b50dbc.The stamp commit also drops the
tfparseexclude-newer-packageoverride added in #3. It admitted tfparse 0.6.20 past the 7-day
cooldown, but as a fixed date it now capped tfparse at releases before
2026-06-02. Without it the lock resolves tfparse 0.6.22, matching
upstream. Apart from that, the lock differs from upstream's only in the
cooldown span and the workspace versions.
Upstream content picked up:
durationvalue_type; resolver URI caching keys on requestheaders and skips blank CSV rows.
(file-system, mount-target, access-point), Q Business, AIOps
investigation groups, rds-global-cluster, eventbridge event-connection
and kafka-broker, plus expanded IoT support; new actions including
account
set-access-analyzer, amiset-disabled, cloudtrailset-event-selectorsand sagemaker-space update; SSM tagging formanaged instances, ops items, patch baselines, maintenance windows and
associations; security-group listing now paginates; fixes for
invoke-lambda qualifiers, secrets-manager ARN lookup, and tag actions
on CFN
ROLLBACK_COMPLETEstacks and CloudTrail shadow trails.schedule resources, and ML job cancel / data container archive
actions; disk snapshot and NSG remove-rules actions; set-firewall-rules
no longer corrupts rules across resources; API call stats.
artifact resources; set-public-access on buckets, set-ip-forwarding on
instances, set-iam-policy on KMS keys with auditConfig support; project
metrics scoped to one project at a time; API call stats.
No Stacklet code changed.
why
The October c7n upgrade (ENG-9376) needs upstream's latest changes.
Upstream released 0.9.53.0 on 2026-10-01, so per the versioning
convention the base moves to the next unreleased version, 0.9.54, and
the alpha restarts at a1.
This should land as a merge commit, not a squash, so that
0fa7d19f5stays in the ancestry and the next upstream merge has the right merge
base (see #24).
testing
The upstream commits bring their own tests, which arrive with the
merge. No Stacklet code changed, so Stacklet CI and upstream CI running
on this PR is the verification of the code.
For the stamp: all 13 workspace packages are stamped uniformly,
c7n/version.pyis regenerated, no reference to the old hash remains,and the cumulative diff against upstream
maincontains only the knownStacklet deltas (versions, cooldown, CODEOWNERS,
stacklet-*.yml, theci.yml trigger retarget, justfile, release tooling).
make pkg-build-wheelbuilds all 13 wheels and passestwine check --strict,tools/dev/check_release_wheels.pyacceptsthem, and
make lintpasses.docs
No docs needed.
🤖 Generated with Claude Code