Skip to content

Commit

Permalink
custom
Browse files Browse the repository at this point in the history
  • Loading branch information
vilit1 committed Dec 11, 2024
1 parent da2c906 commit 65aee3b
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 18 deletions.
35 changes: 35 additions & 0 deletions azext_edge/edge/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,41 @@ def load_iotops_help():
short-summary: Create asset endpoint profiles.
"""

helps[
"iot ops asset endpoint create custom"
] = """
type: command
short-summary: Create an asset endpoint profile with a custom connector.
examples:
- name: Create an asset endpoint with anonymous user authentication using the given instance in the same resource group.
text: >
az iot ops asset endpoint create custom --name myprofile -g myresourcegroup --instance myinstance
--target-address http://rest-server-service.azure-iot-operations.svc.cluster.local:80 --endpoint-type rest-thermostat
- name: Create an asset endpoint with username-password user authentication using the given instance in a different resource group but same subscription. The additional
configuration is provided as an inline json.
text: >
az iot ops asset endpoint create opcua --name myprofile -g myresourcegroup --instance myinstance
--instance-resource-group myinstanceresourcegroup
--target-address http://rest-server-service.azure-iot-operations.svc.cluster.local:80 --endpoint-type rest-thermostat
--username-ref rest-server-auth-creds/username --password-ref rest-server-auth-creds/password
--additional-configuration addition_configuration.json
- name: Create an asset endpoint with anonymous user authentication using the given instance in the same resource group. The inline content is a powershell syntax example.
text: >
az iot ops asset endpoint create custom --name myprofile -g myresourcegroup --instance myinstance
--target-address http://rest-server-service.azure-iot-operations.svc.cluster.local:80 --endpoint-type rest-thermostat
--additional-configuration '{\\\"hello\\\": \\\"world\\\"}'
- name: Create an asset endpoint with anonymous user authentication using the given instance in the same resource group. The inline content is a cmd syntax example.
text: >
az iot ops asset endpoint create custom --name myprofile -g myresourcegroup --instance myinstance
--target-address http://rest-server-service.azure-iot-operations.svc.cluster.local:80 --endpoint-type rest-thermostat
--additional-configuration "{\\\"hello\\\": \\\"world\\\"}"
- name: Create an asset endpoint with anonymous user authentication using the given instance in the same resource group. The inline content is a bash syntax example.
text: >
az iot ops asset endpoint create custom --name myprofile -g myresourcegroup --instance myinstance
--target-address http://rest-server-service.azure-iot-operations.svc.cluster.local:80 --endpoint-type rest-thermostat
--additional-configuration '{"hello": "world"}'
"""

helps[
"iot ops asset endpoint create opcua"
] = """
Expand Down
1 change: 1 addition & 0 deletions azext_edge/edge/command_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def load_iotops_commands(self, _):
"iot ops asset endpoint create",
command_type=aep_resource_ops,
) as cmd_group:
cmd_group.command("custom", "create_custom_asset_endpoint_profile")
cmd_group.command("opcua", "create_opcua_asset_endpoint_profile")

with self.command_group(
Expand Down
35 changes: 35 additions & 0 deletions azext_edge/edge/commands_asset_endpoint_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@ def create_opcua_asset_endpoint_profile(
)


def create_custom_asset_endpoint_profile(
cmd,
asset_endpoint_profile_name: str,
endpoint_profile_type: str,
instance_name: str,
resource_group_name: str,
target_address: str,
certificate_reference: Optional[str] = None,
instance_resource_group: Optional[str] = None,
instance_subscription: Optional[str] = None,
location: Optional[str] = None,
password_reference: Optional[str] = None,
username_reference: Optional[str] = None,
tags: Optional[Dict[str, str]] = None,
additional_configuration: Optional[str] = None,
**kwargs
) -> dict:
return AssetEndpointProfiles(cmd).create(
asset_endpoint_profile_name=asset_endpoint_profile_name,
endpoint_profile_type=endpoint_profile_type,
instance_name=instance_name,
resource_group_name=resource_group_name,
target_address=target_address,
certificate_reference=certificate_reference,
instance_resource_group=instance_resource_group,
instance_subscription=instance_subscription,
location=location,
password_reference=password_reference,
username_reference=username_reference,
tags=tags,
additional_configuration=additional_configuration,
**kwargs
)


def delete_asset_endpoint_profile(
cmd,
asset_endpoint_profile_name: str,
Expand Down
2 changes: 1 addition & 1 deletion azext_edge/edge/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class AEPAuthModes(Enum):
userpass = "UsernamePassword"


class AEPTypes(Enum):
class AEPTypes(ListableEnum):
"""Asset Endpoint Profile (connector) Types"""

# TODO: ensure this is the final enum
Expand Down
14 changes: 14 additions & 0 deletions azext_edge/edge/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,20 @@ def load_iotops_arguments(self, _):
arg_type=tags_type,
)

with self.argument_context("iot ops asset endpoint create custom") as context:
context.argument(
"endpoint_profile_type",
options_list=["--endpoint-type", "--et"],
help="Endpoint Profile Type for the Connector.",
arg_group="Connector",
)
context.argument(
"additional_configuration",
options_list=["--additional-config", "--ac"],
help="File path containing or inline json for the additional configuration.",
arg_group="Connector",
)

with self.argument_context("iot ops asset endpoint create opcua") as context:
context.argument(
"application_name",
Expand Down
20 changes: 16 additions & 4 deletions azext_edge/edge/providers/rpsaas/adr/asset_endpoint_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
InvalidArgumentValueError,
MutuallyExclusiveArgumentError,
RequiredArgumentMissingError,
FileOperationError,
)
from .user_strings import (
AUTH_REF_MISMATCH_ERROR,
Expand Down Expand Up @@ -60,8 +61,9 @@ def create(
password_reference: Optional[str] = None,
username_reference: Optional[str] = None,
tags: Optional[Dict[str, str]] = None,
**additional_configuration
**kwargs
):
from ....util import read_file_content
from .helpers import get_extended_location
extended_location = get_extended_location(
cmd=self.cmd,
Expand All @@ -78,9 +80,19 @@ def create(
# Properties
properties = {"endpointProfileType": endpoint_profile_type}

configuration = None
if endpoint_profile_type == AEPTypes.opcua.value:
properties["additionalConfiguration"] = _build_opcua_config(**additional_configuration)
# TODO: add other connector types in
configuration = _build_opcua_config(**kwargs)
elif kwargs.get("additional_configuration"): # custom type
configuration = kwargs["additional_configuration"]
try:
logger.debug("Processing additional configuration.")
configuration = read_file_content(configuration)
except FileOperationError:
logger.debug("Given additional configuration is not a file.")
pass
properties["additionalConfiguration"] = configuration

_update_properties(
properties,
target_address=target_address,
Expand All @@ -103,7 +115,7 @@ def create(
asset_endpoint_profile_name,
resource=aep_body
)
return wait_for_terminal_state(poller, **additional_configuration)
return wait_for_terminal_state(poller, **kwargs)

def delete(self, asset_endpoint_profile_name: str, resource_group_name: str, **kwargs):
self.show(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,26 @@
logger = get_logger(__name__)


# TODO: update with OPCUA connector args
def test_asset_endpoint_lifecycle(require_init, tracked_resources):
def test_asset_endpoint_lifecycle(require_init, tracked_resources, tracked_files):
rg = require_init["resourceGroup"]
instance = require_init["instanceName"]
custom_location = require_init["customLocationId"]

# Create an endpoint profile
anon_name = "test-endpoint-" + generate_random_string(force_lower=True)[:4]
address = f"opc.tcp://{generate_random_string()}:5000"
endpoint_type = generate_random_string()
anon_endpoint = run(
f"az iot ops asset endpoint create opcua -n {anon_name} -g {rg} --instance {instance} "
f"--ta {address}"
f"az iot ops asset endpoint create custom -n {anon_name} -g {rg} --instance {instance} "
f"--ta {address} --et {endpoint_type}"
)
tracked_resources.append(anon_endpoint["id"])
assert_endpoint_props(
result=anon_endpoint,
name=anon_name,
custom_location=custom_location,
target_address=address
target_address=address,
endpoint_type=endpoint_type,
)

show_endpoint = run(
Expand All @@ -41,7 +42,8 @@ def test_asset_endpoint_lifecycle(require_init, tracked_resources):
result=show_endpoint,
name=anon_name,
custom_location=custom_location,
target_address=address
target_address=address,
endpoint_type=endpoint_type,
)

update_endpoint = run(
Expand All @@ -52,8 +54,38 @@ def test_asset_endpoint_lifecycle(require_init, tracked_resources):
name=anon_name,
custom_location=custom_location,
target_address=address,
endpoint_type=endpoint_type,
)

json_content = json.dumps({
generate_random_string(): generate_random_string(),
generate_random_string(): {
generate_random_string(): generate_random_string()
},
generate_random_string(): generate_random_string()
})
file_name = f"test_schema_version_content_{generate_random_string(size=4)}.json"
tracked_files.append(file_name)
with open(file_name, "w", encoding="utf-8") as f:
f.write(json_content)

anon_name2 = "test-endpoint-" + generate_random_string(force_lower=True)[:4]
address = f"opc.tcp://{generate_random_string()}:5000"
endpoint_type = generate_random_string()
anon_endpoint2 = run(
f"az iot ops asset endpoint create custom -n {anon_name2} -g {rg} --instance {instance} "
f"--ta {address} --et {endpoint_type} --ac {file_name}"
)
tracked_resources.append(anon_endpoint2["id"])
assert_endpoint_props(
result=anon_endpoint2,
name=anon_name2,
custom_location=custom_location,
target_address=address,
endpoint_type=endpoint_type,
)
assert anon_endpoint2["properties"]["additionalConfiguration"] == json_content

userpass_name = "test-endpoint-" + generate_random_string(force_lower=True)[:4]
username = generate_random_string()
password = generate_random_string()
Expand All @@ -69,7 +101,8 @@ def test_asset_endpoint_lifecycle(require_init, tracked_resources):
custom_location=custom_location,
target_address=address,
username_reference=username,
password_reference=password
password_reference=password,
endpoint_type="Microsoft.OpcUa",
)

cert_name = "test-endpoint-" + generate_random_string(force_lower=True)[:4]
Expand Down Expand Up @@ -102,6 +135,10 @@ def test_asset_endpoint_lifecycle(require_init, tracked_resources):
custom_location=custom_location,
target_address=address,
certificate_reference=cert,
endpoint_type="Microsoft.OpcUa",
)
assert_opcua_props(
result=cert_endpoint,
accept_untrusted_certs=True,
run_asset_discovery=True,
**opcua_args
Expand All @@ -120,6 +157,7 @@ def assert_endpoint_props(result, **expected):
assert result["extendedLocation"]["name"].endswith(expected["custom_location"])

result_props = result["properties"]
assert result_props["endpointProfileType"] == expected["endpoint_type"]
assert result_props["targetAddress"] == expected["target_address"]

user_auth = result_props["authentication"]
Expand All @@ -133,7 +171,6 @@ def assert_endpoint_props(result, **expected):
assert creds["usernameSecretName"] == expected["username_reference"]
else:
assert user_auth["method"] == "Anonymous"
assert_opcua_props(result, **expected)


def assert_opcua_props(result, **expected):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@
# Licensed under the MIT License. See License file in the project root for license information.
# ----------------------------------------------------------------------------------------------

from functools import partial
from typing import Dict, Optional
import json
import pytest
import responses

from azext_edge.edge.commands_asset_endpoint_profiles import (
create_custom_asset_endpoint_profile,
create_opcua_asset_endpoint_profile,
delete_asset_endpoint_profile,
list_asset_endpoint_profiles,
show_asset_endpoint_profile,
update_asset_endpoint_profile
)
from azext_edge.edge.common import AEPTypes

from .conftest import get_profile_id, get_profile_record, get_mgmt_uri
from ....generators import generate_random_string


# TODO: add in OPCUA additional config args
# TODO: add in additional config args
@pytest.mark.parametrize("req", [
{},
{
Expand All @@ -37,11 +40,13 @@
"username_reference": generate_random_string(),
}
])
@pytest.mark.parametrize("endpoint_type", AEPTypes.list() + [generate_random_string()])
def test_create(
mocked_cmd,
mocked_get_extended_location,
mocked_responses: responses,
req: Dict[str, str]
req: Dict[str, str],
endpoint_type: str
):
profile_name = generate_random_string()
target_address = generate_random_string()
Expand All @@ -59,7 +64,10 @@ def test_create(
content_type="application/json",
)

result = create_opcua_asset_endpoint_profile(
create_command = partial(create_custom_asset_endpoint_profile, endpoint_profile_type=endpoint_type)
if endpoint_type == AEPTypes.opcua.value:
create_command = create_opcua_asset_endpoint_profile
result = create_command(
cmd=mocked_cmd,
asset_endpoint_profile_name=profile_name,
target_address=target_address,
Expand All @@ -77,7 +85,7 @@ def test_create(

call_body_props = call_body["properties"]
# TODO: will change later
assert call_body_props["endpointProfileType"] == "Microsoft.OpcUa"
assert call_body_props["endpointProfileType"] == endpoint_type
assert call_body_props["targetAddress"] == target_address

auth_props = call_body_props["authentication"]
Expand All @@ -92,7 +100,8 @@ def test_create(
else:
assert auth_props["method"] == "Anonymous"

assert call_body_props["additionalConfiguration"]
if endpoint_type == AEPTypes.opcua.value:
assert call_body_props["additionalConfiguration"]


@pytest.mark.parametrize("discovered", [False]) # TODO: discovered
Expand Down

0 comments on commit 65aee3b

Please sign in to comment.