Skip to content

[Core] az rest: default JSON Content-Type for PUT/POST/PATCH body requests#33455

Draft
Copilot wants to merge 2 commits into
devfrom
copilot/fix-unsupported-media-type-error
Draft

[Core] az rest: default JSON Content-Type for PUT/POST/PATCH body requests#33455
Copilot wants to merge 2 commits into
devfrom
copilot/fix-unsupported-media-type-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 28, 2026

Related command
az rest --uri /subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Web/sites/$appname/config/authsettings?api-version=2020-09-01 --method put --body auth4.json

Description
az rest could send body payloads for PUT/POST/PATCH without Content-Type, which causes ARM/RP endpoints to return HTTP 415 (UnsupportedMediaType) when they require JSON.
This change makes header behavior deterministic for body-carrying methods while preserving explicit user headers.

  • Behavior change

    • In send_raw_request, when body is provided and method is PUT/POST/PATCH, set Content-Type: application/json if not already specified.
    • Keep existing behavior when caller passes --headers Content-Type=....
  • Regression coverage

    • Added focused unit test for string body input (e.g., auth4.json) to verify:
      • default JSON content type is injected for PUT/POST/PATCH
      • explicit content type remains unchanged.
  • Illustrative snippet

    if body:
        try:
            body_object = shell_safe_json_parse(body)
            body = json.dumps(body_object)
        except Exception:
            pass
        if 'Content-Type' not in headers and method and method.upper() in ['PUT', 'POST', 'PATCH']:
            headers['Content-Type'] = 'application/json'

Testing Guide

  • az rest --method put --uri <arm-url> --body auth4.json
    • Expected: request carries Content-Type: application/json (unless overridden via --headers).

History Notes
[Core] az rest: Default Content-Type: application/json for PUT/POST/PATCH requests with body when not explicitly provided.


This checklist is used to make sure that common guidelines for a pull request are followed.

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

azure-client-tools-bot-prd Bot commented May 28, 2026

❌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
Type Test Case Error Message Line
Failed test_send_raw_requests self = <azure.cli.core.tests.test_util.TestUtils testMethod=test_send_raw_requests>
send_mock = <function send at 0x7fd3bf50b420>
get_raw_token_mock = <function get_raw_token at 0x7fd3bf50b9c0>

    @mock.patch.dict('os.environ')
    @mock.patch('azure.cli.core.profile.Profile.get_raw_token', autospec=True)
    @mock.patch('requests.Session.send', autospec=True)
    def test_send_raw_requests(self, send_mock, get_raw_token_mock):
        if 'AZURE_HTTP_USER_AGENT' in os.environ:
            del os.environ['AZURE_HTTP_USER_AGENT']  # Clear env var possibly added by DevOps
    
        return_val = mock.MagicMock()
        return_val.is_ok = True
        send_mock.return_value = return_val
        get_raw_token_mock.return_value = ("Bearer", "eyJ0eXAiOiJKV1", None), None, None
    
        cli_ctx = DummyCli()
        cli_ctx.data = {
            'command': 'rest',
            'safe_params': ['method', 'uri']
        }
        test_arm_active_directory_resource_id = 'https://management.core.windows.net/'
        test_arm_endpoint = 'https://management.azure.com/'
        subscription_id = '00000001-0000-0000-0000-000000000000'
        arm_resource_id = '/subscriptions/{}/resourcegroups/02?api-version=2019-07-01'.format(subscription_id)
        full_arm_rest_url = test_arm_endpoint.rstrip('/') + arm_resource_id
        test_body = '{"b1": "v1"}'
    
        expected_header = {
            'User-Agent': get_az_rest_user_agent(),
            'Accept': '/',
            'Connection': 'keep-alive',
            'Content-Type': 'application/json',
            'CommandName': 'rest',
            'ParameterSetName': 'method uri',
            'Content-Length': '12'
        }
        expected_header_with_auth = expected_header.copy()
        expected_header_with_auth['Authorization'] = 'Bearer eyJ0eXAiOiJKV1'
    
        # Test basic usage
        # Mock Put Blob https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob
        # Authenticate with service SAS https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
        sas_token = ['sv=2019-02-02', '{"srt": "s"}', "{'ss': 'bf'}"]
        send_raw_request(cli_ctx, 'PUT', 'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30',
                         uri_parameters=sas_token, body=test_body,
                         generated_client_request_id_name=None)
    
        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args[0][1]
        self.assertEqual(request.method, 'PUT')
        self.assertEqual(request.url, 'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30&sv=2019-02-02&srt=s&ss=bf')
        self.assertEqual(request.body, '{"b1": "v1"}')
        # Verify no Authorization header
        self.assert_headers(request.headers, expected_header)
        self.assertEqual(send_mock.call_args[1]["verify"], not should_disable_connection_verify())
    
        # Test Authorization header is skipped
        send_raw_request(cli_ctx, 'GET', full_arm_rest_url, body=test_body, skip_authorization_header=True,
                         generated_client_request_id_name=None)
    
        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args[0][1]
>       self.assert_headers(request.headers, expected_header)

src/azure-cli-core/azure/cli/core/tests/test_util.py:307: 
 
 
                                     _ 
src/azure-cli-core/azure/cli/core/tests/test_util.py:38: in _assert_headers
    self.assertDictEqual(actual_headers, expected_headers)
E   AssertionError: {'Use[134 chars], 'CommandName': 'rest', 'ParameterSetName': '[31 chars]'12'} != {'Use[134 chars], 'Content-Type': 'application/json', 'Command[67 chars]'12'}
E     {'Accept': '/',
E      'CommandName': 'rest',
E      'Connection': 'keep-alive',
E      'Content-Length': '12',
E   +  'Content-Type': 'application/json',
E      'ParameterSetName': 'method uri',
E      'User-Agent': 'python/3.12.12 (Linux-6.8.0-1052-azure-x86_64-with-glibc2.35) '
E                    'AZURECLI/2.87.0'}
azure/cli/core/tests/test_util.py:247
❌3.13
Type Test Case Error Message Line
Failed test_send_raw_requests self = <azure.cli.core.tests.test_util.TestUtils testMethod=test_send_raw_requests>
send_mock = <function send at 0x7f53fff142c0>
get_raw_token_mock = <function get_raw_token at 0x7f53fff14860>

    @mock.patch.dict('os.environ')
    @mock.patch('azure.cli.core.profile.Profile.get_raw_token', autospec=True)
    @mock.patch('requests.Session.send', autospec=True)
    def test_send_raw_requests(self, send_mock, get_raw_token_mock):
        if 'AZURE_HTTP_USER_AGENT' in os.environ:
            del os.environ['AZURE_HTTP_USER_AGENT']  # Clear env var possibly added by DevOps
    
        return_val = mock.MagicMock()
        return_val.is_ok = True
        send_mock.return_value = return_val
        get_raw_token_mock.return_value = ("Bearer", "eyJ0eXAiOiJKV1", None), None, None
    
        cli_ctx = DummyCli()
        cli_ctx.data = {
            'command': 'rest',
            'safe_params': ['method', 'uri']
        }
        test_arm_active_directory_resource_id = 'https://management.core.windows.net/'
        test_arm_endpoint = 'https://management.azure.com/'
        subscription_id = '00000001-0000-0000-0000-000000000000'
        arm_resource_id = '/subscriptions/{}/resourcegroups/02?api-version=2019-07-01'.format(subscription_id)
        full_arm_rest_url = test_arm_endpoint.rstrip('/') + arm_resource_id
        test_body = '{"b1": "v1"}'
    
        expected_header = {
            'User-Agent': get_az_rest_user_agent(),
            'Accept': '/',
            'Connection': 'keep-alive',
            'Content-Type': 'application/json',
            'CommandName': 'rest',
            'ParameterSetName': 'method uri',
            'Content-Length': '12'
        }
        expected_header_with_auth = expected_header.copy()
        expected_header_with_auth['Authorization'] = 'Bearer eyJ0eXAiOiJKV1'
    
        # Test basic usage
        # Mock Put Blob https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob
        # Authenticate with service SAS https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
        sas_token = ['sv=2019-02-02', '{"srt": "s"}', "{'ss': 'bf'}"]
        send_raw_request(cli_ctx, 'PUT', 'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30',
                         uri_parameters=sas_token, body=test_body,
                         generated_client_request_id_name=None)
    
        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args[0][1]
        self.assertEqual(request.method, 'PUT')
        self.assertEqual(request.url, 'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30&sv=2019-02-02&srt=s&ss=bf')
        self.assertEqual(request.body, '{"b1": "v1"}')
        # Verify no Authorization header
        self.assert_headers(request.headers, expected_header)
        self.assertEqual(send_mock.call_args[1]["verify"], not should_disable_connection_verify())
    
        # Test Authorization header is skipped
        send_raw_request(cli_ctx, 'GET', full_arm_rest_url, body=test_body, skip_authorization_header=True,
                         generated_client_request_id_name=None)
    
        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args[0][1]
>       self.assert_headers(request.headers, expected_header)

src/azure-cli-core/azure/cli/core/tests/test_util.py:307: 
 
 
                                     _ 
src/azure-cli-core/azure/cli/core/tests/test_util.py:38: in _assert_headers
    self.assertDictEqual(actual_headers, expected_headers)
E   AssertionError: {'Use[134 chars], 'CommandName': 'rest', 'ParameterSetName': '[31 chars]'12'} != {'Use[134 chars], 'Content-Type': 'application/json', 'Command[67 chars]'12'}
E     {'Accept': '/',
E      'CommandName': 'rest',
E      'Connection': 'keep-alive',
E      'Content-Length': '12',
E   +  'Content-Type': 'application/json',
E      'ParameterSetName': 'method uri',
E      'User-Agent': 'python/3.13.11 (Linux-6.8.0-1052-azure-x86_64-with-glibc2.35) '
E                    'AZURECLI/2.87.0'}
azure/cli/core/tests/test_util.py:247
️✔️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
️✔️postgresql
️✔️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
️✔️3.13
️✔️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 May 28, 2026

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

Copilot AI changed the title [WIP] Fix unsupported media type error in az rest command [Core] az rest: default JSON Content-Type for PUT/POST/PATCH body requests May 28, 2026
Copilot finished work on behalf of a0x1ab May 28, 2026 07:54
Copilot AI requested a review from a0x1ab May 28, 2026 07:54
@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented May 28, 2026

Core

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.

"ERROR: Unsupported Media Type" when trying to use az rest

3 participants