Skip to content

Commit

Permalink
Sunny/bicep deployer (Azure#3)
Browse files Browse the repository at this point in the history
* Draft create NFDV using python SDK

* pushing this but then moving on to delete

* start to move to bicep deployer

* VNF generate and deploy bare bones, not working

* predeployer maybe working

* Fix up VNF generate and deploy

* printing

* artifact upload half broken

* Artifact manifest stuff over to SDK

* delete NFD function

* linting

* Fix up json config -> VNFConfiguration

* artifact manifests separate

* style

* renames

* style

* lint and reverse some renames

* dev instructions

* oops. bad find and replace work.

* undo friday afternoon bad changes

* manifest optional

* Basic README

* delete publisher definition

* unwanted constant

* d

* Update src/aosm/setup.py

Suggestion from cyclam

Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com>

* markups

* lint

---------

Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com>
  • Loading branch information
sunnycarter and Cyclam authored May 12, 2023
1 parent 8cdf766 commit 057ef05
Show file tree
Hide file tree
Showing 30 changed files with 2,181 additions and 576 deletions.
57 changes: 57 additions & 0 deletions src/aosm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Microsoft Azure CLI 'aosm' Extension
==========================================

This package is for the 'aosm' extension to support Azure Operator Service Manager
functions.
i.e. `az aosm`

Install via `az extension add --name aosm`


# Background
The `az aosm` extension provides support for publishing Network Function Definitions
to use with Azure Operator Service Manager or Network Function Manager.

# Pre-requisites
## VNFs
For VNFs, you will need a single ARM template which would create the Azure resources
for your VNF, for example a Virtual Machine, disks and NICs. You'll also need a VHD
image that would be used for the VNF Virtual Machine.

# Command examples

Get help on command arguments

`az aosm -h`
`az aosm definition -h`
`az aosm definition build -h`
etc...

All these commands take a `--definition-type` argument of `vnf`, `cnf` or (coming) `nsd`

Create an example config file for building a definition

`az aosm definition generate-config --config-file input.json`

This will output a file called `input.json` which must be filled in.
Once the config file has been filled in the following commands can be run.

Build a definition locally

`az aosm definition build --config-file input.json`

Build and publish a definition

`az aosm definition build --config-file input.json --publish`

Publish a pre-built definition

`az aosm definition publish --config-file input.json`

Delete a published definition

`az aosm definition delete --config-file input.json`

Delete a published definition and the publisher, artifact stores and NFD group

`az aosm definition delete --config-file input.json --clean`
5 changes: 0 additions & 5 deletions src/aosm/README.rst

This file was deleted.

8 changes: 5 additions & 3 deletions src/aosm/azext_aosm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@


class AosmCommandsLoader(AzCommandsLoader):

def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
aosm_custom = CliCommandType(operations_tmpl='azext_aosm.custom#{}')
super(AosmCommandsLoader, self).__init__(cli_ctx=cli_ctx, custom_command_type=aosm_custom)

aosm_custom = CliCommandType(operations_tmpl="azext_aosm.custom#{}")
super().__init__(cli_ctx=cli_ctx, custom_command_type=aosm_custom)

def load_command_table(self, args):
from azext_aosm.commands import load_command_table

load_command_table(self, args)
return self.command_table

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

load_arguments(self, command)


Expand Down
4 changes: 3 additions & 1 deletion src/aosm/azext_aosm/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
from azure.cli.core.profiles import ResourceType
from .vendored_sdks import HybridNetworkManagementClient


def cf_aosm(cli_ctx, *_) -> HybridNetworkManagementClient:
return get_mgmt_service_client(cli_ctx, HybridNetworkManagementClient)


def cf_resources(cli_ctx, subscription_id=None):
return get_mgmt_service_client(
cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, subscription_id=subscription_id
).resources
)
154 changes: 134 additions & 20 deletions src/aosm/azext_aosm/_configuration.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,156 @@
from dataclasses import dataclass
from typing import Optional
from knack.util import CLIError
from ._constants import VNF, CNF, NSD
from typing import Dict, Optional, Any
from pathlib import Path
from azure.cli.core.azclierror import ValidationError, InvalidArgumentValueError
from azext_aosm.util.constants import VNF_DEFINITION_OUTPUT_BICEP_PREFIX, VNF, CNF, NSD

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."
),
"nf_name": "Name of NF definition",
"version": "Version of the NF definition",
"acr_artifact_store_name": "Name of the ACR Artifact Store resource",
"location": "Azure location to use when creating resources",
"blob_artifact_store_name": "Name of the storage account Artifact Store resource",
"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."
),
"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"
),
}


@dataclass
class ArtifactConfig:
artifact_name: str = "Name of the artifact"
file_path: Optional[str] = "File path of the artifact you wish to upload from your local disk"
blob_sas_url: Optional[str] = "SAS URL of the blob artifact you wish to copy to your Artifact Store"
artifact_name: str = DESCRIPTION_MAP["artifact_name"]
# artifact.py checks for the presence of the default descriptions, change there if
# you change the descriptions.
file_path: Optional[str] = DESCRIPTION_MAP["file_path"]
blob_sas_url: Optional[str] = DESCRIPTION_MAP["blob_sas_url"]
version: str = DESCRIPTION_MAP["artifact_version"]


@dataclass
class Configuration():
publisher_name: str = "Name of the Publisher resource you want you definition published to"
publisher_resource_group_name: str = "Resource group the Publisher resource is in or you want it to be in"
name: str = "Name of NF definition"
version: str = "Version of the NF definition"
acr_artifact_store_name: str = "Name of the ACR Artifact Store resource"
class NFConfiguration:
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"]

@property
def nfdg_name(self) -> str:
"""Return the NFD Group name from the NFD name."""
return f"{self.nf_name}-nfdg"

@property
def acr_manifest_name(self) -> str:
"""Return the ACR manifest name from the NFD name."""
return f"{self.nf_name}-acr-manifest-{self.version.replace('.', '-')}"


@dataclass
class VNFConfiguration(Configuration):
blob_artifact_store_name: str = "Name of the storage account Artifact Store resource"
arm_template: ArtifactConfig = ArtifactConfig()
vhd: ArtifactConfig = ArtifactConfig()
class VNFConfiguration(NFConfiguration):
blob_artifact_store_name: str = DESCRIPTION_MAP["blob_artifact_store_name"]
arm_template: Any = ArtifactConfig()
vhd: Any = ArtifactConfig()

def __post_init__(self):
"""
Cope with deserializing subclasses from dicts to ArtifactConfig.
Used when creating VNFConfiguration object from a loaded json config file.
"""
if isinstance(self.arm_template, dict):
self.arm_template = ArtifactConfig(**self.arm_template)

if isinstance(self.vhd, dict):
self.vhd = ArtifactConfig(**self.vhd)
self.validate()

def validate(self) -> None:
"""
Validate the configuration passed in.
:raises ValidationError for any invalid config
"""
if self.vhd.version == DESCRIPTION_MAP["version"]:
# Config has not been filled in. Don't validate.
return

if "." in self.vhd.version or "-" not in self.vhd.version:
raise ValidationError(
"Config validation error. VHD artifact version should be in format A-B-C"
)
if "." not in self.arm_template.version or "-" in self.arm_template.version:
raise ValidationError(
"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"]
)
# 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:
"""Return the Storage account manifest name from the NFD name."""
return f"{self.nf_name}-sa-manifest-{self.version.replace('.', '-')}"

@property
def build_output_folder_name(self) -> str:
"""Return the local folder for generating the bicep template to."""
arm_template_path = self.arm_template.file_path
return (
f"{VNF_DEFINITION_OUTPUT_BICEP_PREFIX}{Path(str(arm_template_path)).stem}"
)


def get_configuration(definition_type, config_as_dict=None):
def get_configuration(
definition_type: str, config_as_dict: Optional[Dict[Any, Any]] = None
) -> NFConfiguration:
if config_as_dict is None:
config_as_dict = {}

if definition_type == VNF:
config = VNFConfiguration(**config_as_dict)
elif definition_type == CNF:
config = Configuration(**config_as_dict)
config = NFConfiguration(**config_as_dict)
elif definition_type == NSD:
config = Configuration(**config_as_dict)
config = NFConfiguration(**config_as_dict)
else:
raise CLIError("Definition type not recognized, options are: vnf, cnf or nsd")
raise InvalidArgumentValueError(
"Definition type not recognized, options are: vnf, cnf or nsd"
)

return config
Loading

0 comments on commit 057ef05

Please sign in to comment.