Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/aimanager/azext_aimanager/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@
text: az aimanager namespace get-credentials -m my-ai-manager -g myrg --name team-alpha -f -
"""

helps['aimanager namespace list-accesskeys'] = """
type: command
short-summary: List the inference gateway endpoint and API keys for an AI Manager namespace.
examples:
- name: List namespace access keys
text: az aimanager namespace list-accesskeys -m my-ai-manager -g myrg --name team-alpha
"""

helps['aimanager namespace rotate-accesskeys'] = """
type: command
short-summary: Rotate the API keys for an AI Manager namespace.
long-summary: |-
Generates a new primary key and moves the previous primary key to the secondary key so
clients can roll over without downtime.
examples:
- name: Rotate namespace access keys
text: az aimanager namespace rotate-accesskeys -m my-ai-manager -g myrg --name team-alpha
"""

helps['aimanager namespace modeldeployment'] = """
type: group
short-summary: Manage model deployments within an AI Manager namespace.
Expand Down
6 changes: 6 additions & 0 deletions src/aimanager/azext_aimanager/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def load_arguments(self, _):
c.argument('aks_custom_headers', options_list=['--aks-custom-headers'],
help='Comma-separated key=value pairs to specify custom headers.')

for scope in ['aimanager namespace list-accesskeys',
'aimanager namespace rotate-accesskeys']:
with self.argument_context(scope) as c:
c.argument('aks_custom_headers', options_list=['--aks-custom-headers'],
help='Comma-separated key=value pairs to specify custom headers.')

with self.argument_context('aimanager namespace modeldeployment') as c:
c.argument('ai_manager_name', options_list=['--aimanager-name'],
validator=validate_ai_manager_name,
Expand Down
2 changes: 2 additions & 0 deletions src/aimanager/azext_aimanager/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def load_command_table(self, _):
g.custom_command("list", "list_aimanager_namespace")
g.custom_command("delete", "delete_aimanager_namespace", supports_no_wait=True, confirmation=True)
g.custom_command("get-credentials", "aimanager_namespace_get_credentials")
g.custom_command("list-accesskeys", "list_aimanager_namespace_access_keys")
g.custom_command("rotate-accesskeys", "rotate_aimanager_namespace_access_keys", confirmation=True)
g.wait_command("wait")

# aimanager namespace modeldeployment command group
Expand Down
14 changes: 14 additions & 0 deletions src/aimanager/azext_aimanager/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,20 @@ def aimanager_namespace_get_credentials(cmd,
resource_group_name, ai_manager_name, namespace_name, headers=headers)
_write_kubeconfig(credential_results, path, overwrite_existing, context_name)


def list_aimanager_namespace_access_keys(cmd, client, resource_group_name, ai_manager_name,
namespace_name, aks_custom_headers=None): # pylint: disable=unused-argument
headers = get_aks_custom_headers(aks_custom_headers)
return client.list_access_keys(
resource_group_name, ai_manager_name, namespace_name, headers=headers)


def rotate_aimanager_namespace_access_keys(cmd, client, resource_group_name, ai_manager_name,
namespace_name, aks_custom_headers=None): # pylint: disable=unused-argument
headers = get_aks_custom_headers(aks_custom_headers)
return client.rotate_keys(
resource_group_name, ai_manager_name, namespace_name, headers=headers)

# endregion


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from unittest.mock import MagicMock, patch

from azure.cli.testsdk import ScenarioTest

from azext_aimanager.vendored_sdks.v2026_05_02_preview import models


class NamespaceAccessKeysScenarioTest(ScenarioTest):

def test_namespace_access_key_commands(self):
current_access = models.NamespaceAccessInfo({
'endpoint': 'https://namespace.example/v1',
'primaryKey': 'primary-before',
'secondaryKey': 'secondary-before',
})
rotated_access = models.NamespaceAccessInfo({
'endpoint': 'https://namespace.example/v1',
'primaryKey': 'primary-after',
'secondaryKey': 'primary-before',
'lastRotatedAt': '2026-08-12T00:00:00Z',
})

operations = MagicMock()
operations.list_access_keys.return_value = current_access
operations.rotate_keys.return_value = rotated_access
service_client = MagicMock()
service_client.ai_manager_namespaces = operations

command_suffix = '-g rg -m manager -n namespace --aks-custom-headers test-header=value'
with patch('azext_aimanager._client_factory.get_aimanager_client',
return_value=service_client):
self.cmd(
'aimanager namespace list-accesskeys ' + command_suffix,
checks=[
self.check('endpoint', 'https://namespace.example/v1'),
self.check('primaryKey', 'primary-before'),
self.check('secondaryKey', 'secondary-before'),
])
self.cmd(
'aimanager namespace rotate-accesskeys ' + command_suffix + ' --yes',
checks=[
self.check('primaryKey', 'primary-after'),
self.check('secondaryKey', 'primary-before'),
self.check('lastRotatedAt', '2026-08-12T00:00:00+00:00'),
])

expected_headers = {'test-header': 'value'}
operations.list_access_keys.assert_called_once_with(
'rg', 'manager', 'namespace', headers=expected_headers)
operations.rotate_keys.assert_called_once_with(
'rg', 'manager', 'namespace', headers=expected_headers)
Loading