Skip to content
29 changes: 12 additions & 17 deletions src/ssh/azext_ssh/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,24 @@
# regenerated.
# --------------------------------------------------------------------------

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


def cf_hybridconnectivity_cl(cli_ctx, *_):
from azext_ssh.vendored_sdks.hybridconnectivity import HybridConnectivityManagementAPI
return get_mgmt_service_client(cli_ctx,
HybridConnectivityManagementAPI)


def cf_endpoint(cli_ctx, *_):
return cf_hybridconnectivity_cl(cli_ctx).endpoints


def cf_connectedmachine_cl(cli_ctx, *_):
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azext_ssh.vendored_sdks.connectedmachine import ConnectedMachine
return get_mgmt_service_client(cli_ctx,
ConnectedMachine)


def cf_machine(cli_ctx, *_):
return cf_connectedmachine_cl(cli_ctx).machines


def cf_machine_extension(cli_ctx, *_):
return cf_connectedmachine_cl(cli_ctx).machine_extensions


def cf_private_link_scope(cli_ctx, *_):
return cf_connectedmachine_cl(cli_ctx).private_link_scopes


def cf_private_link_resource(cli_ctx, *_):
return cf_connectedmachine_cl(cli_ctx).private_link_resources


def cf_private_endpoint_connection(cli_ctx, *_):
return cf_connectedmachine_cl(cli_ctx).private_endpoint_connections
4 changes: 2 additions & 2 deletions src/ssh/azext_ssh/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

CLIENT_PROXY_VERSION = "1.3.017131"
CLIENT_PROXY_RELEASE = "release10-09-21"
CLIENT_PROXY_VERSION = "1.3.017634"
CLIENT_PROXY_RELEASE = "release01-11-21"
CLIENT_PROXY_STORAGE_URL = "https://sshproxysa.blob.core.windows.net"
CLEANUP_TOTAL_TIME_LIMIT_IN_SECONDS = 120
CLEANUP_TIME_INTERVAL_IN_SECONDS = 10
62 changes: 40 additions & 22 deletions src/ssh/azext_ssh/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,24 @@ def _arc_get_client_side_proxy():
# Get the Access Details to connect to Arc Connectivity platform from the HybridCompute Resource Provider
# TO DO: This is a temporary API call to get the relay info. We will move to a different one in the future.
def _arc_list_access_details(cmd, resource_group, vm_name):
from azext_ssh._client_factory import cf_machine
client = cf_machine(cmd.cli_ctx)
status_code, result = client.list_access_details(resource_group_name=resource_group, machine_name=vm_name)
if status_code in [501, 404]:
error = {404: 'Not Found', 501: 'Not Implemented'}
raise azclierror.BadRequestError("REST API request for access information returned an invalid status "
f"\"{error[status_code]}\". Please update the current version of the SSH"
"extension by runing \"az extension update ssh\".")
result = result.replace("\'", "\"")
result_bytes = result.encode("ascii")
from azext_ssh._client_factory import cf_endpoint
client = cf_endpoint(cmd.cli_ctx)
try:
result = client.list_credentials(resource_group_name=resource_group, machine_name=vm_name, endpoint_name="default")
except Exception as e:
raise azclierror.ClientRequestError(f"Request for Azure Relay Information Failed: {str(e)}")

result_string = json.dumps(
{
"relay": {
"namespaceName": result.namespace_name,
"namespaceNameSuffix": result.namespace_name_suffix,
"hybridConnectionName": result.hybrid_connection_name,
"accessKey": result.access_key,
"expiresOn": result.expires_on
}
})
result_bytes = result_string.encode("ascii")
enc = base64.b64encode(result_bytes)
base64_result_string = enc.decode("ascii")
return base64_result_string
Expand Down Expand Up @@ -334,17 +342,21 @@ def _decide_op_call(cmd, resource_group_name, vm_name, resource_id, ssh_ip, conf
raise azclierror.InvalidArgumentValueError(error)

else:
is_azure_vm = _check_if_azure_vm(cmd, resource_group_name, vm_name)
is_arc_server = _check_if_arc_server(cmd, resource_group_name, vm_name)
vm_error, is_azure_vm = _check_if_azure_vm(cmd, resource_group_name, vm_name)
arc_error, is_arc_server = _check_if_arc_server(cmd, resource_group_name, vm_name)

if is_azure_vm and is_arc_server:
raise azclierror.BadRequestError(f"{resource_group_name} has Azure VM and Arc Server with the "
f"same name: {vm_name}. Please try with --resource-id instead "
"of --vm-name and --resource-group")
if not is_azure_vm and not is_arc_server:
from azure.core.exceptions import ResourceNotFoundError
raise ResourceNotFoundError(f"The Resource {vm_name} under resource group '{resource_group_name}' "
"was not found.")
if isinstance(arc_error, ResourceNotFoundError) and isinstance(vm_error, ResourceNotFoundError):
raise azclierror.ResourceNotFoundError(f"The resource {vm_name} in the resource group "
"{resource_group_name} was not found. Erros:\n"
f"{str(arc_error)}\n{str(vm_error)}")
raise azclierror.BadRequestError("Unable to determine the target machine type as Azure VM or "
f"Arc Server. Errors:\n{str(arc_error)}\n{str(vm_error)}")

if config_path:
op_call = functools.partial(ssh_utils.write_ssh_config, config_path=config_path, overwrite=overwrite,
Expand All @@ -361,21 +373,27 @@ def _decide_op_call(cmd, resource_group_name, vm_name, resource_id, ssh_ip, conf
def _check_if_azure_vm(cmd, resource_group_name, vm_name):
from azure.cli.core.commands import client_factory
from azure.cli.core import profiles
from azure.core.exceptions import ResourceNotFoundError
from azure.core.exceptions import ResourceNotFoundError, HttpResponseError
try:
compute_client = client_factory.get_mgmt_service_client(cmd.cli_ctx, profiles.ResourceType.MGMT_COMPUTE)
compute_client.virtual_machines.get(resource_group_name, vm_name)
except ResourceNotFoundError:
return False
return True
except ResourceNotFoundError as e:
return e, False
# If user is not authorized to get the VM, it will throw a HttpResponseError
except HttpResponseError as e:
return e, False
return None, True


def _check_if_arc_server(cmd, resource_group_name, vm_name):
from azure.core.exceptions import ResourceNotFoundError
from azure.core.exceptions import ResourceNotFoundError, HttpResponseError
from azext_ssh._client_factory import cf_machine
client = cf_machine(cmd.cli_ctx)
try:
client.get(resource_group_name=resource_group_name, machine_name=vm_name)
except ResourceNotFoundError:
return False
return True
except ResourceNotFoundError as e:
return e, False
# If user is not authorized to get the arc server, it will throw a HttpResponseError
except HttpResponseError as e:
return e, False
return None, True
3 changes: 3 additions & 0 deletions src/ssh/azext_ssh/vendored_sdks/connectedmachine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# --------------------------------------------------------------------------

from ._connected_machine import ConnectedMachine
from ._version import VERSION

__version__ = VERSION
__all__ = ['ConnectedMachine']

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
from azure.core.pipeline import policies
from azure.mgmt.core.policies import ARMHttpLoggingPolicy

from ._version import VERSION

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from typing import Any

from azure.core.credentials import TokenCredential

VERSION = "unknown"

class ConnectedMachineConfiguration(Configuration):
"""Configuration for ConnectedMachine.
Expand Down Expand Up @@ -47,9 +48,9 @@ def __init__(

self.credential = credential
self.subscription_id = subscription_id
self.api_version = "2021-06-10-preview"
self.api_version = "2021-05-20"
self.credential_scopes = kwargs.pop('credential_scopes', ['https://management.azure.com/.default'])
kwargs.setdefault('sdk_moniker', 'connectedmachine/{}'.format(VERSION))
kwargs.setdefault('sdk_moniker', 'mgmt-hybridcompute/{}'.format(VERSION))
self._configure(**kwargs)

def _configure(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ class ConnectedMachine(ConnectedMachineOperationsMixin):
"""The Hybrid Compute Management Client.

:ivar machines: MachinesOperations operations
:vartype machines: connected_machine.operations.MachinesOperations
:vartype machines: azure.mgmt.hybridcompute.operations.MachinesOperations
:ivar machine_extensions: MachineExtensionsOperations operations
:vartype machine_extensions: connected_machine.operations.MachineExtensionsOperations
:vartype machine_extensions: azure.mgmt.hybridcompute.operations.MachineExtensionsOperations
:ivar operations: Operations operations
:vartype operations: connected_machine.operations.Operations
:vartype operations: azure.mgmt.hybridcompute.operations.Operations
:ivar private_link_scopes: PrivateLinkScopesOperations operations
:vartype private_link_scopes: connected_machine.operations.PrivateLinkScopesOperations
:vartype private_link_scopes: azure.mgmt.hybridcompute.operations.PrivateLinkScopesOperations
:ivar private_link_resources: PrivateLinkResourcesOperations operations
:vartype private_link_resources: connected_machine.operations.PrivateLinkResourcesOperations
:vartype private_link_resources: azure.mgmt.hybridcompute.operations.PrivateLinkResourcesOperations
:ivar private_endpoint_connections: PrivateEndpointConnectionsOperations operations
:vartype private_endpoint_connections: connected_machine.operations.PrivateEndpointConnectionsOperations
:vartype private_endpoint_connections: azure.mgmt.hybridcompute.operations.PrivateEndpointConnectionsOperations
:param credential: Credential needed for the client to connect to Azure.
:type credential: ~azure.core.credentials.TokenCredential
:param subscription_id: The ID of the target subscription.
Expand All @@ -66,6 +66,7 @@ def __init__(

client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)}
self._serialize = Serializer(client_models)
self._serialize.client_side_validation = False
self._deserialize = Deserializer(client_models)

self.machines = MachinesOperations(
Expand Down
9 changes: 9 additions & 0 deletions src/ssh/azext_ssh/vendored_sdks/connectedmachine/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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.
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

VERSION = "1.0.0b1"
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
from azure.core.pipeline import policies
from azure.mgmt.core.policies import ARMHttpLoggingPolicy

from .._version import VERSION

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from azure.core.credentials_async import AsyncTokenCredential

VERSION = "unknown"

class ConnectedMachineConfiguration(Configuration):
"""Configuration for ConnectedMachine.
Expand Down Expand Up @@ -44,9 +45,9 @@ def __init__(

self.credential = credential
self.subscription_id = subscription_id
self.api_version = "2021-06-10-preview"
self.api_version = "2021-05-20"
self.credential_scopes = kwargs.pop('credential_scopes', ['https://management.azure.com/.default'])
kwargs.setdefault('sdk_moniker', 'connectedmachine/{}'.format(VERSION))
kwargs.setdefault('sdk_moniker', 'mgmt-hybridcompute/{}'.format(VERSION))
self._configure(**kwargs)

def _configure(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ class ConnectedMachine(ConnectedMachineOperationsMixin):
"""The Hybrid Compute Management Client.

:ivar machines: MachinesOperations operations
:vartype machines: connected_machine.aio.operations.MachinesOperations
:vartype machines: azure.mgmt.hybridcompute.aio.operations.MachinesOperations
:ivar machine_extensions: MachineExtensionsOperations operations
:vartype machine_extensions: connected_machine.aio.operations.MachineExtensionsOperations
:vartype machine_extensions: azure.mgmt.hybridcompute.aio.operations.MachineExtensionsOperations
:ivar operations: Operations operations
:vartype operations: connected_machine.aio.operations.Operations
:vartype operations: azure.mgmt.hybridcompute.aio.operations.Operations
:ivar private_link_scopes: PrivateLinkScopesOperations operations
:vartype private_link_scopes: connected_machine.aio.operations.PrivateLinkScopesOperations
:vartype private_link_scopes: azure.mgmt.hybridcompute.aio.operations.PrivateLinkScopesOperations
:ivar private_link_resources: PrivateLinkResourcesOperations operations
:vartype private_link_resources: connected_machine.aio.operations.PrivateLinkResourcesOperations
:vartype private_link_resources: azure.mgmt.hybridcompute.aio.operations.PrivateLinkResourcesOperations
:ivar private_endpoint_connections: PrivateEndpointConnectionsOperations operations
:vartype private_endpoint_connections: connected_machine.aio.operations.PrivateEndpointConnectionsOperations
:vartype private_endpoint_connections: azure.mgmt.hybridcompute.aio.operations.PrivateEndpointConnectionsOperations
:param credential: Credential needed for the client to connect to Azure.
:type credential: ~azure.core.credentials_async.AsyncTokenCredential
:param subscription_id: The ID of the target subscription.
Expand All @@ -63,6 +63,7 @@ def __init__(

client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)}
self._serialize = Serializer(client_models)
self._serialize.client_side_validation = False
self._deserialize = Deserializer(client_models)

self.machines = MachinesOperations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def _upgrade_extensions_initial(
401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError
}
error_map.update(kwargs.pop('error_map', {}))
api_version = "2021-06-10-preview"
api_version = "2021-05-20"
content_type = kwargs.pop("content_type", "application/json")
accept = "application/json"

Expand Down Expand Up @@ -87,7 +87,7 @@ async def begin_upgrade_extensions(
:param machine_name: The name of the hybrid machine.
:type machine_name: str
:param extension_upgrade_parameters: Parameters supplied to the Upgrade Extensions operation.
:type extension_upgrade_parameters: ~connected_machine.models.MachineExtensionUpgrade
:type extension_upgrade_parameters: ~azure.mgmt.hybridcompute.models.MachineExtensionUpgrade
:keyword callable cls: A custom type or function that will be passed the direct response
:keyword str continuation_token: A continuation token to restart a poller from a saved state.
:keyword polling: True for ARMPolling, False for no polling, or a
Expand Down
Loading