Skip to content

[HORIZONDB] az horizondb create | show | delete: Introduce commands for Azure HorizonDB#9840

Open
nasc17 wants to merge 3 commits intoAzure:mainfrom
nasc17:nasc/onboardHorizonDB430
Open

[HORIZONDB] az horizondb create | show | delete: Introduce commands for Azure HorizonDB#9840
nasc17 wants to merge 3 commits intoAzure:mainfrom
nasc17:nasc/onboardHorizonDB430

Conversation

@nasc17
Copy link
Copy Markdown
Member

@nasc17 nasc17 commented Apr 30, 2026

Related command
az horizondb create
az horizondb show
az horizondb delete

Description
Onboarding Horizon DB commands for public preview.
Azure/azure-cli#33267

Testing Guide
Manual

..\azure-cli-extensions\src\horizondb\azext_horizondb\tests\latest\test_horizondb_commands.py::HorizonDBClusterMgmtScenarioTest::test_horizondb_cluster_mgmt
[gw0] [100%] PASSED ..\azure-cli-extensions\src\horizondb\azext_horizondb\tests\latest\test_horizondb_commands.py::HorizonDBClusterMgmtScenarioTest::test_horizondb_cluster_mgmt

-------------------------------------- generated xml file: C:\Users\nasc.azdev\env_config\Users\nasc\azure-cli\env\test_results.xml --------------------------------------
=========================================================================== 1 passed in 11.46s

History Notes

[Component Name 1] BREAKING CHANGE: az command a: Make some customer-facing breaking change
[Component Name 2] az command b: Add some customer-facing feature


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

Copilot AI review requested due to automatic review settings April 30, 2026 22:38
@azure-client-tools-bot-prd
Copy link
Copy Markdown

Validation for Breaking Change Starting...

Thanks for your contribution!

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

Hi @nasc17,
Please write the description of changes which can be perceived by customers into HISTORY.rst.
If you want to release a new extension version, please update the version in setup.py as well.

@nasc17
Copy link
Copy Markdown
Member Author

nasc17 commented Apr 30, 2026

Advised by @necusjz to close Azure/azure-cli#33281 and restart within extensions repo.

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new Azure CLI extension module (horizondb) for public preview, including a vendored management SDK and initial CLI commands to manage HorizonDB clusters.

Changes:

  • Introduces az horizondb create | show | delete command group wiring, params, help, and scenario tests.
  • Adds a vendored (generated) HorizonDB management SDK (sync + async clients, models, ops, serialization utils).
  • Adds extension packaging scaffolding (setup.py, setup.cfg, metadata, docs/history).

Reviewed changes

Copilot reviewed 43 out of 45 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/horizondb/setup.py Extension packaging metadata and wheel hook wiring.
src/horizondb/setup.cfg Wheel build configuration.
src/horizondb/linter_exclusions.yml Linter exclusions scaffold for the extension.
src/horizondb/README.md Extension readme with install and command list.
src/horizondb/HISTORY.rst Initial release notes.
src/horizondb/azext_horizondb/azext_metadata.json Declares minimum CLI core version for the extension.
src/horizondb/azext_horizondb/init.py Registers the command loader, help, and argument context.
src/horizondb/azext_horizondb/_help.py Help entries and examples for horizondb commands.
src/horizondb/azext_horizondb/_params.py CLI argument definitions for create/delete/show.
src/horizondb/azext_horizondb/_client_factory.py Creates mgmt client + overrides for testing.
src/horizondb/azext_horizondb/cluster_commands.py Command table wiring for horizondb group.
src/horizondb/azext_horizondb/commands/custom_commands.py Custom implementations for create/delete/list.
src/horizondb/azext_horizondb/utils/_context.py Custom AzArgumentContext behavior for validator composition.
src/horizondb/azext_horizondb/utils/validators.py Password prompting and combined validator logic.
src/horizondb/azext_horizondb/utils/_transformers.py Table transformers for command output.
src/horizondb/azext_horizondb/tests/latest/test_horizondb_commands.py Scenario test for create/show/delete.
src/horizondb/azext_horizondb/tests/latest/constants.py Scenario test constants.
src/horizondb/azext_horizondb/tests/init.py Test package init.
src/horizondb/azext_horizondb/tests/latest/init.py Latest test package init.
src/horizondb/azext_horizondb/utils/init.py Utils package init.
src/horizondb/azext_horizondb/commands/init.py Commands package init.
src/horizondb/azext_horizondb/vendored_sdks/init.py Vendored SDK package entrypoint.
src/horizondb/azext_horizondb/vendored_sdks/_client.py Generated synchronous mgmt client.
src/horizondb/azext_horizondb/vendored_sdks/_configuration.py Generated sync configuration.
src/horizondb/azext_horizondb/vendored_sdks/_patch.py Generated patch hook placeholder.
src/horizondb/azext_horizondb/vendored_sdks/_version.py Generated SDK version constant.
src/horizondb/azext_horizondb/vendored_sdks/aio/init.py Vendored async SDK package entrypoint.
src/horizondb/azext_horizondb/vendored_sdks/aio/_client.py Generated asynchronous mgmt client.
src/horizondb/azext_horizondb/vendored_sdks/aio/_configuration.py Generated async configuration.
src/horizondb/azext_horizondb/vendored_sdks/aio/_patch.py Generated async patch hook placeholder.
src/horizondb/azext_horizondb/vendored_sdks/aio/operations/init.py Generated async operations exports.
src/horizondb/azext_horizondb/vendored_sdks/aio/operations/_patch.py Generated async operations patch hook placeholder.
src/horizondb/azext_horizondb/vendored_sdks/operations/init.py Generated sync operations exports.
src/horizondb/azext_horizondb/vendored_sdks/operations/_patch.py Generated sync operations patch hook placeholder.
src/horizondb/azext_horizondb/vendored_sdks/models/init.py Generated models exports and patch hook.
src/horizondb/azext_horizondb/vendored_sdks/models/_models.py Generated REST models for HorizonDB RP.
src/horizondb/azext_horizondb/vendored_sdks/models/_enums.py Generated enums used by models/ops.
src/horizondb/azext_horizondb/vendored_sdks/models/_patch.py Generated models patch hook placeholder.
src/horizondb/azext_horizondb/vendored_sdks/_utils/init.py Vendored SDK utils package init.
src/horizondb/azext_horizondb/vendored_sdks/_utils/model_base.py Generated model base + rest_field serialization logic.
src/horizondb/azext_horizondb/vendored_sdks/_utils/serialization.py Generated serializer/deserializer utilities.
src/horizondb/azext_horizondb/vendored_sdks/py.typed PEP 561 marker for typing support.
Comments suppressed due to low confidence (2)

src/horizondb/setup.cfg:1

  • universal=1 declares a “universal” (py2/py3) wheel, but the extension is clearly Python 3-only (and Azure CLI itself is Python 3-only). Set universal=0 (or remove this section) to avoid misleading wheel metadata.
    src/horizondb/setup.py:1
  • cmdclass is imported but never passed into setup(...), so the bdist wheel hook won’t actually be applied even when azure_bdist_wheel is available. Pass cmdclass=cmdclass into setup(...) when the import succeeds, or remove the import/try-except if the hook is not needed.

Comment on lines +23 to +24
verbs = cmd.name.rsplit(' ', 2)
if verbs[1] == 'server' and verbs[2] == 'create':
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

cmd.name.rsplit(' ', 2) can return only 2 segments for commands like "horizondb create", so indexing verbs[2] will raise IndexError. Also the check for server/create does not match this extension’s command names, so password_validator and default-location logic will never run. Update this logic to match the actual command (e.g., cmd.name == "horizondb create" / cmd.name.endswith("horizondb create")) and avoid unsafe indexing (check list length or compare the full command string).

Suggested change
verbs = cmd.name.rsplit(' ', 2)
if verbs[1] == 'server' and verbs[2] == 'create':
if cmd.name == 'horizondb create' or cmd.name.endswith(' horizondb create'):

Copilot uses AI. Check for mistakes.
if not arg: # when the argument context scope is N/A
return

self.validators.append(arg.settings['validator'])
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

arg.settings['validator'] can be None (or missing), which will later be invoked in get_combined_validator and fail at runtime (TypeError: 'NoneType' object is not callable). Filter out non-callables when collecting validators (e.g., only append if callable(...)), and consider using arg.settings.get('validator') to avoid a KeyError.

Suggested change
self.validators.append(arg.settings['validator'])
validator = arg.settings.get('validator')
if callable(validator):
self.validators.append(validator)

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +14
for key in ('primary endpoint', 'username', 'password', 'location', 'configuration', 'resource group', 'id', 'version'):
entry = OrderedDict()
entry['Property'] = key
entry['Value'] = result[key]
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

result from the custom create command is a HorizonDbCluster model (serialized to dict) and won’t have keys like 'primary endpoint', 'username', or 'password', so this will raise KeyError and break az horizondb create table output. Adjust the transformer to use actual response fields (e.g., result["properties"]["fullyQualifiedDomainName"], result["properties"]["administratorLogin"] if returned, etc.) and avoid indexing on non-existent human-readable keys.

Suggested change
for key in ('primary endpoint', 'username', 'password', 'location', 'configuration', 'resource group', 'id', 'version'):
entry = OrderedDict()
entry['Property'] = key
entry['Value'] = result[key]
properties = result.get('properties', {}) if result else {}
values = OrderedDict([
('primary endpoint', properties.get('fullyQualifiedDomainName')),
('username', properties.get('administratorLogin')),
('password', properties.get('administratorLoginPassword')),
('location', result.get('location') if result else None),
('configuration', properties.get('configuration')),
('resource group', result.get('resourceGroup') if result else None),
('id', result.get('id') if result else None),
('version', properties.get('version', result.get('version') if result else None))
])
for key, value in values.items():
entry = OrderedDict()
entry['Property'] = key
entry['Value'] = value

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +32
new_entry['Name'] = key['name']
new_entry['Resource Group'] = key['resourceGroup']
new_entry['Location'] = key['location']
new_entry['Version'] = key['version']
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The list results for ARM resources typically do not include a resourceGroup or top-level version field; version is likely under properties, and resource group is usually parsed from id. As written this will commonly raise KeyError. Consider using key.get("properties", {}).get("version") for version and extracting resource group from the resource ID (or omit it from the table if not available).

Suggested change
new_entry['Name'] = key['name']
new_entry['Resource Group'] = key['resourceGroup']
new_entry['Location'] = key['location']
new_entry['Version'] = key['version']
resource_group = key.get('resourceGroup')
if resource_group is None:
resource_id = key.get('id', '')
resource_id_parts = resource_id.split('/')
if 'resourceGroups' in resource_id_parts:
rg_index = resource_id_parts.index('resourceGroups') + 1
if rg_index < len(resource_id_parts):
resource_group = resource_id_parts[rg_index]
new_entry['Name'] = key['name']
new_entry['Resource Group'] = resource_group
new_entry['Location'] = key['location']
new_entry['Version'] = key.get('version', key.get('properties', {}).get('version'))

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +30
from msrest.authentication import Authentication # pylint: disable=import-error
credentials = Authentication()

return HorizonDBMgmtClient(
subscription_id=subscription,
base_url=rm_uri_override,
credential=credentials)
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The generated HorizonDBMgmtClient expects an azure.core.credentials.TokenCredential, but the override path passes an msrest.authentication.Authentication instance, which is incompatible and will fail when the pipeline tries to authenticate. Prefer routing the override through get_mgmt_service_client(...) with base_url=rm_uri_override (so Azure CLI supplies a proper TokenCredential), or construct the client using Azure CLI’s credential adapter rather than msrest.

Suggested change
from msrest.authentication import Authentication # pylint: disable=import-error
credentials = Authentication()
return HorizonDBMgmtClient(
subscription_id=subscription,
base_url=rm_uri_override,
credential=credentials)
return get_mgmt_service_client(
cli_ctx,
HorizonDBMgmtClient,
subscription_id=subscription,
base_url=rm_uri_override)

Copilot uses AI. Check for mistakes.
# pylint: disable=line-too-long, too-many-locals

from knack.log import get_logger
from azure.cli.core.util import CLIError, sdk_no_wait, user_confirmation
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

azure.cli.core.util.CLIError is deprecated in modern Azure CLI patterns (and knack.util.CLIError is similarly legacy). Prefer raising azure.cli.core.azclierror exceptions (e.g., AzCLIError, ValidationError, etc.) for consistent formatting and error handling.

Copilot uses AI. Check for mistakes.
client_factory=cf_horizondb_clusters) as g:
g.custom_command('create', 'horizondb_cluster_create', table_transformer=table_transform_output)
g.custom_command('delete', 'horizondb_cluster_delete')
g.show_command('show', 'get')
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The PR description lists az horizondb get, but the command table only wires up show (backed by the get SDK method). Either update the description to match the actual CLI surface area, or add a get command alias so users can run az horizondb get as documented.

Copilot uses AI. Check for mistakes.
@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Apr 30, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

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>

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

@github-actions github-actions Bot added the release-version-block Updates do not qualify release version rules. NOTE: please do not edit it manually. label Apr 30, 2026
@yonzhan yonzhan requested a review from calvinhzy April 30, 2026 23:26
Co-authored-by: Copilot <copilot@github.com>
@github-actions github-actions Bot removed the release-version-block Updates do not qualify release version rules. NOTE: please do not edit it manually. label May 1, 2026
@necusjz
Copy link
Copy Markdown
Member

necusjz commented May 1, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

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.

5 participants