From c172f412bf91516a726f1ec74ff8ee3ca5a9b534 Mon Sep 17 00:00:00 2001 From: Jamie Parsons Date: Fri, 22 Sep 2023 19:10:10 +0100 Subject: [PATCH 1/3] Helptext + a little bit of validation --- src/aosm/azext_aosm/_configuration.py | 273 ++++++++++++------ src/aosm/azext_aosm/custom.py | 25 +- .../tests/latest/mock_nsd/input.json | 2 +- .../latest/mock_nsd/input_multi_nf_nsd.json | 2 +- .../mock_nsd/input_multiple_instances.json | 2 +- 5 files changed, 200 insertions(+), 104 deletions(-) diff --git a/src/aosm/azext_aosm/_configuration.py b/src/aosm/azext_aosm/_configuration.py index cb3a5174ff2..9efb9cd73cd 100644 --- a/src/aosm/azext_aosm/_configuration.py +++ b/src/aosm/azext_aosm/_configuration.py @@ -7,7 +7,7 @@ import logging import json import os -from dataclasses import dataclass, field +from dataclasses import dataclass, field, asdict from pathlib import Path from typing import Any, Dict, List, Optional, Union @@ -110,21 +110,58 @@ class ArtifactConfig: # artifact.py checks for the presence of the default descriptions, change # there if you change the descriptions. - artifact_name: str = DESCRIPTION_MAP["artifact_name"] - file_path: Optional[str] = DESCRIPTION_MAP["file_path"] - blob_sas_url: Optional[str] = DESCRIPTION_MAP["blob_sas_url"] - version: Optional[str] = DESCRIPTION_MAP["artifact_version"] + artifact_name: str = "" + file_path: Optional[str] = "" + blob_sas_url: Optional[str] = "" + version: Optional[str] = "" + + @classmethod + def helptext(cls) -> "ArtifactConfig": + """ + Build an object where each value is helptext for that field. + """ + return ArtifactConfig( + artifact_name=DESCRIPTION_MAP["artifact_name"], + file_path=DESCRIPTION_MAP["file_path"], + blob_sas_url=DESCRIPTION_MAP["blob_sas_url"], + version=DESCRIPTION_MAP["version"], + ) @dataclass class Configuration(abc.ABC): config_file: Optional[str] = None - publisher_name: str = DESCRIPTION_MAP["publisher_name"] - publisher_resource_group_name: str = DESCRIPTION_MAP[ - "publisher_resource_group_name" - ] - acr_artifact_store_name: str = DESCRIPTION_MAP["acr_artifact_store_name"] - location: str = DESCRIPTION_MAP["location"] + publisher_name: str = "" + publisher_resource_group_name: str = "" + acr_artifact_store_name: str = "" + location: str = "" + + @classmethod + def helptext(cls): + """ + Build an object where each value is helptext for that field. + """ + return Configuration( + publisher_name=DESCRIPTION_MAP["publisher_name"], + publisher_resource_group_name=DESCRIPTION_MAP[ + "publisher_resource_group_name" + ], + acr_artifact_store_name=DESCRIPTION_MAP["acr_artifact_store_name"], + location=DESCRIPTION_MAP["location"], + ) + + def validate(self): + """ + Validate the configuration. + """ + if not self.location: + raise ValidationError("Location must be set") + if not self.publisher_name: + raise ValidationError("Publisher name must be set") + if not self.publisher_resource_group_name: + raise ValidationError("Publisher resource group name must be set") + if not self.acr_artifact_store_name: + raise ValidationError("ACR Artifact Store name must be set") def path_from_cli_dir(self, path: str) -> str: """ @@ -169,14 +206,30 @@ def acr_manifest_names(self) -> List[str]: class NFConfiguration(Configuration): """Network Function configuration.""" - publisher_name: str = DESCRIPTION_MAP["publisher_name"] - publisher_resource_group_name: str = DESCRIPTION_MAP[ - "publisher_resource_group_name" - ] - nf_name: str = DESCRIPTION_MAP["nf_name"] - version: str = DESCRIPTION_MAP["version"] - acr_artifact_store_name: str = DESCRIPTION_MAP["acr_artifact_store_name"] - location: str = DESCRIPTION_MAP["location"] + nf_name: str = "" + version: str = "" + + @classmethod + def helptext(cls) -> "NFConfiguration": + """ + Build an object where each value is helptext for that field. + """ + return NFConfiguration( + nf_name=DESCRIPTION_MAP["nf_name"], + version=DESCRIPTION_MAP["version"], + **asdict(Configuration.helptext()), + ) + + def validate(self): + """ + Validate the configuration. + """ + super().validate() + if not self.nf_name: + raise ValidationError("nf_name must be set") + if not self.version: + raise ValidationError("version must be set") + @property def nfdg_name(self) -> str: @@ -197,11 +250,24 @@ def acr_manifest_names(self) -> List[str]: @dataclass class VNFConfiguration(NFConfiguration): - blob_artifact_store_name: str = DESCRIPTION_MAP["blob_artifact_store_name"] - image_name_parameter: str = DESCRIPTION_MAP["image_name_parameter"] + blob_artifact_store_name: str = "" + image_name_parameter: str = "" arm_template: Any = ArtifactConfig() vhd: Any = ArtifactConfig() + @classmethod + def helptext(cls) -> "VNFConfiguration": + """ + Build an object where each value is helptext for that field. + """ + return VNFConfiguration( + blob_artifact_store_name=DESCRIPTION_MAP["blob_artifact_store_name"], + image_name_parameter=DESCRIPTION_MAP["image_name_parameter"], + arm_template=ArtifactConfig.helptext(), + vhd=ArtifactConfig.helptext(), + **asdict(NFConfiguration.helptext()), + ) + def __post_init__(self): """ Cope with deserializing subclasses from dicts to ArtifactConfig. @@ -241,13 +307,10 @@ def validate(self) -> None: "Config validation error. ARM template artifact version should be in" " format A.B.C" ) - filepath_set = ( - self.vhd.file_path and self.vhd.file_path != DESCRIPTION_MAP["file_path"] - ) - sas_set = ( - self.vhd.blob_sas_url - and self.vhd.blob_sas_url != DESCRIPTION_MAP["blob_sas_url"] - ) + filepath_set = bool(self.vhd.file_path) + sas_set = bool(self.vhd.blob_sas_url) + print(asdict(self.vhd)) + # If these are the same, either neither is set or both are, both of which are errors if filepath_set == sas_set: raise ValidationError( @@ -284,6 +347,18 @@ class HelmPackageConfig: default_factory=lambda: [DESCRIPTION_MAP["helm_depends_on"]] ) + @classmethod + def helptext(cls): + """ + Build an object where each value is helptext for that field. + """ + return HelmPackageConfig( + name=DESCRIPTION_MAP["helm_package_name"], + path_to_chart=DESCRIPTION_MAP["path_to_chart"], + path_to_mappings=DESCRIPTION_MAP["path_to_mappings"], + depends_on=[DESCRIPTION_MAP["helm_depends_on"]], + ) + @dataclass class CNFImageConfig: @@ -293,24 +368,16 @@ class CNFImageConfig: source_registry_namespace: str = "" source_local_docker_image: str = "" - def __post_init__(self): + @classmethod + def helptext(cls) -> "CNFImageConfig": """ - Cope with optional parameters being omitted in the loaded json config file. - - If param is set to placeholder text, it is not in the config file and should be unset. + Build an object where each value is helptext for that field. """ - if self.source_registry == DESCRIPTION_MAP["source_registry"]: - self.source_registry = "" - if ( - self.source_registry_namespace - == DESCRIPTION_MAP["source_registry_namespace"] - ): - self.source_registry_namespace = "" - if ( - self.source_local_docker_image - == DESCRIPTION_MAP["source_local_docker_image"] - ): - self.source_local_docker_image = "" + return CNFImageConfig( + source_registry=DESCRIPTION_MAP["source_registry"], + source_registry_namespace=DESCRIPTION_MAP["source_registry_namespace"], + source_local_docker_image=DESCRIPTION_MAP["source_local_docker_image"], + ) @dataclass @@ -337,6 +404,17 @@ def __post_init__(self): self.images = CNFImageConfig(**self.images) self.validate() + @classmethod + def helptext(cls) -> "CNFConfiguration": + """ + Build an object where each value is helptext for that field. + """ + return CNFConfiguration( + images=CNFImageConfig.helptext(), + helm_packages=[HelmPackageConfig.helptext()], + ** asdict(NFConfiguration.helptext()), + ) + @property def output_directory_for_build(self) -> Path: """Return the directory the build command will writes its output to.""" @@ -390,14 +468,30 @@ def validate(self): class NFDRETConfiguration: # pylint: disable=too-many-instance-attributes """The configuration required for an NFDV that you want to include in an NSDV.""" - publisher: str = PUBLISHER_NAME - publisher_resource_group: str = PUBLISHER_RESOURCE_GROUP - name: str = NFD_NAME - version: str = NFD_VERSION - publisher_offering_location: str = NFD_LOCATION - publisher_scope: str = PUBLISHER_SCOPE - type: str = NFD_TYPE - multiple_instances: Union[str, bool] = MULTIPLE_INSTANCES + publisher: str = "" + publisher_resource_group: str = "" + name: str = "" + version: str = "" + publisher_offering_location: str = "" + publisher_scope: str = "" + type: str = "" + multiple_instances: Union[str, bool] = False + + @classmethod + def helptext(cls) -> "NFDRETConfiguration": + """ + Build an object where each value is helptext for that field. + """ + return NFDRETConfiguration( + publisher=PUBLISHER_NAME, + publisher_resource_group=PUBLISHER_RESOURCE_GROUP, + name=NFD_NAME, + version=NFD_VERSION, + publisher_offering_location=NFD_LOCATION, + publisher_scope=PUBLISHER_SCOPE, + type=NFD_TYPE, + multiple_instances=MULTIPLE_INSTANCES, + ) def validate(self) -> None: """ @@ -405,28 +499,28 @@ def validate(self) -> None: :raises ValidationError for any invalid config """ - if self.name == NFD_NAME: + if not self.name: raise ValidationError("Network function definition name must be set") - if self.publisher == PUBLISHER_NAME: + if not self.publisher: raise ValidationError(f"Publisher name must be set for {self.name}") - if self.publisher_resource_group == PUBLISHER_RESOURCE_GROUP: + if not self.publisher_resource_group: raise ValidationError( f"Publisher resource group name must be set for {self.name}" ) - if self.version == NFD_VERSION: + if not self.version: raise ValidationError( f"Network function definition version must be set for {self.name}" ) - if self.publisher_offering_location == NFD_LOCATION: + if not self.publisher_offering_location: raise ValidationError( f"Network function definition offering location must be set, for {self.name}" ) - if self.publisher_scope == PUBLISHER_SCOPE: + if not self.publisher_scope: raise ValidationError( f"Network function definition publisher scope must be set, for {self.name}" ) @@ -498,9 +592,9 @@ class NSConfiguration(Configuration): NFDRETConfiguration(), ] ) - nsd_name: str = DESCRIPTION_MAP["nsd_name"] - nsd_version: str = DESCRIPTION_MAP["nsd_version"] - nsdv_description: str = DESCRIPTION_MAP["nsdv_description"] + nsd_name: str = "" + nsd_version: str = "" + nsdv_description: str = "" def __post_init__(self): """Covert things to the correct format.""" @@ -510,35 +604,35 @@ def __post_init__(self): ] self.network_functions = nf_ret_list + @classmethod + def helptext(cls) -> "NSConfiguration": + """ + Build a NSConfiguration object where each value is helptext for that field. + """ + nsd_helptext = NSConfiguration( + nsd_name=DESCRIPTION_MAP["nsd_name"], + nsd_version=DESCRIPTION_MAP["nsd_version"], + nsdv_description=DESCRIPTION_MAP["nsdv_description"], + **asdict(Configuration.helptext()) + ) + nsd_helptext.network_functions = [NFDRETConfiguration.helptext()] + + return nsd_helptext + def validate(self): """ Validate the configuration passed in. :raises ValueError for any invalid config """ - - if self.location in (DESCRIPTION_MAP["location"], ""): - raise ValueError("Location must be set") - if self.publisher_name in (DESCRIPTION_MAP["publisher_name"], ""): - raise ValueError("Publisher name must be set") - if self.publisher_resource_group_name in ( - DESCRIPTION_MAP["publisher_resource_group_name_nsd"], - "", - ): - raise ValueError("Publisher resource group name must be set") - if self.acr_artifact_store_name in ( - DESCRIPTION_MAP["acr_artifact_store_name"], - "", - ): - raise ValueError("ACR Artifact Store name must be set") if self.network_functions in ([], None): raise ValueError(("At least one network function must be included.")) for configuration in self.network_functions: configuration.validate() - if self.nsd_name in (DESCRIPTION_MAP["nsd_name"], ""): + if not self.nsd_name: raise ValueError("NSD name must be set") - if self.nsd_version in (DESCRIPTION_MAP["nsd_version"], ""): + if not self.nsd_version: raise ValueError("NSD Version must be set") @property @@ -563,9 +657,7 @@ def acr_manifest_names(self) -> List[str]: return [nf.acr_manifest_name(self.nsd_version) for nf in self.network_functions] -def get_configuration( - configuration_type: str, config_file: Optional[str] = None -) -> Configuration: +def get_configuration(configuration_type: str, config_file: str) -> Configuration: """ Return the correct configuration object based on the type. @@ -573,16 +665,13 @@ def get_configuration( :param config_file: The path to the config file :return: The configuration object """ - if config_file: - try: - with open(config_file, "r", encoding="utf-8") as f: - config_as_dict = json.loads(f.read()) - except json.decoder.JSONDecodeError as e: - raise InvalidArgumentValueError( - f"Config file {config_file} is not valid JSON: {e}" - ) from e - else: - config_as_dict = {} + try: + with open(config_file, "r", encoding="utf-8") as f: + config_as_dict = json.loads(f.read()) + except json.decoder.JSONDecodeError as e: + raise InvalidArgumentValueError( + f"Config file {config_file} is not valid JSON: {e}" + ) from e config: Configuration try: @@ -600,5 +689,7 @@ def get_configuration( raise InvalidArgumentValueError( f"Config file {config_file} is not valid: {typeerr}" ) from typeerr + + config.validate() return config diff --git a/src/aosm/azext_aosm/custom.py b/src/aosm/azext_aosm/custom.py index 736da37c73b..dee5efb2e2b 100644 --- a/src/aosm/azext_aosm/custom.py +++ b/src/aosm/azext_aosm/custom.py @@ -89,9 +89,14 @@ def generate_definition_config(definition_type: str, output_file: str = "input.j :param definition_type: CNF, VNF :param output_file: path to output config file, defaults to "input.json" - :type output_file: str, optional """ - _generate_config(configuration_type=definition_type, output_file=output_file) + config: Configuration + if definition_type == CNF: + config = CNFConfiguration.helptext() + elif definition_type == VNF: + config = VNFConfiguration.helptext() + + _generate_config(configuration=config, output_file=output_file) def _get_config_from_file(config_file: str, configuration_type: str) -> Configuration: @@ -324,21 +329,21 @@ def generate_design_config(output_file: str = "input.json"): :param output_file: path to output config file, defaults to "input.json" :type output_file: str, optional """ - _generate_config(NSD, output_file) + _generate_config(NSConfiguration.helptext(), output_file) -def _generate_config(configuration_type: str, output_file: str = "input.json"): +def _generate_config(configuration: Configuration, output_file: str = "input.json"): """ Generic generate config function for NFDs and NSDs. - :param configuration_type: CNF, VNF or NSD + :param configuration: The Configuration object with helptext filled in for each of + the fields. :param output_file: path to output config file, defaults to "input.json" - :type output_file: str, optional """ # Config file is a special parameter on the configuration objects. It is the path # to the configuration file, rather than an input parameter. It therefore shouldn't # be included here. - config = asdict(get_configuration(configuration_type)) + config = asdict(configuration) config.pop("config_file") config_as_dict = json.dumps(config, indent=4) @@ -353,10 +358,10 @@ def _generate_config(configuration_type: str, output_file: str = "input.json"): with open(output_file, "w", encoding="utf-8") as f: f.write(config_as_dict) - if configuration_type in (CNF, VNF): - prtName = "definition" - else: + if isinstance(configuration, NSConfiguration): prtName = "design" + else: + prtName = "definition" print(f"Empty {prtName} configuration has been written to {output_file}") logger.info( "Empty %s configuration has been written to %s", prtName, output_file diff --git a/src/aosm/azext_aosm/tests/latest/mock_nsd/input.json b/src/aosm/azext_aosm/tests/latest/mock_nsd/input.json index 94d92fe0334..78b00cf08e5 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nsd/input.json @@ -15,7 +15,7 @@ "publisher_scope": "private" } ], - "nsdg_name": "ubuntu", + "nsd_name": "ubuntu", "nsd_version": "1.0.0", "nsdv_description": "Plain ubuntu VM" } \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json index 8ef686b99ec..970c8c16b2a 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json @@ -25,7 +25,7 @@ "multiple_instances": false } ], - "nsdg_name": "multinf", + "nsd_name": "multinf", "nsd_version": "1.0.1", "nsdv_description": "Test deploying multiple NFs" } diff --git a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json index 0f2a55de152..1775c42a053 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json @@ -15,7 +15,7 @@ "publisher_resource_group": "Jamie-publisher" } ], - "nsdg_name": "ubuntu", + "nsd_name": "ubuntu", "nsd_version": "1.0.0", "nsdv_description": "Plain ubuntu VM" } \ No newline at end of file From 3ef7042b0a05939c01bc8e2946458bc7f59579ef Mon Sep 17 00:00:00 2001 From: Jamie Parsons Date: Mon, 25 Sep 2023 11:52:23 +0100 Subject: [PATCH 2/3] Fixed up validation --- src/aosm/azext_aosm/_configuration.py | 351 ++++++++---------- .../cnf_nsd_input_template.json | 2 +- .../vnf_nsd_input_template.json | 2 +- 3 files changed, 167 insertions(+), 188 deletions(-) diff --git a/src/aosm/azext_aosm/_configuration.py b/src/aosm/azext_aosm/_configuration.py index 9efb9cd73cd..2ffc950d1ed 100644 --- a/src/aosm/azext_aosm/_configuration.py +++ b/src/aosm/azext_aosm/_configuration.py @@ -9,7 +9,7 @@ import os from dataclasses import dataclass, field, asdict from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, List, Optional, Union from azure.cli.core.azclierror import InvalidArgumentValueError, ValidationError from azext_aosm.util.constants import ( @@ -23,88 +23,6 @@ logger = logging.getLogger(__name__) -DESCRIPTION_MAP: Dict[str, str] = { - "publisher_resource_group_name": ( - "Resource group for the Publisher resource. " - "Will be created if it does not exist." - ), - "publisher_name": ( - "Name of the Publisher resource you want your definition published to. " - "Will be created if it does not exist." - ), - "publisher_resource_group_name_nsd": "Resource group for the Publisher resource.", - "nf_name": "Name of NF definition", - "version": "Version of the NF definition in A.B.C format.", - "acr_artifact_store_name": ( - "Name of the ACR Artifact Store resource. Will be created if it does not exist." - ), - "location": "Azure location to use when creating resources.", - "blob_artifact_store_name": ( - "Name of the storage account Artifact Store resource. Will be created if it " - "does not exist." - ), - "artifact_name": "Name of the artifact", - "file_path": ( - "Optional. File path of the artifact you wish to upload from your local disk. " - "Delete if not required. Relative paths are relative to the configuration file." - "On Windows escape any backslash with another backslash." - ), - "blob_sas_url": ( - "Optional. SAS URL of the blob artifact you wish to copy to your Artifact" - " Store. Delete if not required." - ), - "artifact_version": ( - "Version of the artifact. For VHDs this must be in format A-B-C. " - "For ARM templates this must be in format A.B.C" - ), - "nsdv_description": "Description of the NSDV", - "nsd_name": ( - "Network Service Design (NSD) name. This is the collection of Network Service" - " Design Versions. Will be created if it does not exist." - ), - "nsd_version": ( - "Version of the NSD to be created. This should be in the format A.B.C" - ), - "helm_package_name": "Name of the Helm package", - "path_to_chart": ( - "File path of Helm Chart on local disk. Accepts .tgz, .tar or .tar.gz." - " Use Linux slash (/) file separator even if running on Windows." - ), - "path_to_mappings": ( - "File path of value mappings on local disk where chosen values are replaced " - "with deploymentParameter placeholders. Accepts .yaml or .yml. If left as a " - "blank string, a value mappings file will be generated with every value " - "mapped to a deployment parameter. Use a blank string and --interactive on " - "the build command to interactively choose which values to map." - ), - "helm_depends_on": ( - "Names of the Helm packages this package depends on. " - "Leave as an empty array if no dependencies" - ), - "image_name_parameter": ( - "The parameter name in the VM ARM template which specifies the name of the " - "image to use for the VM." - ), - "source_registry": ( - "Optional. Login server of the source acr registry from which to pull the " - "image(s). For example sourceacr.azurecr.io. Leave blank if you have set " - "source_local_docker_image." - ), - "source_local_docker_image": ( - "Optional. Image name of the source docker image from local machine. For " - "limited use case where the CNF only requires a single docker image and exists " - "in the local docker repository. Set to blank of not required." - ), - "source_registry_namespace": ( - "Optional. Namespace of the repository of the source acr registry from which " - "to pull. For example if your repository is samples/prod/nginx then set this to" - " samples/prod . Leave blank if the image is in the root namespace or you have " - "set source_local_docker_image." - "See https://learn.microsoft.com/en-us/azure/container-registry/" - "container-registry-best-practices#repository-namespaces for further details." - ), -} - @dataclass class ArtifactConfig: @@ -121,12 +39,39 @@ def helptext(cls) -> "ArtifactConfig": Build an object where each value is helptext for that field. """ return ArtifactConfig( - artifact_name=DESCRIPTION_MAP["artifact_name"], - file_path=DESCRIPTION_MAP["file_path"], - blob_sas_url=DESCRIPTION_MAP["blob_sas_url"], - version=DESCRIPTION_MAP["version"], + artifact_name="Optional. Name of the artifact.", + file_path=( + "Optional. File path of the artifact you wish to upload from your local disk. " + "Delete if not required. Relative paths are relative to the configuration file." + "On Windows escape any backslash with another backslash." + ), + blob_sas_url=( + "Optional. SAS URL of the blob artifact you wish to copy to your Artifact" + " Store. Delete if not required." + ), + version="Version of the artifact in A.B.C format.", ) + def __post_init__(self): + """ + Change empty stings to None. + """ + if not self.file_path: + self.file_path = None + if not self.blob_sas_url: + self.blob_sas_url = None + + def validate(self): + """ + Validate the configuration. + """ + if not self.version: + raise ValidationError("version must be set.") + if self.blob_sas_url and self.file_path: + raise ValidationError("Only one of file_path or blob_sas_url may be set.") + if not (self.blob_sas_url or self.file_path): + raise ValidationError("One of file_path or sas_blob_url must be set.") + @dataclass class Configuration(abc.ABC): @@ -142,14 +87,20 @@ def helptext(cls): Build an object where each value is helptext for that field. """ return Configuration( - publisher_name=DESCRIPTION_MAP["publisher_name"], - publisher_resource_group_name=DESCRIPTION_MAP[ - "publisher_resource_group_name" - ], - acr_artifact_store_name=DESCRIPTION_MAP["acr_artifact_store_name"], - location=DESCRIPTION_MAP["location"], + publisher_name=( + "Name of the Publisher resource you want your definition published to. " + "Will be created if it does not exist." + ), + publisher_resource_group_name=( + "Resource group for the Publisher resource. " + "Will be created if it does not exist." + ), + acr_artifact_store_name=( + "Name of the ACR Artifact Store resource. Will be created if it does not exist." + ), + location="Azure location to use when creating resources.", ) - + def validate(self): """ Validate the configuration. @@ -215,11 +166,11 @@ def helptext(cls) -> "NFConfiguration": Build an object where each value is helptext for that field. """ return NFConfiguration( - nf_name=DESCRIPTION_MAP["nf_name"], - version=DESCRIPTION_MAP["version"], + nf_name="Name of NF definition", + version="Version of the NF definition in A.B.C format.", **asdict(Configuration.helptext()), ) - + def validate(self): """ Validate the configuration. @@ -229,7 +180,6 @@ def validate(self): raise ValidationError("nf_name must be set") if not self.version: raise ValidationError("version must be set") - @property def nfdg_name(self) -> str: @@ -261,8 +211,14 @@ def helptext(cls) -> "VNFConfiguration": Build an object where each value is helptext for that field. """ return VNFConfiguration( - blob_artifact_store_name=DESCRIPTION_MAP["blob_artifact_store_name"], - image_name_parameter=DESCRIPTION_MAP["image_name_parameter"], + blob_artifact_store_name=( + "Name of the storage account Artifact Store resource. Will be created if it " + "does not exist." + ), + image_name_parameter=( + "The parameter name in the VM ARM template which specifies the name of the " + "image to use for the VM." + ), arm_template=ArtifactConfig.helptext(), vhd=ArtifactConfig.helptext(), **asdict(NFConfiguration.helptext()), @@ -284,7 +240,6 @@ def __post_init__(self): if self.vhd.get("file_path"): self.vhd["file_path"] = self.path_from_cli_dir(self.vhd["file_path"]) self.vhd = ArtifactConfig(**self.vhd) - self.validate() def validate(self) -> None: """ @@ -292,10 +247,10 @@ def validate(self) -> None: :raises ValidationError for any invalid config """ + super().validate() - if self.vhd.version == DESCRIPTION_MAP["version"]: - # Config has not been filled in. Don't validate. - return + self.vhd.validate() + self.arm_template.validate() if "." in self.vhd.version or "-" not in self.vhd.version: raise ValidationError( @@ -307,23 +262,6 @@ def validate(self) -> None: "Config validation error. ARM template artifact version should be in" " format A.B.C" ) - filepath_set = bool(self.vhd.file_path) - sas_set = bool(self.vhd.blob_sas_url) - print(asdict(self.vhd)) - - # If these are the same, either neither is set or both are, both of which are errors - if filepath_set == sas_set: - raise ValidationError( - "Config validation error. VHD config must have either a local filepath" - " or a blob SAS URL" - ) - - if filepath_set: - # Explicitly set the blob SAS URL to None to avoid other code having to - # check if the value is the default description - self.vhd.blob_sas_url = None - elif sas_set: - self.vhd.file_path = None @property def sa_manifest_name(self) -> str: @@ -340,12 +278,10 @@ def output_directory_for_build(self) -> Path: @dataclass class HelmPackageConfig: - name: str = DESCRIPTION_MAP["helm_package_name"] - path_to_chart: str = DESCRIPTION_MAP["path_to_chart"] - path_to_mappings: str = DESCRIPTION_MAP["path_to_mappings"] - depends_on: List[str] = field( - default_factory=lambda: [DESCRIPTION_MAP["helm_depends_on"]] - ) + name: str = "" + path_to_chart: str = "" + path_to_mappings: str = "" + depends_on: List[str] = field(default_factory=lambda: []) @classmethod def helptext(cls): @@ -353,12 +289,33 @@ def helptext(cls): Build an object where each value is helptext for that field. """ return HelmPackageConfig( - name=DESCRIPTION_MAP["helm_package_name"], - path_to_chart=DESCRIPTION_MAP["path_to_chart"], - path_to_mappings=DESCRIPTION_MAP["path_to_mappings"], - depends_on=[DESCRIPTION_MAP["helm_depends_on"]], + name="Name of the Helm package", + path_to_chart=( + "File path of Helm Chart on local disk. Accepts .tgz, .tar or .tar.gz." + " Use Linux slash (/) file separator even if running on Windows." + ), + path_to_mappings=( + "File path of value mappings on local disk where chosen values are replaced " + "with deploymentParameter placeholders. Accepts .yaml or .yml. If left as a " + "blank string, a value mappings file will be generated with every value " + "mapped to a deployment parameter. Use a blank string and --interactive on " + "the build command to interactively choose which values to map." + ), + depends_on=( + "Names of the Helm packages this package depends on. " + "Leave as an empty array if no dependencies" + ), ) + def validate(self): + """ + Validate the configuration. + """ + if not self.name: + raise ValidationError("name must be set") + if not self.path_to_chart: + raise ValidationError("path_to_chart must be set") + @dataclass class CNFImageConfig: @@ -374,16 +331,51 @@ def helptext(cls) -> "CNFImageConfig": Build an object where each value is helptext for that field. """ return CNFImageConfig( - source_registry=DESCRIPTION_MAP["source_registry"], - source_registry_namespace=DESCRIPTION_MAP["source_registry_namespace"], - source_local_docker_image=DESCRIPTION_MAP["source_local_docker_image"], + source_registry=( + "Optional. Login server of the source acr registry from which to pull the " + "image(s). For example sourceacr.azurecr.io. Leave blank if you have set " + "source_local_docker_image." + ), + source_registry_namespace=( + "Optional. Namespace of the repository of the source acr registry from which " + "to pull. For example if your repository is samples/prod/nginx then set this to" + " samples/prod . Leave blank if the image is in the root namespace or you have " + "set source_local_docker_image." + "See https://learn.microsoft.com/en-us/azure/container-registry/" + "container-registry-best-practices#repository-namespaces for further details." + ), + source_local_docker_image=( + "Optional. Image name of the source docker image from local machine. For " + "limited use case where the CNF only requires a single docker image and exists " + "in the local docker repository. Set to blank of not required." + ), ) + def validate(self): + """ + Validate the configuration. + """ + if self.source_registry_namespace and not self.source_registry: + raise ValidationError( + "Config validation error. The image source registry namespace should " + "only be configured if a source registry is configured." + ) + + if self.source_registry and self.source_local_docker_image: + raise ValidationError( + "Only one of source_registry and source_local_docker_image can be set." + ) + + if not (self.source_registry or self.source_local_docker_image): + raise ValidationError( + "One of source_registry or source_local_docker_image must be set." + ) + @dataclass class CNFConfiguration(NFConfiguration): images: Any = CNFImageConfig() - helm_packages: List[Any] = field(default_factory=lambda: [HelmPackageConfig()]) + helm_packages: List[Any] = field(default_factory=lambda: []) def __post_init__(self): """ @@ -402,7 +394,6 @@ def __post_init__(self): self.helm_packages[package_index] = HelmPackageConfig(**dict(package)) if isinstance(self.images, dict): self.images = CNFImageConfig(**self.images) - self.validate() @classmethod def helptext(cls) -> "CNFConfiguration": @@ -412,7 +403,7 @@ def helptext(cls) -> "CNFConfiguration": return CNFConfiguration( images=CNFImageConfig.helptext(), helm_packages=[HelmPackageConfig.helptext()], - ** asdict(NFConfiguration.helptext()), + **asdict(NFConfiguration.helptext()), ) @property @@ -426,42 +417,14 @@ def validate(self): :raises ValidationError: If source registry ID doesn't match the regex """ + assert isinstance(self.images, CNFImageConfig) + super().validate() - source_reg_set = self.images.source_registry != "" - source_local_set = self.images.source_local_docker_image != "" - source_reg_namespace_set = self.images.source_registry_namespace != "" - - if source_reg_namespace_set and not source_reg_set: - raise ValidationError( - "Config validation error. The image source registry namespace should " - "only be configured if a source registry is configured." - ) - # If these are the same, either neither is set or both are, both of which are errors - if source_reg_set == source_local_set: - raise ValidationError( - "Config validation error. Images config must have either a local docker image" - " or a source registry, but not both." - ) - + self.images.validate() -NFD_NAME = "The name of the existing Network Function Definition Group to deploy using this NSD" -NFD_VERSION = ( - "The version of the existing Network Function Definition to base this NSD on. " - "This NSD will be able to deploy any NFDV with deployment parameters compatible " - "with this version." -) -NFD_LOCATION = "The region that the NFDV is published to." -PUBLISHER_RESOURCE_GROUP = "The resource group that the publisher is hosted in." -PUBLISHER_NAME = "The name of the publisher that this NFDV is published under." -PUBLISHER_SCOPE = ( - "The scope that the publisher is published under. Only 'private' is supported." -) -NFD_TYPE = "Type of Network Function. Valid values are 'cnf' or 'vnf'" -MULTIPLE_INSTANCES = ( - "Set to true or false. Whether the NSD should allow arbitrary numbers of this " - "type of NF. If set to false only a single instance will be allowed. Only " - "supported on VNFs, must be set to false on CNFs." -) + for helm_package in self.helm_packages: + assert isinstance(helm_package, HelmPackageConfig) + helm_package.validate() @dataclass @@ -483,14 +446,24 @@ def helptext(cls) -> "NFDRETConfiguration": Build an object where each value is helptext for that field. """ return NFDRETConfiguration( - publisher=PUBLISHER_NAME, - publisher_resource_group=PUBLISHER_RESOURCE_GROUP, - name=NFD_NAME, - version=NFD_VERSION, - publisher_offering_location=NFD_LOCATION, - publisher_scope=PUBLISHER_SCOPE, - type=NFD_TYPE, - multiple_instances=MULTIPLE_INSTANCES, + publisher="The name of the existing Network Function Definition Group to deploy using this NSD", + publisher_resource_group="The resource group that the publisher is hosted in.", + name="The name of the existing Network Function Definition Group to deploy using this NSD", + version=( + "The version of the existing Network Function Definition to base this NSD on. " + "This NSD will be able to deploy any NFDV with deployment parameters compatible " + "with this version." + ), + publisher_offering_location="The region that the NFDV is published to.", + publisher_scope=( + "The scope that the publisher is published under. Only 'private' is supported." + ), + type="Type of Network Function. Valid values are 'cnf' or 'vnf'", + multiple_instances=( + "Set to true or false. Whether the NSD should allow arbitrary numbers of this " + "type of NF. If set to false only a single instance will be allowed. Only " + "supported on VNFs, must be set to false on CNFs." + ), ) def validate(self) -> None: @@ -610,10 +583,15 @@ def helptext(cls) -> "NSConfiguration": Build a NSConfiguration object where each value is helptext for that field. """ nsd_helptext = NSConfiguration( - nsd_name=DESCRIPTION_MAP["nsd_name"], - nsd_version=DESCRIPTION_MAP["nsd_version"], - nsdv_description=DESCRIPTION_MAP["nsdv_description"], - **asdict(Configuration.helptext()) + nsd_name=( + "Network Service Design (NSD) name. This is the collection of Network Service" + " Design Versions. Will be created if it does not exist." + ), + nsd_version=( + "Version of the NSD to be created. This should be in the format A.B.C" + ), + nsdv_description="Description of the NSDV.", + **asdict(Configuration.helptext()), ) nsd_helptext.network_functions = [NFDRETConfiguration.helptext()] @@ -625,15 +603,16 @@ def validate(self): :raises ValueError for any invalid config """ + super().validate() if self.network_functions in ([], None): raise ValueError(("At least one network function must be included.")) for configuration in self.network_functions: configuration.validate() if not self.nsd_name: - raise ValueError("NSD name must be set") + raise ValueError("nsd_name must be set") if not self.nsd_version: - raise ValueError("NSD Version must be set") + raise ValueError("nsd_version must be set") @property def output_directory_for_build(self) -> Path: @@ -689,7 +668,7 @@ def get_configuration(configuration_type: str, config_file: str) -> Configuratio raise InvalidArgumentValueError( f"Config file {config_file} is not valid: {typeerr}" ) from typeerr - + config.validate() return config diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json index ac0822bf6f1..2a4c5622ee4 100644 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json +++ b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json @@ -15,7 +15,7 @@ "publisher_resource_group": "{{publisher_resource_group_name}}" } ], - "nsdg_name": "nginx", + "nsd_name": "nginx", "nsd_version": "1.0.0", "nsdv_description": "Deploys a basic NGINX CNF" } \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json index b74099cb3ca..960a4b0b57b 100644 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json +++ b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json @@ -15,7 +15,7 @@ "publisher_resource_group": "{{publisher_resource_group_name}}" } ], - "nsdg_name": "ubuntu", + "nsd_name": "ubuntu", "nsd_version": "1.0.0", "nsdv_description": "Plain ubuntu VM" } \ No newline at end of file From c6992584cffae1f60c8b2b5e3074e19c1e2e1597 Mon Sep 17 00:00:00 2001 From: Jamie Parsons Date: Tue, 26 Sep 2023 11:47:53 +0100 Subject: [PATCH 3/3] Code markups + linting. --- src/aosm/azext_aosm/_configuration.py | 61 ++++++++++++------- src/aosm/azext_aosm/deploy/deploy_with_arm.py | 25 ++++++-- src/aosm/azext_aosm/deploy/pre_deploy.py | 4 +- .../generate_nfd/cnf_nfd_generator.py | 2 + .../generate_nfd/vnf_nfd_generator.py | 5 +- src/aosm/azext_aosm/generate_nsd/nf_ret.py | 14 +++-- .../azext_aosm/generate_nsd/nsd_generator.py | 14 +++-- .../latest/mock_nsd/input_multi_nf_nsd.json | 2 +- .../mock_nsd/input_multiple_instances.json | 2 +- 9 files changed, 84 insertions(+), 45 deletions(-) diff --git a/src/aosm/azext_aosm/_configuration.py b/src/aosm/azext_aosm/_configuration.py index 2ffc950d1ed..cf76bdea7c8 100644 --- a/src/aosm/azext_aosm/_configuration.py +++ b/src/aosm/azext_aosm/_configuration.py @@ -9,7 +9,7 @@ import os from dataclasses import dataclass, field, asdict from pathlib import Path -from typing import Any, List, Optional, Union +from typing import Any, Dict, List, Optional, Union from azure.cli.core.azclierror import InvalidArgumentValueError, ValidationError from azext_aosm.util.constants import ( @@ -29,8 +29,8 @@ class ArtifactConfig: # artifact.py checks for the presence of the default descriptions, change # there if you change the descriptions. artifact_name: str = "" - file_path: Optional[str] = "" - blob_sas_url: Optional[str] = "" + file_path: Optional[str] = None + blob_sas_url: Optional[str] = None version: Optional[str] = "" @classmethod @@ -52,15 +52,6 @@ def helptext(cls) -> "ArtifactConfig": version="Version of the artifact in A.B.C format.", ) - def __post_init__(self): - """ - Change empty stings to None. - """ - if not self.file_path: - self.file_path = None - if not self.blob_sas_url: - self.blob_sas_url = None - def validate(self): """ Validate the configuration. @@ -202,8 +193,8 @@ def acr_manifest_names(self) -> List[str]: class VNFConfiguration(NFConfiguration): blob_artifact_store_name: str = "" image_name_parameter: str = "" - arm_template: Any = ArtifactConfig() - vhd: Any = ArtifactConfig() + arm_template: Union[Dict[str, str], ArtifactConfig] = ArtifactConfig() + vhd: Union[Dict[str, str], ArtifactConfig] = ArtifactConfig() @classmethod def helptext(cls) -> "VNFConfiguration": @@ -249,9 +240,14 @@ def validate(self) -> None: """ super().validate() + assert isinstance(self.vhd, ArtifactConfig) + assert isinstance(self.arm_template, ArtifactConfig) self.vhd.validate() self.arm_template.validate() + assert self.vhd.version + assert self.arm_template.version + if "." in self.vhd.version or "-" not in self.vhd.version: raise ValidationError( "Config validation error. VHD artifact version should be in format" @@ -272,6 +268,8 @@ def sa_manifest_name(self) -> str: @property def output_directory_for_build(self) -> Path: """Return the local folder for generating the bicep template to.""" + assert isinstance(self.arm_template, ArtifactConfig) + assert self.arm_template.file_path arm_template_name = Path(self.arm_template.file_path).stem return Path(f"{NF_DEFINITION_OUTPUT_BICEP_PREFIX}{arm_template_name}") @@ -374,8 +372,10 @@ def validate(self): @dataclass class CNFConfiguration(NFConfiguration): - images: Any = CNFImageConfig() - helm_packages: List[Any] = field(default_factory=lambda: []) + images: Union[Dict[str, str], CNFImageConfig] = CNFImageConfig() + helm_packages: List[Union[Dict[str, Any], HelmPackageConfig]] = field( + default_factory=lambda: [] + ) def __post_init__(self): """ @@ -440,6 +440,17 @@ class NFDRETConfiguration: # pylint: disable=too-many-instance-attributes type: str = "" multiple_instances: Union[str, bool] = False + def __post_init__(self): + """ + Convert parameters to the correct types. + """ + # Cope with multiple_instances being supplied as a string, rather than a bool. + if isinstance(self.multiple_instances, str): + if self.multiple_instances.lower() == "true": + self.multiple_instances = True + elif self.multiple_instances.lower() == "false": + self.multiple_instances = False + @classmethod def helptext(cls) -> "NFDRETConfiguration": """ @@ -560,10 +571,8 @@ def acr_manifest_name(self, nsd_version: str) -> str: @dataclass class NSConfiguration(Configuration): - network_functions: List[NFDRETConfiguration] = field( - default_factory=lambda: [ - NFDRETConfiguration(), - ] + network_functions: List[Union[NFDRETConfiguration, Dict[str, Any]]] = field( + default_factory=lambda: [] ) nsd_name: str = "" nsd_version: str = "" @@ -583,6 +592,7 @@ def helptext(cls) -> "NSConfiguration": Build a NSConfiguration object where each value is helptext for that field. """ nsd_helptext = NSConfiguration( + network_functions=[asdict(NFDRETConfiguration.helptext())], nsd_name=( "Network Service Design (NSD) name. This is the collection of Network Service" " Design Versions. Will be created if it does not exist." @@ -593,7 +603,6 @@ def helptext(cls) -> "NSConfiguration": nsdv_description="Description of the NSDV.", **asdict(Configuration.helptext()), ) - nsd_helptext.network_functions = [NFDRETConfiguration.helptext()] return nsd_helptext @@ -604,7 +613,7 @@ def validate(self): :raises ValueError for any invalid config """ super().validate() - if self.network_functions in ([], None): + if not self.network_functions: raise ValueError(("At least one network function must be included.")) for configuration in self.network_functions: @@ -633,7 +642,13 @@ def cg_schema_name(self) -> str: @property def acr_manifest_names(self) -> List[str]: """The list of ACR manifest names for all the NF ARM templates.""" - return [nf.acr_manifest_name(self.nsd_version) for nf in self.network_functions] + acr_manifest_names = [] + for nf in self.network_functions: + assert isinstance(nf, NFDRETConfiguration) + acr_manifest_names.append(nf.acr_manifest_name(self.nsd_version)) + + logger.debug("ACR manifest names: %s", acr_manifest_names) + return acr_manifest_names def get_configuration(configuration_type: str, config_file: str) -> Configuration: diff --git a/src/aosm/azext_aosm/deploy/deploy_with_arm.py b/src/aosm/azext_aosm/deploy/deploy_with_arm.py index 453fb4f50e8..2a3b7ef03c1 100644 --- a/src/aosm/azext_aosm/deploy/deploy_with_arm.py +++ b/src/aosm/azext_aosm/deploy/deploy_with_arm.py @@ -18,9 +18,11 @@ from knack.util import CLIError from azext_aosm._configuration import ( + ArtifactConfig, CNFConfiguration, Configuration, NFConfiguration, + NFDRETConfiguration, NSConfiguration, VNFConfiguration, ) @@ -179,14 +181,20 @@ def _vnfd_artifact_upload(self) -> None: vhd_artifact = storage_account_manifest.artifacts[0] arm_template_artifact = acr_manifest.artifacts[0] + vhd_config = self.config.vhd + arm_template_config = self.config.arm_template + + assert isinstance(vhd_config, ArtifactConfig) + assert isinstance(arm_template_config, ArtifactConfig) + if self.skip == IMAGE_UPLOAD: print("Skipping VHD artifact upload") else: print("Uploading VHD artifact") - vhd_artifact.upload(self.config.vhd) + vhd_artifact.upload(vhd_config) print("Uploading ARM template artifact") - arm_template_artifact.upload(self.config.arm_template) + arm_template_artifact.upload(arm_template_config) def _cnfd_artifact_upload(self) -> None: """Uploads the Helm chart and any additional images.""" @@ -302,6 +310,8 @@ def construct_parameters(self) -> Dict[str, Any]: """ if self.resource_type == VNF: assert isinstance(self.config, VNFConfiguration) + assert isinstance(self.config.vhd, ArtifactConfig) + assert isinstance(self.config.arm_template, ArtifactConfig) return { "location": {"value": self.config.location}, "publisherName": {"value": self.config.publisher_name}, @@ -341,6 +351,8 @@ def construct_manifest_parameters(self) -> Dict[str, Any]: """Create the parmeters dictionary for VNF, CNF or NSD.""" if self.resource_type == VNF: assert isinstance(self.config, VNFConfiguration) + assert isinstance(self.config.vhd, ArtifactConfig) + assert isinstance(self.config.arm_template, ArtifactConfig) return { "location": {"value": self.config.location}, "publisherName": {"value": self.config.publisher_name}, @@ -363,9 +375,11 @@ def construct_manifest_parameters(self) -> Dict[str, Any]: if self.resource_type == NSD: assert isinstance(self.config, NSConfiguration) - arm_template_names = [ - nf.arm_template.artifact_name for nf in self.config.network_functions - ] + arm_template_names = [] + + for nf in self.config.network_functions: + assert isinstance(nf, NFDRETConfiguration) + arm_template_names.append(nf.arm_template.artifact_name) # Set the artifact version to be the same as the NSD version, so that they # don't get over written when a new NSD is published. @@ -417,6 +431,7 @@ def deploy_nsd_from_bicep(self) -> None: for manifest, nf in zip( self.config.acr_manifest_names, self.config.network_functions ): + assert isinstance(nf, NFDRETConfiguration) acr_manifest = ArtifactManifestOperator( self.config, self.api_clients, diff --git a/src/aosm/azext_aosm/deploy/pre_deploy.py b/src/aosm/azext_aosm/deploy/pre_deploy.py index 5c10e3db1c4..49637ea1046 100644 --- a/src/aosm/azext_aosm/deploy/pre_deploy.py +++ b/src/aosm/azext_aosm/deploy/pre_deploy.py @@ -418,9 +418,7 @@ def ensure_nsd_exists( network_service_design_group_name=nsd_name, parameters=NetworkServiceDesignGroup(location=location), ) - LongRunningOperation(self.cli_ctx, "Creating Network Service Design...")( - poller - ) + LongRunningOperation(self.cli_ctx, "Creating Network Service Design...")(poller) def resource_exists_by_name(self, rg_name: str, resource_name: str) -> bool: """ diff --git a/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py b/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py index 19e4cb62037..fd365d7aae2 100644 --- a/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py +++ b/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py @@ -117,6 +117,8 @@ def generate_nfd(self) -> None: try: for helm_package in self.config.helm_packages: # Unpack the chart into the tmp directory + assert isinstance(helm_package, HelmPackageConfig) + self._extract_chart(Path(helm_package.path_to_chart)) # TODO: Validate charts diff --git a/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py b/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py index 1be74bebc88..7990f3bf234 100644 --- a/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py +++ b/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py @@ -13,7 +13,7 @@ from knack.log import get_logger -from azext_aosm._configuration import VNFConfiguration +from azext_aosm._configuration import ArtifactConfig, VNFConfiguration from azext_aosm.generate_nfd.nfd_generator_base import NFDGenerator from azext_aosm.util.constants import ( CONFIG_MAPPINGS_DIR_NAME, @@ -64,6 +64,9 @@ class VnfNfdGenerator(NFDGenerator): def __init__(self, config: VNFConfiguration, order_params: bool, interactive: bool): self.config = config + assert isinstance(self.config.arm_template, ArtifactConfig) + assert self.config.arm_template.file_path + self.arm_template_path = Path(self.config.arm_template.file_path) self.output_directory: Path = self.config.output_directory_for_build diff --git a/src/aosm/azext_aosm/generate_nsd/nf_ret.py b/src/aosm/azext_aosm/generate_nsd/nf_ret.py index 4a9a0e55f2b..0ef91e0b21e 100644 --- a/src/aosm/azext_aosm/generate_nsd/nf_ret.py +++ b/src/aosm/azext_aosm/generate_nsd/nf_ret.py @@ -50,12 +50,14 @@ def _get_nfdv( "Reading existing NFDV resource object " f"{config.version} from group {config.name}" ) - nfdv_object = api_clients.aosm_client.proxy_network_function_definition_versions.get( - publisher_scope_name=config.publisher_scope, - publisher_location_name=config.publisher_offering_location, - proxy_publisher_name=config.publisher, - network_function_definition_group_name=config.name, - network_function_definition_version_name=config.version, + nfdv_object = ( + api_clients.aosm_client.proxy_network_function_definition_versions.get( + publisher_scope_name=config.publisher_scope, + publisher_location_name=config.publisher_offering_location, + proxy_publisher_name=config.publisher, + network_function_definition_group_name=config.name, + network_function_definition_version_name=config.version, + ) ) return nfdv_object diff --git a/src/aosm/azext_aosm/generate_nsd/nsd_generator.py b/src/aosm/azext_aosm/generate_nsd/nsd_generator.py index e914c7c73c3..adee03677e6 100644 --- a/src/aosm/azext_aosm/generate_nsd/nsd_generator.py +++ b/src/aosm/azext_aosm/generate_nsd/nsd_generator.py @@ -13,7 +13,7 @@ from jinja2 import Template from knack.log import get_logger -from azext_aosm._configuration import NSConfiguration +from azext_aosm._configuration import NFDRETConfiguration, NSConfiguration from azext_aosm.generate_nsd.nf_ret import NFRETGenerator from azext_aosm.util.constants import ( CONFIG_MAPPINGS_DIR_NAME, @@ -56,10 +56,14 @@ def __init__(self, api_clients: ApiClients, config: NSConfiguration): self.config = config self.nsd_bicep_template_name = NSD_DEFINITION_JINJA2_SOURCE_TEMPLATE self.nsd_bicep_output_name = NSD_BICEP_FILENAME - self.nf_ret_generators = [ - NFRETGenerator(api_clients, nf_config, self.config.cg_schema_name) - for nf_config in self.config.network_functions - ] + + self.nf_ret_generators = [] + + for nf_config in self.config.network_functions: + assert isinstance(nf_config, NFDRETConfiguration) + self.nf_ret_generators.append( + NFRETGenerator(api_clients, nf_config, self.config.cg_schema_name) + ) def generate_nsd(self) -> None: """Generate a NSD templates which includes an Artifact Manifest, NFDV and NF templates.""" diff --git a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json index 970c8c16b2a..0c5c5160ca5 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json @@ -12,7 +12,7 @@ "version": "1.0.0", "publisher_offering_location": "eastus", "type": "cnf", - "multiple_instances": false + "multiple_instances": "False" }, { "publisher": "reference-publisher", diff --git a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json index 1775c42a053..e03f76c9c0d 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json @@ -9,7 +9,7 @@ "version": "1.0.0", "publisher_offering_location": "eastus", "type": "vnf", - "multiple_instances": true, + "multiple_instances": "True", "publisher_scope": "private", "publisher": "jamie-mobile-publisher", "publisher_resource_group": "Jamie-publisher"