Skip to content

Conversation

@caverac
Copy link

@caverac caverac commented Dec 27, 2025

Issue

Closes #36401.

Reason for this change

When trying to create a custom metric with an account property from a different AWS account for Application Auto Scaling target tracking policies, the account information is silently ignored. This leads to unexpected behavior where the policy uses metrics from the current account instead of the specified cross-account metric.

Description of changes

Simply adding account: metric.account to the return object will not fix the problem. Here's why:

1. CloudFormation schema does not support Account for CustomizedMetricSpecification

The AWS CloudFormation documentation for CustomizedMetricSpecification only supports these properties:

  • Dimensions
  • MetricName
  • Metrics
  • Namespace
  • Statistic
  • Unit

There is no Account or AccountId property listed.

2. CloudFormation schema does not support AccountId for TargetTrackingMetricDataQuery

The TargetTrackingMetricDataQuery (used by the Metrics array) only supports:

  • Expression
  • Id
  • Label
  • MetricStat
  • ReturnData

Again, no AccountId property - unlike CloudWatch Alarms' MetricDataQuery which does have AccountId.

3. CDK's generated CloudFormation converter strips unknown properties

Even if we add account to the TypeScript object, the CDK's generated conversion function will strip it out.

In packages/aws-cdk-lib/aws-applicationautoscaling/lib/applicationautoscaling.generated.ts (lines 2097-2108):

function convertCfnScalingPolicyCustomizedMetricSpecificationPropertyToCloudFormation(properties: any): any {
  if (!cdk.canInspect(properties)) return properties;
  CfnScalingPolicyCustomizedMetricSpecificationPropertyValidator(properties).assertSuccess();
  return {
    Dimensions: cdk.listMapper(convertCfnScalingPolicyMetricDimensionPropertyToCloudFormation)(properties.dimensions),
    MetricName: cdk.stringToCloudFormation(properties.metricName),
    Metrics: cdk.listMapper(convertCfnScalingPolicyTargetTrackingMetricDataQueryPropertyToCloudFormation)(properties.metrics),
    Namespace: cdk.stringToCloudFormation(properties.namespace),
    Statistic: cdk.stringToCloudFormation(properties.statistic),
    Unit: cdk.stringToCloudFormation(properties.unit)
  };
}

There is no Account property in the return object. Any account property we add gets silently dropped during CloudFormation synthesis.

Solution

Rather than silently ignoring the account property (which was the original behavior), this change adds a validation that throws a clear error when a user attempts to use a cross-account metric. This provides immediate feedback during synthesis rather than having the deployment fail or behave unexpectedly.

Code changes:

  • Added validation in renderCustomMetric() at packages/aws-cdk-lib/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts to detect when a metric's account differs from the stack's account
  • Throws a ValidationError with a clear message explaining that cross-account metrics are not supported for Application Auto Scaling target tracking policies

Alternatives considered:

  1. Pass through the account property: This was not possible because:
    • The CloudFormation schema doesn't have an Account field (docs)
    • The CDK's generated converter (convertCfnScalingPolicyCustomizedMetricSpecificationPropertyToCloudFormation) strips unknown properties
  2. Use the Metrics array format: Investigated using the newer Metrics property format, but TargetTrackingMetricDataQuery also doesn't support AccountId (docs)

Describe any new or updated permissions being added

None.

Description of how you validated changes

Added two unit tests:

  1. throws error when using cross-account custom metric - Verifies that using a metric with a different account throws the expected error
  2. allows custom metric from same account - Verifies that metrics from the same account continue to work correctly

All existing tests continue to pass:

PASS aws-applicationautoscaling/test/target-tracking.test.ts
  target tracking
    ✓ test setup target tracking on predefined metric
    ✓ test setup target tracking on predefined metric for lambda
    ✓ test setup target tracking on predefined metric for DYNAMODB_WRITE_CAPACITY_UTILIZATION
    ✓ test setup target tracking on predefined metric for SAGEMAKER_VARIANT_PROVISIONED_CONCURRENCY_UTILIZATION
    ✓ test setup target tracking on predefined metric for SAGEMAKER_VARIANT_CONCURRENT_REQUESTS_PER_MODEL_HIGH_RESOLUTION
    ✓ test setup target tracking on predefined metric for SAGEMAKER_INFERENCE_COMPONENT_CONCURRENT_REQUESTS_PER_COPY_HIGH_RESOLUTION
    ✓ test setup target tracking on predefined metric for WORKSPACES_AVERAGE_USER_SESSIONS_CAPACITY_UTILIZATION
    ✓ test setup target tracking on custom metric
    ✓ throws error when using cross-account custom metric
    ✓ allows custom metric from same account

Test Suites: 1 passed, 1 total
Tests:       10 passed, 10 total

Checklist


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

… in target tracking policy.

The `renderCustomMetric` function was silently ignoring the `account` property from custom metrics. This change adds validation to detect when a cross-account metric is used and throws a clear error, since Application Auto Scaling's CloudFormation schema does not currently support cross-account metrics (unlike CloudWatch Alarms which have `accountId` in MetricDataQuery).

This provides immediate feedback during synthesis rather than having the metric silently default to the current account.

Closes aws#36401
@aws-cdk-automation aws-cdk-automation requested a review from a team December 27, 2025 16:43
@github-actions github-actions bot added effort/small Small work item – less than a day of effort feature-request A feature should be added or improved. p2 beginning-contributor [Pilot] contributed between 0-2 PRs to the CDK labels Dec 27, 2025
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

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

The pull request linter fails with the following errors:

❌ Fixes must contain a change to an integration test file and the resulting snapshot.

If you believe this pull request should receive an exemption, please comment and provide a justification. A comment requesting an exemption should contain the text Exemption Request. Additionally, if clarification is needed, add Clarification Request to a comment.

✅ A exemption request has been requested. Please wait for a maintainer's review.

@caverac
Copy link
Author

caverac commented Dec 29, 2025

Putting together this diagram helped me a lot tracking accountId all the way to the CloudFormation API

flowchart TB
    subgraph UserCode["User Code"]
        A["new cloudwatch.Metric({
            namespace: 'Test',
            metricName: 'Metric',
            account: '222222222222'
        })"]
    end

    subgraph ScalableTarget["ScalableTarget"]
        B["scaleToTrackMetric(id, props)"]
    end

    subgraph TargetTrackingScalingPolicy["TargetTrackingScalingPolicy"]
        C["constructor(scope, id, props)"]
        D["renderCustomMetric(scope, props.customMetric)"]
    end

    subgraph MetricProcessing["Metric Processing"]
        E["metric.toMetricConfig()"]
        F["Validation: Compare c.account vs Stack.of(scope).account"]
    end

    subgraph ValidationOutcome["Validation Outcome"]
        G["ValidationError:
        Cross-account metrics are not supported"]
        H["Return CustomizedMetricSpecificationProperty
        {dimensions, metricName, namespace, statistic, unit}"]
    end

    subgraph CfnLayer["CloudFormation Layer"]
        I["new CfnScalingPolicy()"]
        J["CDK Synthesizer"]
    end

    subgraph CloudFormation["CloudFormation API"]
        K["AWS::ApplicationAutoScaling::ScalingPolicy
        CustomizedMetricSpecification:
          - Dimensions
          - MetricName
          - Namespace
          - Statistic
          - Unit
          (NO Account field)"]
    end

    A -->|"IMetric"| B
    B -->|"BasicTargetTrackingScalingPolicyProps
    {customMetric: IMetric, targetValue: number, ...}"| C
    C -->|"IMetric"| D
    D -->|"IMetric.toMetricConfig()"| E
    E -->|"MetricConfig.metricStat: MetricStatConfig
    {namespace, metricName, statistic, period,
     dimensions?, unitFilter?, account?, region?}"| F

    F -->|"account !== stackAccount"| G
    F -->|"account === stackAccount OR account undefined"| H

    H -->|"CfnScalingPolicy.CustomizedMetricSpecificationProperty
    {dimensions?, metricName, namespace, statistic, unit?}"| I
    I -->|"CfnScalingPolicy resource properties"| J
    J -->|"CloudFormation JSON Template"| K
Loading

@caverac
Copy link
Author

caverac commented Dec 29, 2025

Exemption Request: This change will throw at synthesis time. Because of that, no template will be generated.

That being said, I can add an integration test for the happy path, where no account id is provided and synthesis completes as expected. I need a bit of feedback here, not sure what to do

@aws-cdk-automation aws-cdk-automation added pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. labels Dec 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

beginning-contributor [Pilot] contributed between 0-2 PRs to the CDK effort/small Small work item – less than a day of effort feature-request A feature should be added or improved. p2 pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aws-applicationautoscaling: Target Tracking Scaling Policy does not support account for custom metric

2 participants