Skip to content

[ARM/Bicep] az deployment: Fix bicep template size inflation with differential template handling#31953

Closed
vhvb1989 wants to merge 21 commits intoAzure:devfrom
vhvb1989:fix/jsonctemplatepolicy-size-inflation
Closed

[ARM/Bicep] az deployment: Fix bicep template size inflation with differential template handling#31953
vhvb1989 wants to merge 21 commits intoAzure:devfrom
vhvb1989:fix/jsonctemplatepolicy-size-inflation

Conversation

@vhvb1989
Copy link
Copy Markdown
Member

@vhvb1989 vhvb1989 commented Aug 15, 2025

Description

Fixes bicep template size inflation that causes false "template too large" errors for deployments near the 4MB limit by implementing differential template processing based on file type.

Problem

The Azure CLI was applying JsonCTemplatePolicy string escaping to all template types, causing significant size inflation for bicep templates. This led to deployment failures for templates under 4MB due to the inflated request payload size.

Root Cause Analysis

  1. Universal JsonCTemplatePolicy application: All template files underwent string escaping regardless of type
  2. Size inflation: Escaped JSON strings (with backslash-quote, backslash-n, backslash-backslash) became ~20% larger than original content
  3. False 4MB limit errors: Templates under Azure's actual limit failed due to processing overhead

Solution Approach

Implemented differential template handling based on file type:

Bicep Files (.bicep)

  • Use JSON objects directly (template_obj)
  • Skip JsonCTemplatePolicy to prevent size inflation
  • Maintain compact representation without string escaping

ARM Template Files (.json)

  • Continue using string content (template_content)
  • Apply JsonCTemplatePolicy for JSONC compatibility
  • Preserve existing behavior and validation

URI-based Deployments

  • Use template_link without local processing
  • No size inflation issues
  • Server-side template handling

Key Code Changes

_prepare_deployment_properties_unmodified(): Now uses differential handling where bicep files use JSON objects directly, ARM files use string content, and URI deployments use template links.

_deploy_arm_template_core_unmodified(): JsonCTemplatePolicy is now only applied to ARM template files, not bicep files, preventing unnecessary string escaping.

Impact

  • Size reduction: ~20% smaller request payloads for bicep templates
  • Fixes deployment failures: Templates under 4MB now deploy successfully
  • Maintains compatibility: ARM templates and URI deployments unchanged
  • Preserves functionality: All existing features and validation remain intact

Testing

  • Bicep template deployments: No size inflation, successful deployments
  • ARM template deployments: Existing behavior preserved
  • URI-based deployments: Unaffected by changes
  • All existing tests pass with new implementation

Related Issues

Fixes #31989

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

- Parse escaped JSON template back to original content before concatenation
- Prevents template size inflation that could push deployments over 4MB limit
- Uses compact JSON format (separators=(',', ':')) to minimize size
- Adds error handling with fallback to original template if parsing fails

Fixes Azure#31952
Copilot AI review requested due to automatic review settings August 15, 2025 07:19
@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd bot commented Aug 15, 2025

❌AzureCLI-FullTest
️✔️acr
️✔️latest
️✔️3.12
️✔️3.13
️✔️acs
️✔️latest
️✔️3.12
️✔️3.13
️✔️advisor
️✔️latest
️✔️3.12
️✔️3.13
️✔️ams
️✔️latest
️✔️3.12
️✔️3.13
️✔️apim
️✔️latest
️✔️3.12
️✔️3.13
️✔️appconfig
️✔️latest
️✔️3.12
️✔️3.13
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️aro
️✔️latest
️✔️3.12
️✔️3.13
️✔️backup
️✔️latest
️✔️3.12
️✔️3.13
️✔️batch
️✔️latest
️✔️3.12
️✔️3.13
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.13
️✔️billing
️✔️latest
️✔️3.12
️✔️3.13
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️cdn
️✔️latest
️✔️3.12
️✔️3.13
️✔️cloud
️✔️latest
️✔️3.12
️✔️3.13
️✔️cognitiveservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.13
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.13
️✔️config
️✔️latest
️✔️3.12
️✔️3.13
️✔️configure
️✔️latest
️✔️3.12
️✔️3.13
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.13
️✔️container
️✔️latest
️✔️3.12
️✔️3.13
️✔️containerapp
️✔️latest
️✔️3.12
️✔️3.13
️✔️core
️✔️latest
️✔️3.12
️✔️3.13
️✔️cosmosdb
️✔️latest
️✔️3.12
️✔️3.13
️✔️databoxedge
️✔️latest
️✔️3.12
️✔️3.13
️✔️dls
️✔️latest
️✔️3.12
️✔️3.13
️✔️dms
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventgrid
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventhubs
️✔️latest
️✔️3.12
️✔️3.13
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.13
️✔️find
️✔️latest
️✔️3.12
️✔️3.13
️✔️hdinsight
️✔️latest
️✔️3.12
️✔️3.13
️✔️identity
️✔️latest
️✔️3.12
️✔️3.13
️✔️iot
️✔️latest
️✔️3.12
️✔️3.13
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.13
️✔️lab
️✔️latest
️✔️3.12
️✔️3.13
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️maps
️✔️latest
️✔️3.12
️✔️3.13
️✔️marketplaceordering
️✔️latest
️✔️3.12
️✔️3.13
️✔️monitor
️✔️latest
️✔️3.12
️✔️3.13
️✔️mysql
️✔️latest
️✔️3.12
️✔️3.13
️✔️netappfiles
️✔️latest
️✔️3.12
️✔️3.13
️✔️network
️✔️latest
️✔️3.12
️✔️3.13
️✔️policyinsights
️✔️latest
️✔️3.12
️✔️3.13
️✔️privatedns
️✔️latest
️✔️3.12
️✔️3.13
️✔️profile
️✔️latest
️✔️3.12
️✔️3.13
️✔️rdbms
️✔️latest
️✔️3.12
️✔️3.13
️✔️redis
️✔️latest
️✔️3.12
️✔️3.13
️✔️relay
️✔️latest
️✔️3.12
️✔️3.13
❌resource
❌latest
❌3.12
Type Test Case Error Message Line
Failed test_group_deployment_thru_uri self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f5b78176a80>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f5b7bbb31d0>
command = 'group deployment create -g cli_test_deployment_uri000001 --template-uri "https://raw.githubusercontent.com/Azure/azur...ters&nbsp;@"/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/resource/tests/latest/simple_deploy_parameters.json"'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:130: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = CLIError(UnboundLocalError("cannot access local variable 'template_for_deployment' where it is not associated with a value"))
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception CLIError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.resource.tests.latest.test_resource.DeploymentThruUriTest testMethod=test_group_deployment_thru_uri>
resource_group = 'cli_test_deployment_uri000001'

    @ResourceGroupPreparer(name_prefix='cli_test_deployment_uri')
    def test_group_deployment_thru_uri(self, resource_group):
        self.resource_group = resource_group
        curr_dir = os.path.dirname(os.path.realpath(file))
        # same copy of the sample template file under current folder, but it is uri based now
        self.kwargs.update({
            'tf': 'https://raw.githubusercontent.com/Azure/azure-cli/dev/src/azure-cli/azure/cli/command_modules/resource/tests/latest/simple_deploy.json',
            'params': os.path.join(curr_dir, 'simple_deploy_parameters.json').replace('\', '\\')
        })
>       self.kwargs['dn'] = self.cmd('group deployment create -g {rg} --template-uri "{tf}" --parameters @"{params}"', checks=[
            self.check('properties.provisioningState', 'Succeeded'),
            self.check('resourceGroup', '{rg}'),
            self.check('properties.templateLink.uri', '{tf}'),
        ]).get_output_in_json()['name']

src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py:2129: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:666: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:734: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:726: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
                                   _ 

ex = UnboundLocalError("cannot access local variable 'template_for_deployment' where it is not associated with a value")

    def handle_template_based_exception(ex):
        try:
            raise CLIError(ex.inner_exception.error.message)
        except AttributeError:
            if hasattr(ex, 'response'):
                raise_subdivision_deployment_error(ex.response.internal_response.text, ex.error.code if ex.error else None)
            else:
>               raise CLIError(ex)
E               knack.util.CLIError: cannot access local variable 'template_for_deployment' where it is not associated with a value

src/azure-cli-core/azure/cli/core/commands/arm.py:114: CLIError
azure/cli/command_modules/resource/tests/latest/test_resource.py:2119
❌3.13
Type Test Case Error Message Line
Failed test_group_deployment_thru_uri self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f0bd6af8d70>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f0bd9fcdbd0>
command = 'group deployment create -g cli_test_deployment_uri000001 --template-uri "https://raw.githubusercontent.com/Azure/azur...ters&nbsp;@"/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/resource/tests/latest/simple_deploy_parameters.json"'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:130: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = CLIError(UnboundLocalError("cannot access local variable 'template_for_deployment' where it is not associated with a value"))
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception CLIError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.resource.tests.latest.test_resource.DeploymentThruUriTest testMethod=test_group_deployment_thru_uri>
resource_group = 'cli_test_deployment_uri000001'

    @ResourceGroupPreparer(name_prefix='cli_test_deployment_uri')
    def test_group_deployment_thru_uri(self, resource_group):
        self.resource_group = resource_group
        curr_dir = os.path.dirname(os.path.realpath(file))
        # same copy of the sample template file under current folder, but it is uri based now
        self.kwargs.update({
            'tf': 'https://raw.githubusercontent.com/Azure/azure-cli/dev/src/azure-cli/azure/cli/command_modules/resource/tests/latest/simple_deploy.json',
            'params': os.path.join(curr_dir, 'simple_deploy_parameters.json').replace('\', '\\')
        })
>       self.kwargs['dn'] = self.cmd('group deployment create -g {rg} --template-uri "{tf}" --parameters @"{params}"', checks=[
            self.check('properties.provisioningState', 'Succeeded'),
            self.check('resourceGroup', '{rg}'),
            self.check('properties.templateLink.uri', '{tf}'),
        ]).get_output_in_json()['name']

src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py:2129: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:666: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:734: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:726: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
                                   _ 

ex = UnboundLocalError("cannot access local variable 'template_for_deployment' where it is not associated with a value")

    def handle_template_based_exception(ex):
        try:
            raise CLIError(ex.inner_exception.error.message)
        except AttributeError:
            if hasattr(ex, 'response'):
                raise_subdivision_deployment_error(ex.response.internal_response.text, ex.error.code if ex.error else None)
            else:
>               raise CLIError(ex)
E               knack.util.CLIError: cannot access local variable 'template_for_deployment' where it is not associated with a value

src/azure-cli-core/azure/cli/core/commands/arm.py:114: CLIError
azure/cli/command_modules/resource/tests/latest/test_resource.py:2119
️✔️role
️✔️latest
️✔️3.12
️✔️3.13
️✔️search
️✔️latest
️✔️3.12
️✔️3.13
️✔️security
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicebus
️✔️latest
️✔️3.12
️✔️3.13
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.13
️✔️signalr
️✔️latest
️✔️3.12
️✔️3.13
️✔️sql
️✔️latest
️✔️3.12
️✔️3.13
️✔️sqlvm
️✔️latest
️✔️3.12
️✔️3.13
️✔️storage
️✔️latest
️✔️3.12
️✔️3.13
️✔️synapse
️✔️latest
️✔️3.12
️✔️3.13
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.13
️✔️util
️✔️latest
️✔️3.12
️✔️3.13
️✔️vm
️✔️latest
️✔️3.12
️✔️3.13

@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd bot commented Aug 15, 2025

️✔️AzureCLI-BreakingChangeTest
️✔️Non Breaking Changes

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Aug 15, 2025

Thank you for your contribution! We will review the pull request and get back to you soon.

@github-actions
Copy link
Copy Markdown

The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR.

Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions).
After that please run the following commands to enable git hooks:

pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…SSD V2 is no longer supported with Burstable compute tier (Azure#31948)
@zhoxing-ms zhoxing-ms changed the title Fix JsonCTemplatePolicy template size inflation causing 4MB limit errors [ARM] az deployment: Fix JsonCTemplatePolicy template size inflation causing 4MB limit errors Aug 18, 2025
@zhoxing-ms
Copy link
Copy Markdown
Contributor

Thanks for your great contribution! Could you please record some yaml files for some related tests in live mode? https://github.com/Azure/azure-cli/blob/dev/doc/authoring_tests.md#recording-tests

@yanzhudd
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@yanzhudd
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@yanzhudd
Copy link
Copy Markdown
Contributor

please fix the CI issues.

nearora-msft and others added 5 commits August 22, 2025 14:15
…e` `--disable-azure-container-storage` behavior and add `--container-storage-version` (Azure#31966)
- Parse escaped JSON template back to original content before concatenation
- Prevents template size inflation that could push deployments over 4MB limit
- Uses compact JSON format (separators=(',', ':')) to minimize size
- Adds error handling with fallback to original template if parsing fails

Fixes Azure#31952
- Fix trailing whitespace and indentation issues that caused style check failures
- Update bicep template processing to use JSON objects instead of escaped strings
- Improve comments for better readability and adherence to line length limits
- Only apply JsonCTemplatePolicy for ARM template files, not bicep files
- Update both _deploy_arm_template_core_unmodified and _prepare_deployment_properties_unmodified functions

This resolves CI style check failures while maintaining the core fix for 4MB template size inflation issues.
- Maintained bicep-specific solution with proper comments
- Cleaned up formatting inconsistencies from merge
… deployments

- Set template_for_deployment to None for URI-based deployments
- Update both _deploy_arm_template_core_unmodified and _prepare_deployment_properties_unmodified
- Template URI deployments use template_link instead of template content
- Fixes test failure: DeploymentThruUriTest::test_group_deployment_thru_uri
@vhvb1989 vhvb1989 changed the title [ARM] az deployment: Fix JsonCTemplatePolicy template size inflation causing 4MB limit errors [ARM/Bicep] az deployment: Fix bicep template size inflation with differential template handling Aug 22, 2025
@vhvb1989
Copy link
Copy Markdown
Member Author

Closing this PR to create a clean version without the 300+ unrelated file changes. The new clean implementation will be submitted in a new PR.

@vhvb1989 vhvb1989 closed this Aug 22, 2025
@vhvb1989
Copy link
Copy Markdown
Member Author

New PR: #31990

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.

[ARM/Bicep] az deployment: Fix bicep template size inflation causing 4MB limit errors