Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e687e03
first commit
nassiharel Jan 28, 2026
602a90d
more improvements
nassiharel Jan 29, 2026
9510fd8
more improvements
nassiharel Jan 29, 2026
3f418fe
more improvements
nassiharel Jan 30, 2026
58f285f
refactor files
nassiharel Feb 10, 2026
dc41006
add troubleshoot_k8s_extension
nassiharel Feb 10, 2026
9ee8db6
Merge branch 'main' into feature/nassiharel/video-indexer
nassiharel Feb 10, 2026
d844161
refactor files
nassiharel Feb 11, 2026
2b9b2f0
refactor files
nassiharel Feb 11, 2026
947b03a
refactor files
nassiharel Feb 11, 2026
e202b11
fix versioning
nassiharel Feb 11, 2026
f0af6ea
try to fix Pylint errors
nassiharel Feb 11, 2026
7db8476
try to fix copilot comments
nassiharel Feb 11, 2026
8d0f1ad
try to fix copilot comments
nassiharel Feb 11, 2026
0828fd0
improve command arguments
nassiharel Feb 12, 2026
31d5637
add tests
nassiharel Feb 12, 2026
4ed9dd8
add tests
nassiharel Feb 12, 2026
9369147
add tests
nassiharel Feb 12, 2026
ded30bc
improve tests
nassiharel Feb 12, 2026
239662d
Copilot CR fixes
nassiharel Feb 13, 2026
9208013
Copilot CR fixes
nassiharel Feb 13, 2026
4683c5e
Copilot CR fixes
nassiharel Feb 13, 2026
d47bb3c
Copilot CR fixes
nassiharel Feb 13, 2026
b5dfeee
Copilot CR fixes
nassiharel Feb 13, 2026
1435b14
Copilot CR fixes
nassiharel Feb 13, 2026
4c24792
Copilot CR fixes
nassiharel Feb 13, 2026
1221e41
fix disallowed_html_tag_from_parameter
nassiharel Feb 13, 2026
990c724
Copilot CR fixes
nassiharel Feb 13, 2026
04b3264
Copilot CR fixes
nassiharel Feb 13, 2026
8caa58a
Copilot CR fixes
nassiharel Feb 15, 2026
ddc0832
Add more tests and Copilot CR fixes
nassiharel Feb 15, 2026
be34917
Copilot CR fixes
nassiharel Feb 15, 2026
6046a81
Copilot CR fixes
nassiharel Feb 15, 2026
4b4b9fe
Copilot CR fixes
nassiharel Feb 15, 2026
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,5 @@
/src/aks-agent/ @feiskyer @mainred @nilo19

/src/migrate/ @saifaldin14

/src/vi/ @nassiharel
5 changes: 5 additions & 0 deletions src/service_name.json
Original file line number Diff line number Diff line change
Expand Up @@ -993,5 +993,10 @@
"Command": "az migrate",
"AzureServiceName": "Azure Migrate",
"URL": "https://learn.microsoft.com/azure/migrate"
},
{
"Command": "az vi",
"AzureServiceName": "Azure Video Indexer",
"URL": "https://learn.microsoft.com/en-us/azure/azure-video-indexer"
}
]
8 changes: 8 additions & 0 deletions src/vi/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. :changelog:

Release History
===============

1.0.0b1
++++++
* Initial release.
42 changes: 42 additions & 0 deletions src/vi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Microsoft Azure CLI 'vi' Extension
=============================================

This package is for the Video Indexer ('vi') i.e. 'az vi'
Azure AI Video Indexer is an AI-powered solution that helps you extract insights from video and audio content.
It is available both as an Azure Arc extension for edge deployments and as a cloud-based application.
Video Indexer: [more info](https://learn.microsoft.com/en-us/azure/azure-video-indexer/)

#### Video Indexer:
This package includes the 'extension' and 'camera' subgroups.
i.e. 'az vi extension' and 'az vi camera'

### Included Features
##### Show Video Indexer Extension details
```
az vi extension show \
--resource-group groupName \
--connected-cluster clusterName
```

##### Troubleshoot a Video Indexer Extension
```
az vi extension troubleshoot \
--resource-group groupName \
--connected-cluster clusterName
```

##### Add a camera to a Video Indexer Extension
```
az vi camera add \
--resource-group groupName \
--connected-cluster clusterName \
--camera-name mycamera \
--rtsp-url rtsp://mycamera
```

##### List all cameras for a Video Indexer Extension
```
az vi camera list \
--resource-group groupName \
--connected-cluster clusterName
```
Comment thread
nassiharel marked this conversation as resolved.
36 changes: 36 additions & 0 deletions src/vi/azext_vi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core import AzCommandsLoader
from . import consts

from ._help import helps # pylint: disable=unused-import

from knack.commands import CLICommand


class ViCommandsLoader(AzCommandsLoader):

def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
from ._client_factory import cf_vi
vi_custom = CliCommandType(
operations_tmpl=consts.EXTENSION_PACKAGE_NAME + '.custom#{}',
client_factory=cf_vi)
super().__init__(cli_ctx=cli_ctx,
custom_command_type=vi_custom)

def load_command_table(self, args):
from .commands import load_command_table
load_command_table(self, args)
command_table = self.command_table
return command_table
Comment thread
nassiharel marked this conversation as resolved.

def load_arguments(self, command: CLICommand):
from ._params import load_arguments
load_arguments(self, command)


COMMAND_LOADER_CLS = ViCommandsLoader
11 changes: 11 additions & 0 deletions src/vi/azext_vi/_client_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.commands.client_factory import get_mgmt_service_client


def cf_vi(cli_ctx, *_):
from .vendored_sdks import VIManagementClient
return get_mgmt_service_client(cli_ctx, VIManagementClient)
20 changes: 20 additions & 0 deletions src/vi/azext_vi/_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from collections import OrderedDict


def camera_list_table_format(results):
return [__get_table_row(result) for result in results]


def __get_table_row(result):
return OrderedDict([
('name', result['name']),
('status', result.get('status', '')),
('rtspUrl', result.get('rtspUrl', '')),
('liveEnabled', result.get('liveStreamingEnabled', '')),
('recordingEnabled', result.get('recordingEnabled', '')),
])
69 changes: 69 additions & 0 deletions src/vi/azext_vi/_help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from knack.help_files import helps # pylint: disable=unused-import
from . import consts


helps[f'{consts.EXTENSION_NAME}'] = """
type: group
short-summary: Commands to manage Video Indexer for Cloud and Edge.
"""

helps[f'{consts.EXTENSION_NAME} extension'] = """
type: group
short-summary: Commands to manage Video Indexer Extension.
"""

helps[f'{consts.EXTENSION_NAME} extension show'] = f"""
type: command
short-summary: Show Video Indexer Extension details.
long-summary: Show Video Indexer Extension details including its properties.
examples:
- name: Show Video Indexer Extension details
text: |-
az {consts.EXTENSION_NAME} extension show --resource-group my-resource-group \
--connected-cluster mycluster
"""
Comment thread
nassiharel marked this conversation as resolved.

helps[f'{consts.EXTENSION_NAME} extension troubleshoot'] = f"""
type: command
short-summary: Perform diagnostic checks on a Video Indexer Extension.
long-summary: This command is used to troubleshoot a Video Indexer Extension. It \
collects logs and other information that can be used to diagnose issues with the extension.
examples:
- name: Troubleshoot a Video Indexer Extension
text: |-
az {consts.EXTENSION_NAME} extension troubleshoot --resource-group my-resource-group \
--connected-cluster mycluster
"""
Comment thread
nassiharel marked this conversation as resolved.

helps[f'{consts.EXTENSION_NAME} camera'] = """
type: group
short-summary: Commands to manage Video Indexer cameras.
"""

helps[f'{consts.EXTENSION_NAME} camera add'] = f"""
type: command
short-summary: Add a camera to a Video Indexer Extension.
long-summary: Add a camera to a Video Indexer Extension on a connected cluster. This command registers a camera with the extension, allowing it to be used for video indexing.
examples:
- name: Add a camera to a Video Indexer Extension
text: |-
az {consts.EXTENSION_NAME} camera add --resource-group my-resource-group \
--connected-cluster mycluster --camera-name mycamera --rtsp-url rtsp://my-url
"""

helps[f'{consts.EXTENSION_NAME} camera list'] = f"""
type: command
short-summary: List all cameras associated with a Video Indexer Extension.
long-summary: List all cameras associated with a Video Indexer Extension on a connected cluster.
examples:
- name: List all cameras for a Video Indexer Extension
text: |-
az {consts.EXTENSION_NAME} camera list --resource-group my-resource-group \
--connected-cluster mycluster
"""
Comment thread
nassiharel marked this conversation as resolved.
50 changes: 50 additions & 0 deletions src/vi/azext_vi/_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from . import consts

from knack.commands import CLICommand
from knack.arguments import CLIArgumentType

from azure.cli.core.commands.parameters import get_three_state_flag


def load_arguments(self, _: CLICommand):
connected_cluster = CLIArgumentType(
options_list=['--connected-cluster', '-c'],
required=True,
help='Name of the Kubernetes connected cluster')
camera_name = CLIArgumentType(
options_list=['--camera-name'],
required=True,
help='Name of the camera to be added to Video Indexer')
rtsp_url = CLIArgumentType(
options_list=['--rtsp-url'],
required=True,
help='URL of the camera. Should be in RTSP format, e.g. rtsp://my-url')
ignore_certificate = CLIArgumentType(
options_list=['--ignore-certificate', '-i'],
arg_type=get_three_state_flag(),
required=False,
help='Ignore the TLS certificate of the Video Indexer endpoint. '
'By default, certificate verification is enabled. WARNING: Disabling '
'certificate verification reduces security and may expose the connection '
'to man-in-the-middle attacks. Use only in trusted or development environments.')

with self.argument_context(f"{consts.EXTENSION_NAME} extension show") as c:
c.argument('connected_cluster', connected_cluster)

with self.argument_context(f"{consts.EXTENSION_NAME} extension troubleshoot") as c:
c.argument('connected_cluster', connected_cluster)

with self.argument_context(f"{consts.EXTENSION_NAME} camera add") as c:
c.argument('connected_cluster', connected_cluster)
c.argument('ignore_certificate', ignore_certificate)
c.argument('camera_name', camera_name)
c.argument('rtsp_url', rtsp_url)

with self.argument_context(f"{consts.EXTENSION_NAME} camera list") as c:
c.argument('connected_cluster', connected_cluster)
c.argument('ignore_certificate', ignore_certificate)
4 changes: 4 additions & 0 deletions src/vi/azext_vi/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"azext.isPreview": true,
"azext.minCliCoreVersion": "2.38.0"
Comment thread
nassiharel marked this conversation as resolved.
}
20 changes: 20 additions & 0 deletions src/vi/azext_vi/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from ._client_factory import cf_vi
from ._format import camera_list_table_format
from . import consts


def load_command_table(self, _):
with self.command_group(f"{consts.EXTENSION_NAME} extension", client_factory=cf_vi, is_preview=True) \
as g:
g.custom_show_command('show', 'show_vi_extension')
g.custom_command('troubleshoot', 'troubleshoot_vi_extension')

with self.command_group(f"{consts.EXTENSION_NAME} camera", client_factory=cf_vi, is_preview=True) \
as g:
g.custom_command('add', 'add_camera')
g.custom_command('list', 'list_cameras', table_transformer=camera_list_table_format)
22 changes: 22 additions & 0 deletions src/vi/azext_vi/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

EXTENSION_NAME = "vi"
EXTENSION_PACKAGE_NAME = "azext_vi"
PROVIDER_NAMESPACE = "Microsoft.KubernetesConfiguration"

KUBECONFIG_LOAD_FAILED_WARNING = """Unable to load the kubeconfig file.
Please check
https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/diagnose-connection-issues#is-kubeconfig-pointing-to-the-right-cluster"""

HELM_VERSION = "v3.12.2"
HELM_MCR_URL = "azure-cli/helm"
PROVISIONED_CLUSTER_TYPE = "provisionedclusters"
KUBEAPI_CONNECTIVITY_FAILED_WARNING = """Unable to verify connectivity to the Kubernetes cluster.
Please check https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/diagnose-connection-issues"""

KUBERNETES_CONNECTIVITY_FAULT_TYPE = "kubernetes-cluster-connection-error"
ARC_EXT_DIAGNOSTIC_LOGS = "arc_ext_diagnostic_logs"
49 changes: 49 additions & 0 deletions src/vi/azext_vi/custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from knack.util import CLIError
from .k8s_utils import troubleshoot_k8s_extension


def _get_vi_extension(client, resource_group_name, connected_cluster):
extension = client.extensions.get_vi_extension(
resource_group=resource_group_name,
connected_cluster=connected_cluster)
if not extension:
raise CLIError(
f'VI Extension not found in connected cluster "{connected_cluster}" '
f'under resource group "{resource_group_name}".')
return extension


def show_vi_extension(client, resource_group_name, connected_cluster):
extension = _get_vi_extension(client, resource_group_name, connected_cluster)
return extension


def troubleshoot_vi_extension(cmd, client, resource_group_name, connected_cluster):
extension = _get_vi_extension(client, resource_group_name, connected_cluster)
namespace = extension.get("properties", {}).get("scope", {}).get("cluster", {}).get("releaseNamespace")
if not namespace:
raise CLIError(
f'Unable to determine namespace for VI Extension in connected cluster "{connected_cluster}".')

troubleshoot_k8s_extension(cmd=cmd, name=extension.get("name"), namespace_list=namespace)


def add_camera(client, resource_group_name, connected_cluster, camera_name, rtsp_url, ignore_certificate=False):
extension = _get_vi_extension(client, resource_group_name, connected_cluster)
response = client.cameras.add_camera(extension=extension,
camera_name=camera_name,
rtsp_url=rtsp_url,
ignore_certificate=ignore_certificate)
return response


def list_cameras(client, resource_group_name, connected_cluster, ignore_certificate=False):
extension = _get_vi_extension(client, resource_group_name, connected_cluster)
response = client.cameras.list_cameras(extension=extension, ignore_certificate=ignore_certificate)
cameras = response.get('results', [])
return cameras
Comment thread
nassiharel marked this conversation as resolved.
Comment thread
nassiharel marked this conversation as resolved.
Loading
Loading