Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Incremental deployment system(s) update. Pulls together multiple refreshed components. #327

Merged
merged 7 commits into from
Aug 31, 2024
Merged
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
11 changes: 7 additions & 4 deletions azext_edge/edge/commands_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .providers.edge_api.orc import ORC_API_V1
from .providers.orchestration.common import (
KubernetesDistroType,
TrustSourceType,
# TODO MqMemoryProfile,
# TODO MqServiceType,
)
Expand Down Expand Up @@ -109,6 +110,7 @@ def init(
cmd,
cluster_name: str,
resource_group_name: str,
schema_registry_resource_id: str,
cluster_namespace: str = DEFAULT_NAMESPACE,
location: Optional[str] = None,
custom_location_name: Optional[str] = None,
Expand All @@ -123,9 +125,10 @@ def init(
dataflow_profile_instances: int = 1,
container_runtime_socket: Optional[str] = None,
kubernetes_distro: str = KubernetesDistroType.k8s.value,
trust_source: str = TrustSourceType.self_signed.value,
enable_fault_tolerance: Optional[bool] = None,
mi_user_assigned_identities: Optional[List[str]] = None,
no_progress: Optional[bool] = None,
no_block: Optional[bool] = None,
context_name: Optional[str] = None,
ensure_latest: Optional[bool] = None,
**kwargs,
Expand Down Expand Up @@ -163,7 +166,6 @@ def init(
work_manager = WorkManager(cmd)
return work_manager.execute_ops_init(
show_progress=not no_progress,
block=not no_block,
pre_flight=not no_pre_flight,
cluster_name=cluster_name,
resource_group_name=resource_group_name,
Expand All @@ -182,6 +184,9 @@ def init(
container_runtime_socket=container_runtime_socket,
kubernetes_distro=kubernetes_distro,
enable_fault_tolerance=enable_fault_tolerance,
trust_source=trust_source,
schema_registry_resource_id=schema_registry_resource_id,
mi_user_assigned_identities=mi_user_assigned_identities,
)

# TODO - @digimaun
Expand All @@ -196,8 +201,6 @@ def init(
# mq_listener_name=str(mq_listener_name),
# mq_authn_name=str(mq_authn_name),
# keyvault_resource_id=keyvault_resource_id,
# template_path=template_path,
# **kwargs,
# )


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 @@ -248,7 +248,7 @@ class BundleResourceKind(Enum):
PROTOBUF_SERVICE_API_PORT = 9800

# Dataflow constants
DEFAULT_DATAFLOW_PROFILE = "profile"
DEFAULT_DATAFLOW_PROFILE = "default"

# Init Env Control

Expand Down
45 changes: 32 additions & 13 deletions azext_edge/edge/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
KubernetesDistroType,
MqMemoryProfile,
MqServiceType,
TrustSourceType,
)


Expand Down Expand Up @@ -359,15 +360,16 @@ def load_iotops_arguments(self, _):
context.argument(
"location",
options_list=["--location"],
help="The ARM location that will be used for provisioned RPSaaS collateral. "
help="The region that will be used for provisioned resource collateral. "
"If not provided the connected cluster location will be used.",
)
context.argument(
"no_block",
options_list=["--no-block"],
arg_type=get_three_state_flag(),
help="Return immediately after the IoT Operations deployment has started.",
)
# TODO - @digimaun
# context.argument(
# "no_block",
# options_list=["--no-block"],
# arg_type=get_three_state_flag(),
# help="Return immediately after the IoT Operations deployment has started.",
# )
context.argument(
"disable_rsync_rules",
options_list=["--disable-rsync-rules"],
Expand All @@ -380,6 +382,12 @@ def load_iotops_arguments(self, _):
arg_type=get_three_state_flag(),
help="Ensure the latest IoT Ops CLI is being used, raising an error if an upgrade is available.",
)
# Schema Registry
context.argument(
"schema_registry_resource_id",
options_list=["--sr-resource-id"],
help="The schema registry resource Id to use with IoT Operations.",
)
# Akri
context.argument(
"container_runtime_socket",
Expand Down Expand Up @@ -514,12 +522,6 @@ def load_iotops_arguments(self, _):
# "--csi-config can be used one or more times.",
# arg_group="Key Vault CSI Driver",
# )
context.argument(
"template_path",
options_list=["--template-file"],
help="The path to a custom IoT Operations deployment template. Intended for advanced use cases.",
deprecate_info=context.deprecate(hide=True),
)
context.argument(
"dataflow_profile_instances",
type=int,
Expand All @@ -530,8 +532,25 @@ def load_iotops_arguments(self, _):
context.argument(
"enable_fault_tolerance",
arg_type=get_three_state_flag(),
options_list=["--enable-fault-tolerance"],
help="Enable fault tolerance for edge storage accelerator. At least 3 cluster nodes are required.",
)
context.argument(
"mi_user_assigned_identities",
nargs="*",
action="extend",
options_list=["--mi-user-assigned"],
help="Space-separated resource Ids for the desired user managed identities to associate with the instance. "
"Can be used one or more times.",
arg_group="Identity",
)
context.argument(
"trust_source",
arg_type=get_enum_type(TrustSourceType),
options_list=["--trust-source"],
help="Indicates whether a built-in self-signed or user managed trust bundle config should be used.",
arg_group="Trust",
)

with self.argument_context("iot ops delete") as context:
context.argument(
Expand Down
12 changes: 9 additions & 3 deletions azext_edge/edge/providers/orchestration/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ class MqServiceType(Enum):


class KubernetesDistroType(Enum):
k3s = "k3s"
k8s = "k8s"
microk8s = "microk8s"
k3s = "K3s"
k8s = "K8s"
microk8s = "MicroK8s"


class TrustSourceType(Enum):
self_signed = "SelfSigned"
customer_managed = "CustomerManaged"


__all__ = [
"MqMode",
"MqMemoryProfile",
"MqServiceType",
"KubernetesDistroType",
"TrustSourceType",
"DEFAULT_X509_CA_VALID_DAYS",
"KEYVAULT_DATAPLANE_API_VERSION",
"KEYVAULT_CLOUD_API_VERSION",
Expand Down
15 changes: 14 additions & 1 deletion azext_edge/edge/providers/orchestration/connected_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Licensed under the MIT License. See License file in the project root for license information.
# ----------------------------------------------------------------------------------------------

from typing import List, Optional, Union
from typing import List, Optional, Union, Dict

from ...util.resource_graph import ResourceGraph

Expand All @@ -24,6 +24,9 @@
| where properties.ExtensionType startswith 'microsoft.iotoperations'
or properties.ExtensionType =~ 'microsoft.deviceregistry.assets'
or properties.ExtensionType =~ 'microsoft.azurekeyvaultsecretsprovider'
or properties.ExtensionType =~ 'microsoft.secretsynccontroller'
or properties.ExtensionType =~ 'microsoft.openservicemesh'
or properties.ExtensionType =~ 'microsoft.edgestorageaccelerator'
| project id, name, apiVersion
""",
"get_aio_custom_locations": """
Expand Down Expand Up @@ -95,6 +98,16 @@ def extensions(self) -> List[dict]:
self.clusters.extensions.list(resource_group_name=self.resource_group_name, cluster_name=self.cluster_name)
)

def get_extensions_by_type(self, *type_names: str) -> Optional[Dict[str, dict]]:
extensions = self.extensions
desired_extension_map = {name.lower(): None for name in type_names}
for extension in extensions:
extension_type = extension["properties"].get("extensionType", "").lower()
if extension_type in desired_extension_map:
desired_extension_map[extension_type] = extension

return desired_extension_map

def get_custom_location_for_namespace(self, namespace: str) -> Optional[dict]:
query = QUERIES["get_custom_location_for_namespace"].format(resource_id=self.resource_id, namespace=namespace)

Expand Down
27 changes: 14 additions & 13 deletions azext_edge/edge/providers/orchestration/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@
["*", "*/write", "microsoft.authorization/roleassignments/write", "microsoft.authorization/*/write"]
)

ROLE_DEF_FORMAT_STR = "/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/{role_id}"


# TODO: one-off for time, make generic
def verify_write_permission_against_rg(subscription_id: str, resource_group_name: str):
for permission in get_principal_permissions_for_group(
subscription_id=subscription_id, resource_group_name=resource_group_name
):
permission_dict = permission.as_dict()
action_result = False
negate_action_result = False

for action in permission_dict.get("actions", []):
for action in permission.get("actions", []):
if action.lower() in VALID_PERM_FORMS:
action_result = True
break

for not_action in permission_dict.get("not_actions", []):
for not_action in permission.get("notActions", []):
if not_action.lower() in VALID_PERM_FORMS:
negate_action_result = True
break
Expand All @@ -50,7 +51,7 @@ def verify_write_permission_against_rg(subscription_id: str, resource_group_name
)


def get_principal_permissions_for_group(subscription_id: str, resource_group_name: str) -> Iterable:
def get_principal_permissions_for_group(subscription_id: str, resource_group_name: str) -> Iterable[dict]:
authz_client = get_authz_client(subscription_id=subscription_id)
return authz_client.permissions.list_for_resource_group(resource_group_name)

Expand All @@ -72,16 +73,17 @@ def apply_role_assignment(self, scope: str, principal_id: str, role_def_id: str)
scope=scope, filter=f"principalId eq '{principal_id}'"
)
for role_assignment in role_assignments_iter:
role_assignment_dict = role_assignment.as_dict()
if role_assignment_dict["role_definition_id"] == role_def_id:
if role_assignment["properties"]["roleDefinitionId"] == role_def_id:
return

return self.authz_client.role_assignments.create(
scope=scope,
role_assignment_name=str(uuid4()),
parameters={
"role_definition_id": role_def_id,
"principal_id": principal_id,
"properties": {
"roleDefinitionId": role_def_id,
"principalId": principal_id,
}
},
)

Expand All @@ -102,8 +104,7 @@ def can_apply_role_assignment(
)
action_allowed = None
for permission in permissions:
permission_dict = permission.as_dict()
action_result = self._calculate_action(permission_dict=permission_dict, valid_permissions=VALID_PERM_FORMS)
action_result = self._calculate_action(permission=permission, valid_permissions=VALID_PERM_FORMS)

if action_result == PermissionState.ActionAllowed and action_allowed is not False:
action_allowed = True
Expand All @@ -128,16 +129,16 @@ def _get_principal_permissions_for_resource(
resource_name=resource_name,
)

def _calculate_action(self, permission_dict: dict, valid_permissions: frozenset) -> PermissionState:
def _calculate_action(self, permission: dict, valid_permissions: frozenset) -> PermissionState:
action_result = False
negate_action_result = False

for action in permission_dict.get("actions", []):
for action in permission.get("actions", []):
if action.lower() in valid_permissions:
action_result = True
break

for not_action in permission_dict.get("not_actions", []):
for not_action in permission.get("notActions", []):
if not_action.lower() in valid_permissions:
negate_action_result = True
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@
from rich.console import Console

from ....util.az_client import (
get_authz_client,
get_registry_mgmt_client,
get_storage_mgmt_client,
parse_resource_id,
wait_for_terminal_state,
)
from ....util.common import should_continue_prompt
from ....util.queryable import Queryable
from ..permissions import PermissionManager
from ..permissions import PermissionManager, ROLE_DEF_FORMAT_STR

logger = get_logger(__name__)

Expand All @@ -33,7 +32,6 @@
)

STORAGE_BLOB_DATA_CONTRIBUTOR_ROLE_ID = "ba92f5b4-2d11-453d-a403-e96b0029c9fe"
ROLE_DEF_FORMAT_STR = "/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/{role_id}"


def get_user_msg_warn_ra(prefix: str, principal_id: str, scope: str):
Expand All @@ -52,9 +50,6 @@ def __init__(self, cmd):
self.registry_mgmt_client = get_registry_mgmt_client(
subscription_id=self.default_subscription_id,
)
self.authz_client = get_authz_client(
subscription_id=self.default_subscription_id,
)
self.ops: "SchemaRegistriesOperations" = self.registry_mgmt_client.schema_registries

def create(
Expand Down
Loading