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

Achurchard/publish #130

Merged
merged 12 commits into from
Jan 19, 2024
14 changes: 3 additions & 11 deletions src/aosm/azext_aosm/cli_handlers/onboarding_base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __init__(
):
"""Initialize the CLI handler."""
self.aosm_client = aosm_client
config_file = Path(config_file)
# If config file provided (for build, publish and delete)
if config_file:
config_file = Path(config_file)
Expand Down Expand Up @@ -94,17 +95,8 @@ def build(self):
self.definition_folder_builder.write()

def publish(self, command_context: CommandContext):
"""Publish the definition."""
# Takes folder, deploys to Azure
# - Work out where the definition folder is
# - If not specified, use a set path (see constants.py for directory names), and error if not found with option of moving to correct dir, or specifying path
# - If specified, use that path
# - Read folder/ create folder object
if command_context.cli_options["definition_folder"]:
definition_folder = DefinitionFolder(
command_context.cli_options["definition_folder"]
)
# TODO: else logic for finding VNF_OUTPUT_FOLDER_FILENAME, etc., assuming command run from same directory as build.
"""Publish the definition contained in the specified definition folder."""
definition_folder = DefinitionFolder(command_context.cli_options["definition_folder"])
definition_folder.deploy(config=self.config, command_context=command_context)

def delete(self, command_context: CommandContext):
Expand Down
6 changes: 2 additions & 4 deletions src/aosm/azext_aosm/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@


def load_command_table(self: AzCommandsLoader, _):
# TODO: think client_factory can be deleted. The handlers don't use it
with self.command_group("aosm nfd", client_factory=cf_aosm) as g:
with self.command_group("aosm nfd") as g:
# Add each command and bind it to a function in custom.py
g.custom_command("generate-config", "onboard_nfd_generate_config")
g.custom_command("build", "onboard_nfd_build")
g.custom_command("publish", "onboard_nfd_publish")
g.custom_command("delete", "onboard_nfd_delete")

# TODO: think client_factory can be deleted. The handlers don't use it
with self.command_group("aosm nsd", client_factory=cf_aosm) as g:
with self.command_group("aosm nsd") as g:
# Add each command and bind it to a function in custom.py
g.custom_command("generate-config", "onboard_nsd_generate_config")
g.custom_command("build", "onboard_nsd_build")
Expand Down
22 changes: 8 additions & 14 deletions src/aosm/azext_aosm/common/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,13 @@ def _call_subprocess_raise_output(cmd: list) -> None:
called_process.stderr,
)
except subprocess.CalledProcessError as error:
logger.debug("Failed to run %s with %s", log_cmd, error)

all_output: str = (
f"Command: {' '.join(log_cmd)}\n"
f"stdout: {error.stdout}\n"
f"stderr: {error.stderr}\n"
f"Return code: {error.returncode}"
)
logger.debug("All the output:\n%s", all_output)

logger.debug("The following command failed to run:\n%s", all_output)
# Raise the error without the original exception, which may contain secrets.
raise CLIError(all_output) from None

Expand Down Expand Up @@ -148,15 +145,14 @@ class LocalFileACRArtifact(BaseACRArtifact):
def __init__(self, artifact_name, artifact_type, artifact_version, file_path: Path):
super().__init__(artifact_name, artifact_type, artifact_version)
self.file_path = str(file_path) # TODO: Jordan cast this to str here, check output file isn't broken, and/or is it used as a Path elsewhere?

# TODO (WIBNI): check if the artifact name ends in .bicep and if so use utils.convert_bicep_to_arm()
# This way we can support in-place Bicep artifacts in the folder.

def upload(self, config: BaseCommonParametersConfig, command_context: CommandContext, oras_client: OrasClient = None):
def upload(self, config: BaseCommonParametersConfig, command_context: CommandContext):
"""Upload the artifact."""
logger.debug("LocalFileACRArtifact config: %s", config)
if not oras_client:
manifest_credentials = self._manifest_credentials(config=config, aosm_client=command_context.aosm_client)
oras_client = self._get_oras_client(manifest_credentials=manifest_credentials)
manifest_credentials = self._manifest_credentials(config=config, aosm_client=command_context.aosm_client)
oras_client = self._get_oras_client(manifest_credentials=manifest_credentials)
target_acr = self._get_acr(oras_client)
target = (
f"{target_acr}/{self.artifact_name}:{self.artifact_version}"
Expand All @@ -167,16 +163,14 @@ def upload(self, config: BaseCommonParametersConfig, command_context: CommandCon
try:
oras_client.push(files=[self.file_path], target=target)
break
except ValueError as error:
if retries < 50:
except ValueError:
if retries < 20:
logger.info("Retrying pushing local artifact to ACR. Retries so far: %s", retries)
retries += 1
sleep(1)
sleep(3)
continue
raise error
logger.info("LocalFileACRArtifact uploaded %s to %s", self.file_path, target)

return oras_client

class RemoteACRArtifact(BaseACRArtifact):
"""Class for ACR artifacts from a remote ACR image."""
Expand Down
4 changes: 0 additions & 4 deletions src/aosm/azext_aosm/common/command_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,3 @@ def __post_init__(self):
self.resources_client: ResourceManagementClient = get_mgmt_service_client(
self.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES
)

# Old CLI also had a feature client and an ACR registry client, but they don't seem necessary.
# Excluding from here for now, but leaving this note as a reminder in case we need to add them later.
# TODO: Remove above note if we don't need to add them.
4 changes: 2 additions & 2 deletions src/aosm/azext_aosm/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ def convert_bicep_to_arm(bicep_template_path: Path) -> dict:
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError:
raise RuntimeError("Bicep to ARM template compilation failed")
except subprocess.CalledProcessError as error:
raise RuntimeError(f"Bicep to ARM template compilation failed.\n{error.stderr}")

logger.debug("ARM template:\n%s", arm_path.read_text())
arm_json = json.loads(arm_path.read_text())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from abc import ABC
from dataclasses import dataclass


# Config is sometimes used as an argument to cached functions. These
# arguments must be hashable, so we need to use frozen dataclasses.
# This is fine because we shouldn't be changing this initial input anyway.
Expand Down Expand Up @@ -46,7 +47,6 @@ class CNFCommonParametersConfig(NFDCommonParametersConfig):
@dataclass(frozen=True)
class NSDCommonParametersConfig(BaseCommonParametersConfig):
"""Common parameters configuration for NSDs."""

nsDesignGroup: str
nsDesignVersion: str
nfviSiteName: str
2 changes: 1 addition & 1 deletion src/aosm/azext_aosm/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def onboard_nfd_publish(
cli_ctx=cmd.cli_ctx,
cli_options={
"no_subscription_permissions": no_subscription_permissions,
"definition_folder": Path(build_output_folder) if build_output_folder else None,
"definition_folder": Path(build_output_folder),
},
)
if definition_type == "cnf":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ def write(self):
artifacts_list = []
for artifact in self.artifacts:
artifacts_list.append(artifact.to_dict())
print("artifact list", artifacts_list)
(self.path / "artifacts.json").write_text(json.dumps(artifacts_list, indent=4))
self._write_supporting_files()
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,33 @@ def __init__(self, path: Path, only_delete_on_clean: bool):

def create_artifact_object(self, artifact: dict) -> BaseArtifact:
"""
Use the inspect module to identify the artifact class's required fields and
create an instance of the class using the supplied artifact dict.
Use reflection (via the inspect module) to identify the artifact class's required fields
and create an instance of the class using the supplied artifact dict.
"""
print("artifact: ", artifact)
if "type" not in artifact or artifact["type"] not in ARTIFACT_TYPE_TO_CLASS:
raise ValueError("Artifact type is missing or invalid")
raise ValueError("Artifact type is missing or invalid for artifact {artifact}")
# Use reflection to get the required fields for the artifact class
class_sig = inspect.signature(ARTIFACT_TYPE_TO_CLASS[artifact["type"]].__init__)
class_args = [arg for arg, _ in class_sig.parameters.items() if arg != 'self']
Cyclam marked this conversation as resolved.
Show resolved Hide resolved
logger.debug("Artifact configuration from definition folder: %s", artifact)
logger.debug("class_args reflection: %s", class_args)
logger.debug("class_args found for artifact type %s: %s", artifact["type"], class_args)
# Filter the artifact dict to only include the required fields, erroring if any are missing
try:
filtered_dict = {arg: artifact[arg] for arg in class_args}
except KeyError as e:
raise ValueError(f"Artifact is missing required field {e}.\n"
f"Required fields are: {class_args}.\n"
f"Artifact is: {artifact}")
if not all(arg in artifact for arg in class_args):
raise ValueError(f"Artifact is missing required fields.\n"
f"Required fields are: {class_args}.\n"
f"Artifact is: {artifact}")
f"Artifact is: {artifact}.\n"
"This is unexpected and most likely comes from manual editing "
"of the definition folder.")
return ARTIFACT_TYPE_TO_CLASS[artifact["type"]](**filtered_dict)

def deploy(self, config: BaseCommonParametersConfig, command_context: CommandContext):
"""Deploy the element."""
oras_client = None
for artifact in self.artifacts:
logger.info("Deploying artifact %s of type %s", artifact.artifact_name, type(artifact))
logger.info("Oras client id: %s", id(oras_client))
if oras_client:
artifact.upload(config=config, command_context=command_context, oras_client=oras_client)
else:
oras_client = artifact.upload(config=config, command_context=command_context)
artifact.upload(config=config, command_context=command_context)

def delete(self):
"""Delete the element."""
Expand Down