Skip to content

Print cloudformation template diff on every update - #7685

Merged
hgreebe merged 10 commits into
aws:developfrom
hgreebe:update-cluster-cfn-changes
Oct 30, 2026
Merged

hgreebe merged 10 commits into
aws:developfrom
hgreebe:update-cluster-cfn-changes

Conversation

@hgreebe

@hgreebe hgreebe commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Things to note:

  • Dry-run takes around 2 seconds longer because it has to generate the cloudformation template
  • Does not log entire cloudfromation template, only the diff, this is because the entire template is very large and not very readable, making it not very useful to see changes from the update. Because the diff is what provides value, it is the only thing logged.

Description of changes

  • On every pcluster update-cluster, and every update-cluster --dryrun, log the diff between the deployed CloudFormation templates of the cluster and the new ones, for the cluster stack and each of its nested stacks (e.g. Queues, LoginNodes). This shows what an update changes in the stacks, which the config change set alone does not, e.g. resources that change because of values evaluated at update time.
  • Logging the diff never fails an update or a dryrun: any failure is logged as a warning.
  • The messages are logged at INFO, so they land in ~/.parallelcluster/pcluster-cli.log for the CLI and in the CloudWatch log group of the API Lambda for the API. They start with [update] or [update dryrun]:
    • [update] Diff of the CloudFormation template of the cluster stack: followed by a unified diff (--- deployed … / +++ new …), or No change.
    • [update] Diff of the CloudFormation template of the nested stack <logical id>: for each nested stack.
    • A new nested stack, which has no deployed template, gets its new template logged as compact JSON. When a deployed template cannot be read (e.g. GetTemplate is denied), only a warning is logged for that stack; if it is the cluster stack's, nothing else is logged.
  • The diff compares the templates as indented JSON with sorted keys, so only differences of content show.
  • The dryrun now builds the templates as the update does, without uploading anything to the cluster bucket and without touching the stack. Its templates therefore do not carry the S3 versions of the target config (ConfigVersion, cluster_config_version, instance_types_data_version are empty).
  • The deployed templates are read with GetTemplate, and the nested stacks are found with DescribeStackResources; both are already allowed by the ParallelCluster policies. A failure to read them is logged as a warning and does not prevent the update.
  • Messages longer than 150000 characters are logged in numbered parts ((part i/n)), so that each one stays under the 256 KB limit of a CloudWatch Logs event once escaped by the API Lambda logger.
  • The query strings of presigned S3 URLs are redacted from the logged text.
  • Every update diff also shows changes that come from the update itself rather than from the config: the CDK asset parameters of the nested stacks, ConfigVersion, the config versions in the launch templates, and the renamed head node wait condition.

Example:

2026-10-26 14:58:51,606 - INFO - cluster.py:1290:_log_text() - [update dryrun] Diff of the CloudFormation template of the cluster stack:
--- deployed cluster stack
+++ new cluster stack
@@ -53,16 +53,16 @@
       "Description": "Root directory in S3 bucket where cluster artifacts are stored",
       "Type": "String"
     },
-    "AssetParameters961d4c9692f8e9eb36b076d00df46606f83fe5a8a38f9aaad8b25443cc5a3d40ArtifactHash024DF362": {
-      "Description": "Artifact hash for asset \"961d4c9692f8e9eb36b076d00df46606f83fe5a8a38f9aaad8b25443cc5a3d40\"",
+    "AssetParameters08bf867564a2e3603fd02c5f8ce516d6252afe4ec900a4dbc3b3d935aa6ccc8cArtifactHash6A91CFE0": {
+      "Description": "Artifact hash for asset \"08bf867564a2e3603fd02c5f8ce516d6252afe4ec900a4dbc3b3d935aa6ccc8c\"",
       "Type": "String"
     },
…
@@ -81,7 +81,7 @@
       "Type": "String"
     },
     "ConfigVersion": {
-      "Default": "6hT.TdpWrLKiCNImLw1XxznKKcyPWxWd",
+      "Default": "",
       "Description": "Version of the original config used to generate the stack",
       "Type": "String"
     },
@@ -999,7 +999,7 @@
     },
     "HeadNodeAlarmD6381F07": {
       "DependsOn": [
-        "HeadNodeWaitCondition20261026162146"
+        "HeadNodeWaitCondition20261026185849"
       ],
       "Properties": {
         "AlarmName": "test-installed-HeadNode",
…
@@ -1311,7 +1311,7 @@
-                      "\",\n        \"log_group_name\": … \"cluster_config_version\": \"BXsOWD5dhvaHdrKjteB1ClbIoDVa0Xca\",\n        \"instance_types_data_version\": \"wfQWKizL6DQli1rQiJCxJhLYxGJsYpmX\", …",
+                      "\",\n        \"log_group_name\": … \"cluster_config_version\": \"\",\n        \"instance_types_data_version\": \"\", …",
…
@@ -1573,17 +1573,17 @@
       },
       "Type": "AWS::EC2::SecurityGroupIngress"
     },
-    "HeadNodeWaitCondition20261026162146": {
+    "HeadNodeWaitCondition20261026185849": {
       "Properties": {
         "Count": 1,
         "Handle": {
-          "Ref": "HeadNodeWaitConditionHandle20261026162146"
+          "Ref": "HeadNodeWaitConditionHandle20261026185849"
         },
         "Timeout": "2100"
       },
       "Type": "AWS::CloudFormation::WaitCondition"
     },
-    "HeadNodeWaitConditionHandle20261026162146": {
+    "HeadNodeWaitConditionHandle20261026185849": {
       "Type": "AWS::CloudFormation::WaitConditionHandle"
     },
@@ -2279,8 +2279,8 @@
           "Version": "2012-10-17"
         },
         "ManagedPolicyArns": [
-          "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
-          "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
+          "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
+          "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
         ],
         "Path": "/parallelcluster/test-installed/"
       },
2026-10-26 14:58:51,916 - INFO - cluster.py:1290:_log_text() - [update dryrun] Diff of the CloudFormation template of the nested stack ComputeFleetQueuesNestedStackQueuesNestedStackResource4C142E85:
--- deployed nested stack ComputeFleetQueuesNestedStackQueuesNestedStackResource4C142E85
+++ new nested stack ComputeFleetQueuesNestedStackQueuesNestedStackResource4C142E85
@@ -277,7 +277,7 @@
                         {
                           "Ref": "AWS::StackId"
                         },
-                        "\", \"cluster_s3_bucket\": … \"cluster_config_version\": \"BXsOWD5dhvaHdrKjteB1ClbIoDVa0Xca\", …",
+                        "\", \"cluster_s3_bucket\": … \"cluster_config_version\": \"\", …",
                         {
                           "Ref": "referencetotestinstalledSlurmDynamoDBTableD61B589BRef"
                         },

API diff not very readable unless read with jq -r:

{
    "level": "INFO",
    "location": "cluster.py:1290:_log_text()",
    "message": "[update] Diff of the CloudFormation template of the cluster stack:\n--- deployed cluster stack\n+++ new cluster stack\n@@ -53,16 +53,16 @@\n       \"Description\": \"Root directory in S3 bucket where cluster artifacts are stored\",\n       \"Type\": \"String\"\n     },\n-    \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cArtifactHashA404505E\": {\n-      \"Description\": \"Artifact hash for asset \\\"4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8c\\\"\",\n+    \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0ArtifactHashD38D59DD\": {\n+      \"Description\": \"Artifact hash for asset \\\"56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0\\\"\",\n       \"Type\": \"String\"\n     },\n-    \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cS3BucketCFF473B3\": {\n-      \"Description\": \"S3 bucket for asset \\\"4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8c\\\"\",\n+    \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0S3BucketAE7093D9\": {\n+      \"Description\": \"S3 bucket for asset \\\"56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0\\\"\",\n       \"Type\": \"String\"\n     },\n-    \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cS3VersionKey8E53F9DF\": {\n-      \"Description\": \"S3 key for asset version \\\"4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8c\\\"\",\n+    \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0S3VersionKeyDD49443F\": {\n+      \"Description\": \"S3 key for asset version \\\"56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0\\\"\",\n       \"Type\": \"String\"\n     },\n     \"ClusterCWLogGroup\": {\n@@ -81,7 +81,7 @@\n       \"Type\": \"String\"\n     },\n     \"ConfigVersion\": {\n-      \"Default\": \"rPWN32Wq6np3Rpt6FR9oeGgtPGRf6kfi\",\n+      \"Default\": \"LZmRrVEowmpnwGG4p2r0vL9sUpIcEfMU\",\n       \"Description\": \"Version of the original config used to generate the stack\",\n       \"Type\": \"String\"\n     },\n@@ -784,7 +784,7 @@\n               },\n               \"/\",\n               {\n-                \"Ref\": \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cS3BucketCFF473B3\"\n+                \"Ref\": \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0S3BucketAE7093D9\"\n               },\n               \"/\",\n               {\n@@ -794,7 +794,7 @@\n                     \"Fn::Split\": [\n                       \"||\",\n                       {\n-                        \"Ref\": \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cS3VersionKey8E53F9DF\"\n+                        \"Ref\": \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0S3VersionKeyDD49443F\"\n                       }\n                     ]\n                   }\n@@ -807,7 +807,7 @@\n                     \"Fn::Split\": [\n                       \"||\",\n                       {\n-                        \"Ref\": \"AssetParameters4e6493321be7d0b9fef0d6ea1a35c078c836dbb91d847d289a5dea11c2ec9c8cS3VersionKey8E53F9DF\"\n+                        \"Ref\": \"AssetParameters56600058efff495f21faeb2328ac1bff44ed1f1e128a63ef14225e38ed8d97b0S3VersionKeyDD49443F\"\n                       }\n                     ]\n                   }\n@@ -999,7 +999,7 @@\n     },\n     \"HeadNodeAlarmD6381F07\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode\",\n@@ -1051,7 +1051,7 @@\n     },\n     \"HeadNodeClustermgtdHeartbeatAlarm333CCAD7\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode-ClustermgtdHeartbeat\",\n@@ -1081,7 +1081,7 @@\n     },\n     \"HeadNodeCpuAlarm5DF0A86F\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode-Cpu\",\n@@ -1107,7 +1107,7 @@\n     },\n     \"HeadNodeDiskAlarm3749DE06\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode-Disk\",\n@@ -1150,7 +1150,7 @@\n     },\n     \"HeadNodeHealthAlarmB0807419\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode-Health\",\n@@ -1217,7 +1217,7 @@\n                       },\n                       \" --url https://cloudformation.us-east-1.amazonaws.com '\",\n                       {\n-                        \"Ref\": \"HeadNodeWaitConditionHandle20261027015403\"\n+                        \"Ref\": \"HeadNodeWaitConditionHandle20261027020653\"\n                       },\n                       \"' || $CFN_BOOTSTRAP_VIRTUALENV_PATH/cfn-signal --exit-code=1 --reason='Update failed' --region \",\n                       {\n@@ -1225,7 +1225,7 @@\n                       },\n                       \" --url https://cloudformation.us-east-1.amazonaws.com '\",\n                       {\n-                        \"Ref\": \"HeadNodeWaitConditionHandle20261027015403\"\n+                        \"Ref\": \"HeadNodeWaitConditionHandle20261027020653\"\n                       },\n                       \"' || exit 0\"\n                     ]\n@@ -1332,7 +1332,7 @@\n                       {\n                         \"Ref\": \"DynamoDBTable\"\n                       },\n-                      \"\\\",\\n        \\\"log_group_name\\\": \\\"/aws/parallelcluster/integ-tests-igymgs216tziucvr-hgreebe2-202610270154\\\",\\n        \\\"dcv_enabled\\\": \\\"false\\\",\\n        \\\"dcv_port\\\": \\\"NONE\\\",\\n        \\\"enable_intel_hpc_platform\\\": \\\"false\\\",\\n        \\\"cw_logging_enabled\\\": \\\"true\\\",\\n        \\\"log_rotation_enabled\\\": \\\"true\\\",\\n        \\\"cluster_s3_bucket\\\": \\\"parallelcluster-4d7788a658687eb6-v1-do-not-delete\\\",\\n        \\\"cluster_config_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/cluster-config-with-implied-values.yaml\\\",\\n        \\\"cluster_config_version\\\": \\\"4tKQjIJQXog9D73DtIKTsCYZMPsq8GqY\\\",\\n        \\\"instance_types_data_version\\\": \\\"xzKd4J9_8FWGVv94EmbZvO4rPO_1.a4g\\\",\\n        \\\"change_set_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/change-set.json\\\",\\n        \\\"instance_types_data_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/instance-types-data.json\\\",\\n        \\\"custom_node_package\\\": \\\"\\\",\\n        \\\"head_node_imds_secured\\\": \\\"true\\\",\\n        \\\"compute_node_bootstrap_timeout\\\": 2100,\\n        \\\"disable_sudo_access_for_default_user\\\": \\\"false\\\",\\n        \\\"launch_template_id\\\": \\\"HeadNodeLaunchTemplate\\\",\\n        \\\"dns_domain\\\": \\\"\",\n+                      \"\\\",\\n        \\\"log_group_name\\\": \\\"/aws/parallelcluster/integ-tests-igymgs216tziucvr-hgreebe2-202610270154\\\",\\n        \\\"dcv_enabled\\\": \\\"false\\\",\\n        \\\"dcv_port\\\": \\\"NONE\\\",\\n        \\\"enable_intel_hpc_platform\\\": \\\"false\\\",\\n        \\\"cw_logging_enabled\\\": \\\"true\\\",\\n        \\\"log_rotation_enabled\\\": \\\"true\\\",\\n        \\\"cluster_s3_bucket\\\": \\\"parallelcluster-4d7788a658687eb6-v1-do-not-delete\\\",\\n        \\\"cluster_config_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/cluster-config-with-implied-values.yaml\\\",\\n        \\\"cluster_config_version\\\": \\\"nj99UsVKti2qwqTEQcteqpCC66rXVPSz\\\",\\n        \\\"instance_types_data_version\\\": \\\"UOyd.mPy5hwcIEChSuDNQdb15SobNOOe\\\",\\n        \\\"change_set_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/change-set.json\\\",\\n        \\\"instance_types_data_s3_key\\\": \\\"parallelcluster/3.17.0/clusters/integ-tests-igymgs216tziucvr-hgreebe2-t0z2u77wzhkmqcco/configs/instance-types-data.json\\\",\\n        \\\"custom_node_package\\\": \\\"\\\",\\n        \\\"head_node_imds_secured\\\": \\\"true\\\",\\n        \\\"compute_node_bootstrap_timeout\\\": 2100,\\n        \\\"disable_sudo_access_for_default_user\\\": \\\"false\\\",\\n        \\\"launch_template_id\\\": \\\"HeadNodeLaunchTemplate\\\",\\n        \\\"dns_domain\\\": \\\"\",\n                       {\n                         \"Ref\": \"ClusterDNSDomain\"\n                       },\n@@ -1361,7 +1361,7 @@\n               },\n               \"/opt/parallelcluster/tmp/wait_condition_handle.txt\": {\n                 \"content\": {\n-                  \"Ref\": \"HeadNodeWaitConditionHandle20261027015403\"\n+                  \"Ref\": \"HeadNodeWaitConditionHandle20261027020653\"\n                 },\n                 \"group\": \"root\",\n                 \"mode\": \"000600\",\n@@ -1542,7 +1542,7 @@\n     },\n     \"HeadNodeMemAlarm7B308961\": {\n       \"DependsOn\": [\n-        \"HeadNodeWaitCondition20261027015403\"\n+        \"HeadNodeWaitCondition20261027020653\"\n       ],\n       \"Properties\": {\n         \"AlarmName\": \"integ-tests-igymgs216tziucvr-hgreebe2-HeadNode-Mem\",\n@@ -1595,17 +1595,17 @@\n       },\n       \"Type\": \"AWS::EC2::SecurityGroupIngress\"\n     },\n-    \"HeadNodeWaitCondition20261027015403\": {\n+    \"HeadNodeWaitCondition20261027020653\": {\n       \"Properties\": {\n         \"Count\": 1,\n         \"Handle\": {\n-          \"Ref\": \"HeadNodeWaitConditionHandle20261027015403\"\n+          \"Ref\": \"HeadNodeWaitConditionHandle20261027020653\"\n         },\n         \"Timeout\": \"2100\"\n       },\n       \"Type\": \"AWS::CloudFormation::WaitCondition\"\n     },\n-    \"HeadNodeWaitConditionHandle20261027015403\": {\n+    \"HeadNodeWaitConditionHandle20261027020653\": {\n       \"Type\": \"AWS::CloudFormation::WaitConditionHandle\"\n     },\n     \"IamPolicyErrorsFilter\": {",
    "timestamp": "2026-10-27 02:06:55,603+0000",
    "service": "pcluster",
    "cold_start": false,
    "function_name": "integ-tests-api-6wnce5fls1-ParallelClusterFunction-3HIQgnS7m3AT",
    "function_memory_size": "2048",
    "function_arn": "arn:aws:lambda:us-east-1:447714826191:function:integ-tests-api-6wnce5fls1-ParallelClusterFunction-3HIQgnS7m3AT",
    "function_request_id": "849ef3d9-e9e2-446f-b4e5-ee5d613e0c22",
    "xray_trace_id": "1-6ae00734-07828017068178eb0eefdb79"
}

Tests

  • Checked CLI log when running update with and without dryrun
  • Checked API lambda log when running update with API

Please review the guidelines for contributing and Pull Request Instructions.

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

Helena Greebe added 5 commits October 26, 2026 12:21
…r, and their diff, on update and update dryrun

On every update-cluster, and on every update-cluster dryrun, log at INFO, for the cluster stack and each of its
nested stacks: the deployed template, the new template and the unified diff between them. The messages start with
[update] or [update dryrun].

* The dryrun builds the templates as the update does, but uploads nothing to the cluster bucket.
* The templates are logged as compact JSON, in parts of at most 150000 characters, so that each message stays under
  the 256 KB limit of a CloudWatch Logs event when logged by the API Lambda.
* The query strings of presigned S3 URLs are redacted.
* A failure to read a deployed template is logged as a warning: the update goes on.
… update dryrun

Log, for each stack, only the diff between the deployed and new templates. The new template is logged instead when
there is no deployed template to compare with: a new nested stack, or a deployed template that cannot be read.
…best-effort step

Keep the update dryrun as it was, validating the request, and only add a call that logs the diff between the deployed
templates and the ones generated from the target config. The step uploads nothing, leaves the cluster unchanged and
logs any failure as a warning, so that it does not change the outcome of the dryrun.
…on templates diff

Log the templates diff of an update as a best-effort step, as for the update dryrun: any failure is logged as a
warning and the update goes on.
…nes cannot be read

When a deployed template cannot be read, only log a warning instead of the new templates: a policy that denies
GetTemplate would otherwise make every update log every new template in full. The new template of a new nested stack
is still logged. Make the size of the logged parts private, and add the changelog entry.
@hgreebe hgreebe changed the title Print cloudfromation template diff on every update Print cloudformation template diff on every update Oct 27, 2026
@hgreebe
hgreebe marked this pull request as ready for review October 27, 2026 15:41
@hgreebe
hgreebe requested review from a team as code owners October 27, 2026 15:41
Comment thread cli/src/pcluster/models/cluster.py Outdated
Comment on lines +954 to +966
try:
self._add_tags(target_config)
template_body, assets_metadata = CDKTemplateBuilder().build_cluster_template(
cluster_config=target_config,
bucket=self.bucket,
stack_name=self.stack_name,
log_group_name=self.stack.log_group_name,
upload_assets=False,
)
except Exception as e:
LOGGER.warning("Unable to log the CloudFormation templates of the %s: %s", operation, e)
return
self._log_templates_best_effort(template_body, assets_metadata, operation)

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.

How about making the code simpler and by removing _log_templates_best_effort and use _log_templates directly?:

        try:
            self._add_tags(target_config)
            template_body, assets_metadata = CDKTemplateBuilder().build_cluster_template(
                cluster_config=target_config,
                bucket=self.bucket,
                stack_name=self.stack_name,
                log_group_name=self.stack.log_group_name,
                upload_assets=False,
            )
            self._log_templates(template_body, assets_metadata, operation)
        except Exception as e:
            LOGGER.warning("Unable to log the CloudFormation templates of the %s: %s", operation, e)
            return

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.

Done!

Comment thread cli/src/pcluster/models/cluster.py Outdated
def _stack_resources(self) -> dict:
"""Return the resources of the cluster stack by logical ID, or none, with a warning, if they cannot be read."""
try:
return AWSApi.instance().cfn.describe_stack_resources(self.stack_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.

Only the first 100 resources will be returned. If your stack has more resources than this, you should use ListStackResources instead.

https://docs.aws.amazon.com/boto3/latest/reference/services/cloudformation/client/describe_stack_resources.html#

Do we always have fewer than 100 resources?

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 only need the physical ID of each nested stack, so I now look each one up with DescribeStackResources(StackName, LogicalResourceId): the response holds just that resource, so the 100 limit can't drop it. I didn't use ListStackResources because it would need cloudformation:ListStackResources, which isn't in the ParallelCluster policies; this keeps the same action. The cost is one call per deployed nested stack.

Comment thread cli/src/pcluster/models/cluster.py Outdated


def _parse_template(template_body):
"""Return the template as a dict: GetTemplate returns a JSON template already parsed, and a YAML one as text."""

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.

Do we really touch the YAML text path?

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.

added note: The template of the cluster stack is uploaded as YAML (see S3Bucket.upload_cfn_template), so it is text; the
templates of the nested stacks are uploaded as JSON, so they are already parsed.

Comment thread cli/src/pcluster/models/cluster.py Outdated

# Maximum characters of a template logged in one message. Templates are logged in parts so that each message, once
# escaped in a JSON log event (e.g. by the API Lambda logger), stays under the 256 KB limit of a CloudWatch Logs event.
_TEMPLATE_LOG_PART_SIZE = 150_000

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.

Let's shrink this by 10% to allow some space for escape characters

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.

Done!

deployed_resources = deployed_template.get("Resources") or {}
stack_resources = None
for logical_id, resource in (template_body.get("Resources") or {}).items():
if resource.get("Type") != _NESTED_STACK_RESOURCE_TYPE:

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.

[INFO] the code only handles one level of nesting. That's fine for the ParallelCluster today,

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.

Added a note in the doctring

@hgreebe
hgreebe enabled auto-merge (squash) October 30, 2026 13:05
@hgreebe
hgreebe merged commit 4378b19 into aws:develop Oct 30, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants