From 8decf2939e5ec8c653275d4e496dac3298205391 Mon Sep 17 00:00:00 2001 From: jordlay <72226943+jordlay@users.noreply.github.com> Date: Mon, 20 May 2024 07:16:47 +0100 Subject: [PATCH] AZ AOSM Extension Refactor (#7429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge Temp mavenir wheel into MAIN (#135) * added empty template parser * Initial scaffolding of some new classes * added utils, added base builder + reader + their children, added nfd and vhd processors * moved utils to common folder + added local file builder * Add some basic implementations * add new commands and restructure command groups * better naming for command groups * revert addition on the onboard command group * update HISTORY.rst * fix style issue * fix linting issues * added empty vhd and nfd processors; moved client factory from old repo * Add template parser interface (BaseParser) * Add parameters to BaseParser * Make defaults_path optional in base_parser * defaults_path defaults to None in base_parser * add helm chart parser class and common exceptions * add missing doc strings * Change parser class to input template class * added all input config params + comments; added common arm template config; added instructions for completing publish/delete * add typing to base processor and add copyright statement to new files * fixed formatting on input configs * fixed nsd input config + tidied other input config files * added comments for images; general formatting * rename template parsers to input templates and add helm chart processor * add __init__.py to folders * fixed imports + added innit in folders * fixed imports + added defaults for input params * Not quite complete first pass at definition folder handlers * added generate config log; fixed input config objects * rename InputTemplate to InputArtifacts * tidied write config + added read config; deleted json file checked in by mistake * added overwrite validation to generate config; added j2 templates to common * added validation to base config and nsd * Add UTs and fix up definition folder code * added validation to nsd + cnf; added default_file_name property; added tests for generate config; added example nsd-input.jsonc for testing * add vhd processor * remove leftover file from merge * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Fix up some type hinting * added write manifest bicep and nf application to base handler; fixed helm config to produce correct bicep * push latest changes * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * WIP, for sharing with Jacob * Incremental update * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Approximately complete ARM processor. No tests, no testing yet. * Fix artifact store reference * WIP * fixed cnf bicep template * fixed multiple helm/vhd/arm error; working vnfartifactmanifet; attempted vnfdefintiion with testing code still in; updated write bicep functions to take 2 arguments for vnf case * fixed cnfdef bicep; return correct type in cnf handler * Added outputfolder name to base handler; reorganised templates folder structure; added constants for cnf j2 templates; implemented build (tested with mocks) for cnf handler; implemented localfileACR artifact to_dict function; updated write for artifact_builder.py; added overwriting folder logic to defintion folder builder * renamed write manifest; updated nf templates to deploy initial resources; vnf templates working; vnf build commands working * added logic to cnf handler for manifest and definition supporting files + config mappings * fixed cnf params files; added render params for vnf * fix HelmChartInput issues * Merge arm-processor code into Jacob's branch (#127) * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * WIP, for sharing with Jacob * Incremental update * Approximately complete ARM processor. No tests, no testing yet. * Fix artifact store reference --------- Co-authored-by: Andy Churchard * added build deploy params schema to base handler; added functionality for deployParams to be added as supporting file for cnd * fix majority of linting issues * renamed write on base handler to render; added build logic for manifest and artifact list for vnf * added write manifest bicep and nf application to base handler; fixed helm config to produce correct bicep * fixed cnf bicep template * Initial ArmInputTemplate class * complete helm chart processor * almost finished NFD processor * WIP, for sharing with Jacob * Incremental update * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Approximately complete ARM processor. No tests, no testing yet. * WIP * fixed multiple helm/vhd/arm error; working vnfartifactmanifet; attempted vnfdefintiion with testing code still in; updated write bicep functions to take 2 arguments for vnf case * fixed cnfdef bicep; return correct type in cnf handler * Added outputfolder name to base handler; reorganised templates folder structure; added constants for cnf j2 templates; implemented build (tested with mocks) for cnf handler; implemented localfileACR artifact to_dict function; updated write for artifact_builder.py; added overwriting folder logic to defintion folder builder * renamed write manifest; updated nf templates to deploy initial resources; vnf templates working; vnf build commands working * added logic to cnf handler for manifest and definition supporting files + config mappings * fixed cnf params files; added render params for vnf * added build deploy params schema to base handler; added functionality for deployParams to be added as supporting file for cnd * renamed write on base handler to render; added build logic for manifest and artifact list for vnf * fix up code * fix base input * run isort * implemented vnf build + removed test code; fixed arm processor * fixed templates * removed multiple vhds from vnf + fixed bicep; made path to mappings optional for cnf * renamed write file to render contents * fixed helm chart processor + small tidy up * commit for build handover; not working + handover notes sent separately * run isort * fix issues in input classes * add generic schema and value mapping generation and fix arm and vhd processors * push changes I forgot to push before xmas * checked out fixed build processors and inputs from jdarby/add-build-processors * fix values and schema generation * add RET generation for ARM templates * fix linting issues * made input file name constant + renamed inputs to match changes * checked out build processor changes * Moved _render_deployment_params_schema and _build_deploy_params_schema to nfd base handler; added default_config logic to cnf handler;created deploymentParamers for cnf + vnf * changed j2 template for vhd image name * HELM CHART PROCESSOR CHANGES! fixed registry value path and depends on profile in cnf template * fixed nsd artifact manifest, changed artifact type in all processers (PROCESSOR CHANGE) * implemented nsd build get artifact manifest and get artifact list * added base bicep to all handlers; edited j2 templates to note that base resources should be deployed first on publish; made all build use constants * added to_dict for localfileACRartifact * added source namespcae and source reigstry to artifact + in helm processor callinh remote arc artifacts * finish nfd processor * add copyright statement to helm processor file * linting * created common params folder (making new jsonbuilder), adding get params config to base', renamed get_config to get_input_config; aded logging to vnf; chnged custom.py publish inputs * partially ready commit; added cmd context to base + added some logic to nsds * added all parameters constant * tidied vnf handler; added common params logic to cnf * markups inc tidy up * renamed snake_case param * add nsd output config * taken some input and build processor changes from jdarby/build_processor branch * Add logging, improve doc string and fix mypy errors for inputs * fix 2 bugs introduced by merging buildprocessors and adding json deploy params * fixed how aosm client is passed to build; properly get nfdv object with api client * refactored vnf handler to create processors upfront * refactor cnf to instantiate processors upfront * Improve docstrings, add logging and fix linting issues for build processors * nsd temp commit * fix missed conflict * fix linting and formatting issue * rename + add constants + add new definition tempalte * simplified render bicep definiton contents + removes all constants for filenames from nf handlers and templates * build nsd bicep core working * fixed nsd bicep * Fix two minor CLI bugs (#129) * Fix bug where the build directory wasn't deleted * Fix bug where the json wasn't output correctly * fixed incorrect merge fix --------- Co-authored-by: Jordan * small fixes; removed location from nfd input, removed comma from vnd def template, fixed artifact version in nsds, fixed vhd validation * made cgs name constant to fix error in mappings * added store type to all the templates * tidy up + small markups * removed version state from the definition biceps * fixed bug where schemas and defaults were copied incorrectly * made artifact paths absolute in all cases except nsd nfs, where paht is relative to nsd output folder * added missing comments; renamed common params + edited its format; changed custom to take path * added existing back to all templates + descriptors of resources * Merge publish into temp wheel branch (#131) * Move artifact create from dict logic to instantiating code * Create CommandContext class to hold Azure clients and CLI options, and pass instance through to the delete and publish commands. * Mostly implemented and working publish. Lots of debug code still present. * Publish done and mostly tested. * Add some more logging. Remove help text referring to --order-params. * Update src/aosm/azext_aosm/common/artifact.py Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * fix merge conflicts --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Minor input improvements (#132) * fixed common params files for all * remove prints; remove sourcelocaldockerimage + add correct validation; edit input.json comments * fixed extract tar error * only create oras_client once - temp solution --------- Co-authored-by: Jordan Co-authored-by: Andy Churchard * edited input file (#133) Co-authored-by: Jordan * fixed getparamsconfig for vnf/nsd; added fixes for building correct python wheel * Achurchard/publish (#130) Markups from publish PR --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan Co-authored-by: jordlay <72226943+jordlay@users.noreply.github.com> * bug fixes for chart with subcharts * added ruamel * Use safe yaml loading * Move code out of import list * Fix errant config line bug * fixed deployparams error (#134) Co-authored-by: Jordan * fixed yaml load error * disabled multi anchor yaml warning for values.yaml * delete accidental deployparams file --------- Co-authored-by: Jordan Co-authored-by: Chaos Chhapi Co-authored-by: Jacob <53872686+jddarby@users.noreply.github.com> Co-authored-by: Jacob Darby Co-authored-by: Andy Churchard Co-authored-by: patrykkulik-microsoft <116072282+patrykkulik-microsoft@users.noreply.github.com> Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * fix silly yaml import * moved config validate to init, commented out delete command, add context to publish + passed paths properly in custom.py * Add Helm template validation to the build method in the CLI (#136) * added empty template parser * Initial scaffolding of some new classes * added utils, added base builder + reader + their children, added nfd and vhd processors * moved utils to common folder + added local file builder * Add some basic implementations * add new commands and restructure command groups * better naming for command groups * revert addition on the onboard command group * update HISTORY.rst * fix style issue * fix linting issues * added empty vhd and nfd processors; moved client factory from old repo * Add template parser interface (BaseParser) * Add parameters to BaseParser * Make defaults_path optional in base_parser * defaults_path defaults to None in base_parser * add helm chart parser class and common exceptions * add missing doc strings * Change parser class to input template class * added all input config params + comments; added common arm template config; added instructions for completing publish/delete * add typing to base processor and add copyright statement to new files * fixed formatting on input configs * fixed nsd input config + tidied other input config files * added comments for images; general formatting * rename template parsers to input templates and add helm chart processor * add __init__.py to folders * fixed imports + added innit in folders * fixed imports + added defaults for input params * Not quite complete first pass at definition folder handlers * added generate config log; fixed input config objects * rename InputTemplate to InputArtifacts * tidied write config + added read config; deleted json file checked in by mistake * added overwrite validation to generate config; added j2 templates to common * added validation to base config and nsd * Add UTs and fix up definition folder code * added validation to nsd + cnf; added default_file_name property; added tests for generate config; added example nsd-input.jsonc for testing * add vhd processor * remove leftover file from merge * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Fix up some type hinting * added write manifest bicep and nf application to base handler; fixed helm config to produce correct bicep * push latest changes * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * WIP, for sharing with Jacob * Incremental update * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Approximately complete ARM processor. No tests, no testing yet. * Fix artifact store reference * WIP * fixed cnf bicep template * fixed multiple helm/vhd/arm error; working vnfartifactmanifet; attempted vnfdefintiion with testing code still in; updated write bicep functions to take 2 arguments for vnf case * fixed cnfdef bicep; return correct type in cnf handler * Added outputfolder name to base handler; reorganised templates folder structure; added constants for cnf j2 templates; implemented build (tested with mocks) for cnf handler; implemented localfileACR artifact to_dict function; updated write for artifact_builder.py; added overwriting folder logic to defintion folder builder * renamed write manifest; updated nf templates to deploy initial resources; vnf templates working; vnf build commands working * added logic to cnf handler for manifest and definition supporting files + config mappings * fixed cnf params files; added render params for vnf * fix HelmChartInput issues * Merge arm-processor code into Jacob's branch (#127) * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * Initial ArmInputTemplate class * Outline ArmBuildProcessor hierarchy * Update VNF tests to keep built output for comparison * WIP, for sharing with Jacob * Incremental update * Approximately complete ARM processor. No tests, no testing yet. * Fix artifact store reference --------- Co-authored-by: Andy Churchard * added build deploy params schema to base handler; added functionality for deployParams to be added as supporting file for cnd * fix majority of linting issues * renamed write on base handler to render; added build logic for manifest and artifact list for vnf * added write manifest bicep and nf application to base handler; fixed helm config to produce correct bicep * fixed cnf bicep template * Initial ArmInputTemplate class * complete helm chart processor * almost finished NFD processor * WIP, for sharing with Jacob * Incremental update * added vnf config validation; improved comment spacing on input.json; fixed depends on format; changes type for optional params * added optional output file name param to generate config; removed misc print statements * Approximately complete ARM processor. No tests, no testing yet. * WIP * fixed multiple helm/vhd/arm error; working vnfartifactmanifet; attempted vnfdefintiion with testing code still in; updated write bicep functions to take 2 arguments for vnf case * fixed cnfdef bicep; return correct type in cnf handler * Added outputfolder name to base handler; reorganised templates folder structure; added constants for cnf j2 templates; implemented build (tested with mocks) for cnf handler; implemented localfileACR artifact to_dict function; updated write for artifact_builder.py; added overwriting folder logic to defintion folder builder * renamed write manifest; updated nf templates to deploy initial resources; vnf templates working; vnf build commands working * added logic to cnf handler for manifest and definition supporting files + config mappings * fixed cnf params files; added render params for vnf * added build deploy params schema to base handler; added functionality for deployParams to be added as supporting file for cnd * renamed write on base handler to render; added build logic for manifest and artifact list for vnf * fix up code * fix base input * run isort * implemented vnf build + removed test code; fixed arm processor * fixed templates * removed multiple vhds from vnf + fixed bicep; made path to mappings optional for cnf * renamed write file to render contents * fixed helm chart processor + small tidy up * commit for build handover; not working + handover notes sent separately * run isort * fix issues in input classes * add generic schema and value mapping generation and fix arm and vhd processors * push changes I forgot to push before xmas * checked out fixed build processors and inputs from jdarby/add-build-processors * fix values and schema generation * add RET generation for ARM templates * fix linting issues * made input file name constant + renamed inputs to match changes * checked out build processor changes * Moved _render_deployment_params_schema and _build_deploy_params_schema to nfd base handler; added default_config logic to cnf handler;created deploymentParamers for cnf + vnf * changed j2 template for vhd image name * HELM CHART PROCESSOR CHANGES! fixed registry value path and depends on profile in cnf template * fixed nsd artifact manifest, changed artifact type in all processers (PROCESSOR CHANGE) * implemented nsd build get artifact manifest and get artifact list * added base bicep to all handlers; edited j2 templates to note that base resources should be deployed first on publish; made all build use constants * added to_dict for localfileACRartifact * added source namespcae and source reigstry to artifact + in helm processor callinh remote arc artifacts * finish nfd processor * add copyright statement to helm processor file * linting * created common params folder (making new jsonbuilder), adding get params config to base', renamed get_config to get_input_config; aded logging to vnf; chnged custom.py publish inputs * partially ready commit; added cmd context to base + added some logic to nsds * added all parameters constant * tidied vnf handler; added common params logic to cnf * markups inc tidy up * renamed snake_case param * add nsd output config * taken some input and build processor changes from jdarby/build_processor branch * Add logging, improve doc string and fix mypy errors for inputs * fix 2 bugs introduced by merging buildprocessors and adding json deploy params * fixed how aosm client is passed to build; properly get nfdv object with api client * refactored vnf handler to create processors upfront * refactor cnf to instantiate processors upfront * Improve docstrings, add logging and fix linting issues for build processors * nsd temp commit * fix missed conflict * fix linting and formatting issue * rename + add constants + add new definition tempalte * simplified render bicep definiton contents + removes all constants for filenames from nf handlers and templates * build nsd bicep core working * fixed nsd bicep * Fix two minor CLI bugs (#129) * Fix bug where the build directory wasn't deleted * Fix bug where the json wasn't output correctly * fixed incorrect merge fix --------- Co-authored-by: Jordan * small fixes; removed location from nfd input, removed comma from vnd def template, fixed artifact version in nsds, fixed vhd validation * made cgs name constant to fix error in mappings * added store type to all the templates * tidy up + small markups * removed version state from the definition biceps * fixed bug where schemas and defaults were copied incorrectly * made artifact paths absolute in all cases except nsd nfs, where paht is relative to nsd output folder * added missing comments; renamed common params + edited its format; changed custom to take path * added existing back to all templates + descriptors of resources * First stab at doing helm template - check the changes here * Merge publish into temp wheel branch (#131) * Move artifact create from dict logic to instantiating code * Create CommandContext class to hold Azure clients and CLI options, and pass instance through to the delete and publish commands. * Mostly implemented and working publish. Lots of debug code still present. * Publish done and mostly tested. * Add some more logging. Remove help text referring to --order-params. * Update src/aosm/azext_aosm/common/artifact.py Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * fix merge conflicts --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Minor input improvements (#132) * fixed common params files for all * remove prints; remove sourcelocaldockerimage + add correct validation; edit input.json comments * fixed extract tar error * only create oras_client once - temp solution --------- Co-authored-by: Jordan Co-authored-by: Andy Churchard * edited input file (#133) Co-authored-by: Jordan * Add skip step * fixed getparamsconfig for vnf/nsd; added fixes for building correct python wheel * Achurchard/publish (#130) Markups from publish PR --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan Co-authored-by: jordlay <72226943+jordlay@users.noreply.github.com> * bug fixes for chart with subcharts * added ruamel * Use safe yaml loading * Move code out of import list * Fix errant config line bug * fixed deployparams error (#134) Co-authored-by: Jordan * fixed yaml load error * Change "path_to_mappings" to "default_values" * Add unit tests * remove the merge mistake * Delete a file --------- Co-authored-by: Jordan Co-authored-by: Chaos Chhapi Co-authored-by: Jacob <53872686+jddarby@users.noreply.github.com> Co-authored-by: Jacob Darby Co-authored-by: Andy Churchard Co-authored-by: jordlay <72226943+jordlay@users.noreply.github.com> Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Pk5/add image parsing (#137) * Add image parsing * Add unit tests * Fix small log issue * Minor markup edits * Improve unit tests * remove the test_utils file * update HISTORY.rst * update HISTORY file * Validate that helm chart values exist (#138) * add validation of the default values for helm charts * Add unit tests * update HISTORY.rst * Markups * Jl/nsd publish paths (#139) * fix for relative path issue for nsd publish; fix for deployment parameters schema being wrong * Add image parsing * LocalFileACRArtifact converts bicep to ARM before upload. * Remove TODO comment that is done. Refactor convert to bicep code. * Add unit tests * Fix small log issue * Minor markup edits * Add location to configObject passed to NF template. Value is hardcoded to that provided in the input.jsonc file. * Add note on using NF name instead of NSD name when creating NFDInput * Minor fixes: - check attribute exists before accessing it\n - better logging\n - reduce nesting * input file (jsonc) comment updates. * Fix NSD generate --output-file handling bug * Minor markups. --------- Co-authored-by: Jordan Co-authored-by: Patryk Kulik Co-authored-by: Andy Churchard * Bump version to 1.0.0b5 * Linting and typing (#141) Linting and type hinting fixes. * Temp 16feb wheel (#145) * added nexus arm processor; added nfvi type to vnfinput.json; added condition for calling each arm processor * pipe through nfvi type from vnf input to template; added nexus input; fixed mistakes in armprocessor * initial commit for children vnf handlers * added core and nexus config logic * fixed nexus input and nexus processor; fixed templates * generalised render manifest and render definition bicep functions to use one * added incorrect config error handling * added nexus flag to publish; added error handling for incorrect vnf type (if forgot to put nexus flag case); tidied nexus inputs * added vnf nexus base bicep; fixed vnf definition template; temp fix for image versions * moved render bicep to common/utils; replaced build base bicep with render bicep * general tidyup: removed prints, added return types * added nfvi type to nsds * removed old todos * refactored nexus handler, moved generate params code into processor * moved logic to base vnf handler; moved more logic to processor * fix template name in vnf j2 * fixed nexus image file * added nfvitype to nsd nf template by making new j2 + changing how nfd processor works * added nfvitype to nsd nf template by making new j2 + changing how nfd processor works * minor formatting * made vnfnexus a definition type; slight refactor of custom.py * removed nexus param, removed prints, added commented out test file * fixed flake8 + pylint issues * Base handler treats command inputs separately (#143) * refactored base handler init to treat inputs from different commands separately; added better validation * fixed error handling in base handler * fixed error typing --------- Co-authored-by: Jordan * mypy fixes * fixed customLocation id for vnf nexus * markups from review inc moving build manifest to parent vnf handler * added better docstrings * updated history.rst * Achurchard/fix helm chart upload (#144) * Use helm push for Helm charts (not oras push) * Logging --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan * bumped version * Bug: No type in schema (#148) * first fix for anyOf logic error msg * Update src/aosm/azext_aosm/build_processors/base_processor.py Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Update src/aosm/azext_aosm/build_processors/base_processor.py Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> --------- Co-authored-by: Jordan Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Bug: Nexus Image Version Must be Semver (#149) * added semver checking to input config validation; moved split image path to utils * changed semver regex; renamed function * fixed typo * markups * fix typo --------- Co-authored-by: Jordan * Create RG if it doesn't exist (#150) * add validation resource group exists function * tidied up code * Update src/aosm/azext_aosm/definition_folder/reader/definition_folder.py Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * renaming from markups --------- Co-authored-by: Jordan Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * Bug Fix: NFD and NSD Manifest Names Clash (#147) * added sa_manifest and acr_manifest property, added sa and acr to manifest names; added nsd to nsd manifest name * create manifest name from nf/nsd name instead of acr/sa; fixed docs strings --------- Co-authored-by: Jordan * Fix Nexus Linting + Add Unit Tests (#146) * fixed pylint + flake8 errors * fix mypy errors * fixed artifact builder tests * temp commit for unit testing * temp push of broken tests * added new mocks (not perfect) for vnfs£ * fixed artifact write failure * More mypy fixes * fixed style issues --------- Co-authored-by: Jordan Co-authored-by: Andy Churchard * Use ephemeral tempdir for generated helm package .tgz file. (#151) * Use ephemeral tempdir for generated helm package .tgz file. * Fix file_path bug if .tgz file was provided by user. --------- Co-authored-by: Andy Churchard --------- Co-authored-by: Jordan Co-authored-by: Andy Churchard Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> * add to changelog; bump version * added check for filepath before making absolute (#154) Co-authored-by: Jordan * Template Parameters Bug (#156) * converted vhd parameters to camel case to match what aosm expects * markups + fixed apiVersion --------- Co-authored-by: Jordan * fixed config comment (#153) Co-authored-by: Jordan * removed image name parameter, added imageName to default config for arm and vhd input (#157) Co-authored-by: Jordan * bumped version and updated changelog (#158) Co-authored-by: Jordan * Improve Unit Testing (#152) * moved working tests to new folder structure; added empty tests for processors and build * added empty generate cofnig tests; fixed and moved bicep,artifact and definitino folder builder tests * added nexus arm tests; removed outdated vnf mocks * added azure core tests for every function (including base) * fixed up core and nexus arm processor * fixed up arm processors; added logging to tests * added arm template tests * added test from copilot learning day * changed assert * removed build folder; added json dump to artifact and definition tests; improved arm processor testing * removed mock open and replaced with new templates * fixed linting --------- Co-authored-by: Jordan * Fix bool in schema bug (#160) * check object is a dict before looking for `type` key. --------- Co-authored-by: Jordan * Use common.utils function to check if docker/helm is installed (code deduplication). (#161) * changed configuration type from secret to open; updated changelog (#162) Co-authored-by: Jordan * Support using multiple images sources in our CLI (#142) * Initial stab at making the multiple registry support work * Update TODO * update TODO * merge fixes * add logic to find registry credentials * Code cleanup and adding unit tests * updates to the registry class * In Progress work * Working publish * Move _push_image_from_local_registry and copy_image into the registry class * tidy up changes * Namespace and style fixes * Minor input config change * unit tests * Do not push image namespace to target ACR * Make sure not to break the nexus part of the code * Update CHANGELOG * Fix error not being caught correctly * Minor comment change * Markups * Update warning * Change how we treat namespace and find images * markups * modify how we create the registry list --------- Co-authored-by: Andy Churchard * Fix integration tests (#163) * Initial stab at making the multiple registry support work * Update TODO * update TODO * merge fixes * add logic to find registry credentials * Code cleanup and adding unit tests * updates to the registry class * In Progress work * Working publish * Move _push_image_from_local_registry and copy_image into the registry class * tidy up changes * Namespace and style fixes * Minor input config change * unit tests * Do not push image namespace to target ACR * Make sure not to break the nexus part of the code * Update CHANGELOG * initial commit for integration tests * Fix error not being caught correctly * Minor comment change * Fix integration tests * Moving files around * Working vnf integration test * Comment out test that is not working * Clean up the structure of the integration tests * Delete unnecessary tests * Fix CNF test and move a util function * fix other integration tests * Delete the broken tests folder * Fix the vnf test * move files around * Improve the update_input_file function * improve the nsd test * Move files around and improve the nsd output * remove unnecessary line * Update recording processors and recordings * Fix linting issues * bad merge change * Update the nsd output with Open CGVs * Fix few unit tests broken by merge * Markups --------- Co-authored-by: Andy Churchard Co-authored-by: Jordan * Add docker dependency option to oras in setup.py (#164) Co-authored-by: Andy Churchard * Revert "Add docker dependency option to oras in setup.py (#164)" (#165) This reverts commit 9b6fcdecc2e0619e0c24c545e524b27b6b14c7f0. * update history + bump version (#166) Co-authored-by: Jordan * hot fix: anyOf bug * hotfix: fixed 2 style errors * bumped version to 2 (#170) Co-authored-by: Jordan * Fix linting + tests for release (#171) * removed delete helptext and reran tests live * fixed linting --------- Co-authored-by: Jordan * added license headers to fix release jobs (#172) Co-authored-by: Jordan * Add Webhook (#159) * initial attempt at adding roleOverride * add comma * removed comma as breaks single nf application * remove prints * fixed linting + updated history * fixed bug in nfd processor for vnfs; updated nsd tests * fixed lives tests * markups * markups2 --------- Co-authored-by: Jordan * Expose All PRs (#173) * Refactoring related to user customisable param exposure (#167) * Fix: Change 'deploymentParameters' to 'deployParameters' * Refactoring including: - Remove minor inconsistencies between related methods - Use named parameters in method calls to improve readability - Rename variables and methods to be more descriptive/accurate - Improve some exception messages - Make log messages clearer - Improve code comments - Improve type handling --------- Co-authored-by: Andy Churchard * Allow all parameters to be exposed to operator (#169) * Fix: Change 'deploymentParameters' to 'deployParameters' * Refactoring including: - Remove minor inconsistencies between related methods - Use named parameters in method calls to improve readability - Rename variables and methods to be more descriptive/accurate - Improve some exception messages - Make log messages clearer - Improve code comments - Improve type handling * Comment out _find_image_pull_secrets_values_paths(). * Add expose_all function for NFDs. Includes extensive renaming of variables and methods, new code comments and docstrings. * Add expose_all_params to nfd-input.jsonc and plumb through. * Fix linting + tests for release (#171) * removed delete helptext and reran tests live * fixed linting --------- Co-authored-by: Jordan * added license headers to fix release jobs (#172) Co-authored-by: Jordan * Markups * Add Webhook (#159) * initial attempt at adding roleOverride * add comma * removed comma as breaks single nf application * remove prints * fixed linting + updated history * fixed bug in nfd processor for vnfs; updated nsd tests * fixed lives tests * markups * markups2 --------- Co-authored-by: Jordan * fixed line too long * markups including removing multiple instances * fixed all unit tests based on new param (expose_all), removed param (multiple_instances), refactoring of deploymentParameters to deployParams, and removal of using values.schema.json * ran live tests + removed dependsOn * added getting defaults from arm template + added special logic for when defaults are rg().location * fixed tests + fixed styling * ran live tests --------- Co-authored-by: Andy Churchard Co-authored-by: jordlay <72226943+jordlay@users.noreply.github.com> Co-authored-by: Jordan Co-authored-by: Patryk Kulik --------- Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> Co-authored-by: Andy Churchard Co-authored-by: Jordan Co-authored-by: Patryk Kulik * updated history * Fix minor config bugs (#174) * renamed nfdvName to nfdv * changed comment for name in NF RET for nsd input * added ArmTemplate optional comment to NSD input config * changed nfdv examples in mock cgvs * added 1:1 mapping between nfdvs and nfvisFromSite * removed nfvi_type from nsd input config * markups * fixed tests + styling; markups --------- Co-authored-by: Jordan * Help text additions (#175) * Add long summary, parameters and examples to help text. * Remove parameters from help file. Instead, use the help defined in _params.py. Minor updates to text of latter. * Fix lines too long (#176) * updated changelog (#180) * updated changelog * markups --------- Co-authored-by: Jordan * Autogen CLI: (#181) - Add examples - Minor rewording - Ensure all commands are preview * Add quotes to the CNF input config image sources comment (#182) * Bump jinja2 to 3.1.4. (#190) * Remove azure-storage-blob from dependencies. (#192) * Pk5/fix integration tests for release (#195) * Add retry logic to fix integration tests * Modify the publisher name because previous name is used up * Regenerate the recordings * Re-record test (#196) * ran vnf test live (#197) Co-authored-by: Jordan * Make the vnf test live only (#198) --------- Co-authored-by: Jordan Co-authored-by: Chaos Chhapi Co-authored-by: Jacob <53872686+jddarby@users.noreply.github.com> Co-authored-by: Jacob Darby Co-authored-by: Andy Churchard Co-authored-by: patrykkulik-microsoft <116072282+patrykkulik-microsoft@users.noreply.github.com> Co-authored-by: Cyclam <95434717+Cyclam@users.noreply.github.com> Co-authored-by: Patryk Kulik --- src/aosm/HISTORY.rst | 33 + src/aosm/README.md | 49 +- src/aosm/azext_aosm/__init__.py | 9 + src/aosm/azext_aosm/_client_factory.py | 4 +- src/aosm/azext_aosm/_configuration.py | 771 --- src/aosm/azext_aosm/_help.py | 70 +- src/aosm/azext_aosm/_params.py | 157 +- src/aosm/azext_aosm/aaz/__init__.py | 6 + src/aosm/azext_aosm/aaz/latest/__init__.py | 6 + .../azext_aosm/aaz/latest/aosm/__cmd_group.py | 24 + .../azext_aosm/aaz/latest/aosm/__init__.py | 11 + .../aaz/latest/aosm/publisher/__cmd_group.py | 24 + .../aaz/latest/aosm/publisher/__init__.py | 11 + .../artifact_manifest/__cmd_group.py | 24 + .../publisher/artifact_manifest/__init__.py | 15 + .../aosm/publisher/artifact_manifest/_list.py | 258 + .../artifact_manifest/_list_credential.py | 235 + .../aosm/publisher/artifact_manifest/_show.py | 259 + .../artifact_manifest/_update_state.py | 241 + .../publisher/artifact_store/__cmd_group.py | 24 + .../aosm/publisher/artifact_store/__init__.py | 11 + .../artifact_store/artifact/__cmd_group.py | 24 + .../artifact_store/artifact/__init__.py | 12 + .../artifact_store/artifact/_list.py | 228 + .../artifact/version/__cmd_group.py | 24 + .../artifact/version/__init__.py | 13 + .../artifact_store/artifact/version/_list.py | 253 + .../artifact/version/_update_state.py | 302 + .../configuration_group_schema/__cmd_group.py | 24 + .../configuration_group_schema/__init__.py | 14 + .../configuration_group_schema/_list.py | 234 + .../configuration_group_schema/_show.py | 234 + .../_update_state.py | 227 + .../__cmd_group.py | 24 + .../network_function_definition/__init__.py | 11 + .../version/__cmd_group.py | 24 + .../version/__init__.py | 14 + .../version/_list.py | 626 +++ .../version/_show.py | 627 +++ .../version/_update_state.py | 241 + .../network_service_design/__cmd_group.py | 24 + .../network_service_design/__init__.py | 11 + .../version/__cmd_group.py | 24 + .../version/__init__.py | 14 + .../network_service_design/version/_list.py | 358 ++ .../network_service_design/version/_show.py | 359 ++ .../version/_update_state.py | 241 + src/aosm/azext_aosm/azext_metadata.json | 2 +- .../{delete => build_processors}/__init__.py | 0 .../build_processors/arm_processor.py | 250 + .../build_processors/base_processor.py | 319 ++ .../build_processors/helm_chart_processor.py | 402 ++ .../build_processors/nexus_image_processor.py | 190 + .../build_processors/nfd_processor.py | 341 ++ .../build_processors/vhd_processor.py | 203 + .../{deploy => cli_handlers}/__init__.py | 0 .../cli_handlers/onboarding_base_handler.py | 347 ++ .../cli_handlers/onboarding_cnf_handler.py | 317 ++ .../onboarding_core_vnf_handler.py | 203 + .../onboarding_nexus_vnf_handler.py | 158 + .../onboarding_nfd_base_handler.py | 18 + .../cli_handlers/onboarding_nsd_handler.py | 333 ++ .../cli_handlers/onboarding_vnf_handler.py | 180 + src/aosm/azext_aosm/commands.py | 23 +- .../{generate_nfd => common}/__init__.py | 0 src/aosm/azext_aosm/common/artifact.py | 603 ++ src/aosm/azext_aosm/common/command_context.py | 25 + .../azext_aosm/{util => common}/constants.py | 103 +- src/aosm/azext_aosm/common/exceptions.py | 29 + src/aosm/azext_aosm/common/registry.py | 469 ++ .../cnf}/cnfartifactmanifest.bicep.j2 | 16 +- .../common/templates/cnf/cnfbase.bicep | 35 + .../templates/cnf}/cnfdefinition.bicep.j2 | 40 +- .../cnf/cnfhelmtemplateerrors.txt.j2 | 14 + .../common/templates/nsd/nf_template.bicep.j2 | 49 + .../nsd/nsdartifactmanifest.bicep.j2 | 42 + .../common/templates/nsd/nsdbase.bicep | 35 + .../templates/nsd/nsddefinition.bicep.j2} | 39 +- .../vnf/vnfartifactmanifest.bicep.j2 | 78 + .../common/templates/vnf/vnfbase.bicep | 47 + .../templates/vnf/vnfdefinition.bicep.j2 | 132 + .../common/templates/vnf/vnfnexusbase.bicep | 35 + src/aosm/azext_aosm/common/utils.py | 292 + .../__init__.py | 0 .../configuration_models/common_input.py | 46 + .../common_parameters_config.py | 58 + .../onboarding_base_input_config.py | 61 + .../onboarding_cnf_input_config.py | 99 + .../onboarding_nfd_base_input_config.py | 55 + .../onboarding_nsd_input_config.py | 235 + .../onboarding_vnf_input_config.py | 220 + src/aosm/azext_aosm/custom.py | 580 +- .../{util => definition_folder}/__init__.py | 0 .../definition_folder/builder/__init__.py | 5 + .../builder/artifact_builder.py | 44 + .../definition_folder/builder/base_builder.py | 36 + .../builder/bicep_builder.py | 29 + .../builder/definition_folder_builder.py | 68 + .../definition_folder/builder/json_builder.py | 27 + .../builder/local_file_builder.py | 23 + .../definition_folder/reader/__init__.py | 5 + .../reader/artifact_definition.py | 67 + .../reader/base_definition.py | 34 + .../reader/bicep_definition.py | 230 + .../reader/definition_folder.py | 111 + src/aosm/azext_aosm/delete/delete.py | 345 -- src/aosm/azext_aosm/deploy/artifact.py | 645 --- .../azext_aosm/deploy/artifact_manifest.py | 169 - src/aosm/azext_aosm/deploy/deploy_with_arm.py | 731 --- src/aosm/azext_aosm/deploy/pre_deploy.py | 444 -- .../generate_nfd/cnf_nfd_generator.py | 850 --- .../generate_nfd/nfd_generator_base.py | 25 - .../generate_nfd/vnf_nfd_generator.py | 340 -- src/aosm/azext_aosm/generate_nsd/nf_ret.py | 185 - .../azext_aosm/generate_nsd/nsd_generator.py | 261 - .../artifact_manifest_template.bicep | 39 - .../templates/nf_template.bicep.j2 | 81 - src/aosm/azext_aosm/inputs/__init__.py | 4 + .../azext_aosm/inputs/arm_template_input.py | 112 + src/aosm/azext_aosm/inputs/base_input.py | 49 + .../azext_aosm/inputs/helm_chart_input.py | 448 ++ .../azext_aosm/inputs/nexus_image_input.py | 57 + src/aosm/azext_aosm/inputs/nfd_input.py | 166 + src/aosm/azext_aosm/inputs/vhd_file_input.py | 95 + ...-nf-agent-cnf-template-invalid-chart.jsonc | 18 + .../input-nf-agent-cnf-template.jsonc | 18 + .../cnf_mocks/nginxdemo-0.1.0.tgz | Bin .../cnf_input_template.jsonc | 29 + .../cnf_nsd_input_template.jsonc | 19 + .../input_multi_nf_nsd.jsonc | 47 + .../input_multiple_instances.jsonc | 29 + .../mock_input_templates/nsd_core_input.jsonc | 29 + .../vnf_input_template.jsonc | 33 + .../vnf_input_with_sas_token_template.jsonc | 46 + .../vnf_nsd_input_template.jsonc | 29 + .../vnf_mocks/ubuntu.vhd | 0 .../vnf_mocks/ubuntu_template.json | 0 .../{ => integration_tests}/metaschema.json | 0 .../metaschema_modified.json | 0 .../test_build/all_deploy.parameters.json | 10 + .../test_build/artifactManifest/deploy.bicep | 40 + .../test_build/artifacts/artifacts.json | 9 + .../test_build/artifacts/ubuntu-vm.bicep | 42 + .../nsd_output/test_build/base/deploy.bicep | 35 + .../nsd_output/test_build/index.json | 22 + .../nsdDefinition/config-group-schema.json} | 26 +- .../test_build/nsdDefinition/deploy.bicep} | 25 +- .../nsdDefinition/ubuntu-mappings.json | 12 + .../all_deploy.parameters.json | 10 + .../artifactManifest/deploy.bicep | 40 + .../artifacts/artifacts.json | 9 + .../artifacts/ubuntu-vm.bicep | 42 + .../base/deploy.bicep | 35 + .../test_build_multiple_instances/index.json | 22 + .../nsdDefinition/config-group-schema.json | 46 + .../nsdDefinition/deploy.bicep} | 25 +- .../nsdDefinition/ubuntu-mappings.json | 12 + .../artifact_manifest.bicep | 0 .../nginx-nfdg_config_mapping.json | 6 +- .../ubuntu-nfdg_config_mapping.json | 6 +- .../nginx-nfdg_nf.bicep | 8 +- .../nsd_definition.bicep | 0 .../schemas/multinf_ConfigGroupSchema.json | 8 +- .../ubuntu-nfdg_nf.bicep | 8 +- .../scenario_tests}/recording_processors.py | 32 +- .../test_cnf_nfd_build_and_publish.yaml | 1653 ++++++ .../test_vnf_nsd_build_and_publish.yaml | 4854 +++++++++++++++++ .../test_aosm_cnf_build_and_publish.py | 141 + .../test_aosm_vnf_build_and_publish.py | 125 + .../latest/integration_tests/test_cnf.py | 83 + .../{ => integration_tests}/test_nsd.py | 277 +- .../latest/integration_tests/test_vnf.py | 123 + .../tests/latest/integration_tests/utils.py | 59 + .../no-params-template.json | 96 + .../mock_arm_templates/simple-template.json | 102 + .../ubuntu-template.json | 0 .../nf-agent-cnf-invalid/.helmignore | 23 + .../nf-agent-cnf-invalid/Chart.yaml | 24 + .../nf-agent-cnf-invalid/templates/NOTES.txt | 22 + .../templates/_helpers.tpl | 62 + .../templates/deployment.yaml | 71 + .../nf-agent-cnf-invalid/templates/hpa.yaml | 28 + .../templates/ingress.yaml | 61 + .../templates/nf-agent-config-map.yaml | 27 + .../templates/service.yaml | 15 + .../templates/serviceaccount.yaml | 12 + .../templates/tests/test-connection.yaml | 15 + .../nf-agent-cnf-invalid/values.mappings.yaml | 89 + .../nf-agent-cnf-invalid/values.schema.json | 193 + .../input-nf-agent-cnf-template.jsonc | 18 + .../latest/mock_cnf/input-nf-agent-cnf.json | 19 - .../latest/mock_cnf/input-nfconfigchart.json | 21 - .../latest/mock_cnf/input-nfconfigchart.jsonc | 29 + .../latest/mock_cnf/invalid_config_file.json | 3 +- .../nf-agent-cnf-helm_template_output.yaml | 145 + .../input_with_filepath copy.json} | 0 .../mock_core_vnf/input_with_filepath.jsonc | 56 + .../latest/mock_core_vnf/input_with_fp.json | 18 + .../input_with_sas.json | 0 .../mock_core_vnf/input_with_sas_token.json | 21 + .../latest/mock_core_vnf/ubuntu-template.json | 118 + .../input_with_filepath.json} | 14 +- .../input_with_fp.json} | 14 +- .../latest/mock_nexus_vnf/input_with_sas.json | 21 + .../mock_nexus_vnf/input_with_sas_token.json | 21 + .../mock_nexus_vnf/ubuntu-template.json | 118 + .../tests/latest/mock_nsd/input.json | 20 - .../latest/mock_nsd/input_multi_nf_nsd.json | 29 - .../mock_nsd/input_multiple_instances.json | 20 - .../ubuntu-vm-nfdg_config_mapping.json | 9 - .../schemas/ubuntu_ConfigGroupSchema.json | 45 - .../test_build/ubuntu-vm-nfdg_nf.bicep | 72 - .../artifact_manifest.bicep | 39 - .../ubuntu-vm-nfdg_config_mapping.json | 7 - .../ubuntu-vm-nfdg_nf.bicep | 72 - .../artifact_manifest.bicep | 39 - .../cnf_input_template.json | 20 - .../cnf_nsd_input_template.json | 20 - .../mock_input_templates/nsd_input.json | 20 - .../vnf_nsd_input_template.json | 20 - .../tests/latest/schemas/schema-1-values.yaml | 37 + .../tests/latest/schemas/schema-1.json | 136 + .../test_aosm_cnf_publish_and_delete.py | 116 - .../tests/latest/test_aosm_scenario.py | 39 - .../test_aosm_vnf_publish_and_delete.py | 116 - src/aosm/azext_aosm/tests/latest/test_cnf.py | 67 - src/aosm/azext_aosm/tests/latest/test_vnf.py | 89 - .../unit_test/input_files/nsd-input.jsonc | 50 + .../test_artifact_builder.py | 42 + .../test_bicep_builder.py | 62 + .../test_definition_folder_builder.py | 52 + .../test_cnf_generate_config.py | 0 .../test_core_vnf_generate_config.py | 0 .../test_nexus_vnf_generate_config.py | 0 .../test_nsd_generate_config.py | 0 .../test_inputs/test_arm_template_input.py | 171 + .../test_inputs/test_helm_chart_input.py | 152 + .../test_core_arm_processor.py | 177 + .../test_helm_chart_processor.py | 44 + .../test_nexus_arm_processor.py | 177 + .../test_nexus_image_processor.py | 0 .../test_processors/test_vhd_processor.py | 0 .../tests/latest/unit_test/test_registry.py | 227 + .../tests/latest/unit_test/test_utils.py | 0 .../configMappings/templateParameters.json | 8 + .../configMappings/vhdParameters.json | 3 + .../schemas/deployParameters.json | 22 + .../schemas/optionalDeploymentParameters.txt | 13 + .../vnfartifactmanifests.bicep | 0 .../vnfdefinition.bicep | 2 +- .../configMappings/templateParameters.json | 8 + .../configMappings/vhdParameters.json | 6 + .../schemas/deployParameters.json | 22 + .../schemas/optionalDeploymentParameters.txt | 13 + .../vnfartifactmanifests.bicep | 68 + .../vnfdefinition.bicep | 103 + .../azext_aosm/util/management_clients.py | 22 - src/aosm/azext_aosm/util/utils.py | 25 - src/aosm/setup.py | 13 +- 259 files changed, 24263 insertions(+), 6746 deletions(-) delete mode 100644 src/aosm/azext_aosm/_configuration.py create mode 100644 src/aosm/azext_aosm/aaz/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list_credential.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_show.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_update_state.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_update_state.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_show.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_update_state.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_show.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_update_state.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__cmd_group.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__init__.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_list.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_show.py create mode 100644 src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_update_state.py rename src/aosm/azext_aosm/{delete => build_processors}/__init__.py (100%) create mode 100644 src/aosm/azext_aosm/build_processors/arm_processor.py create mode 100644 src/aosm/azext_aosm/build_processors/base_processor.py create mode 100644 src/aosm/azext_aosm/build_processors/helm_chart_processor.py create mode 100644 src/aosm/azext_aosm/build_processors/nexus_image_processor.py create mode 100644 src/aosm/azext_aosm/build_processors/nfd_processor.py create mode 100644 src/aosm/azext_aosm/build_processors/vhd_processor.py rename src/aosm/azext_aosm/{deploy => cli_handlers}/__init__.py (100%) create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_base_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_cnf_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_core_vnf_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_nexus_vnf_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_nfd_base_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_nsd_handler.py create mode 100644 src/aosm/azext_aosm/cli_handlers/onboarding_vnf_handler.py rename src/aosm/azext_aosm/{generate_nfd => common}/__init__.py (100%) create mode 100644 src/aosm/azext_aosm/common/artifact.py create mode 100644 src/aosm/azext_aosm/common/command_context.py rename src/aosm/azext_aosm/{util => common}/constants.py (56%) create mode 100644 src/aosm/azext_aosm/common/exceptions.py create mode 100644 src/aosm/azext_aosm/common/registry.py rename src/aosm/azext_aosm/{generate_nfd/templates => common/templates/cnf}/cnfartifactmanifest.bicep.j2 (65%) create mode 100644 src/aosm/azext_aosm/common/templates/cnf/cnfbase.bicep rename src/aosm/azext_aosm/{generate_nfd/templates => common/templates/cnf}/cnfdefinition.bicep.j2 (50%) create mode 100644 src/aosm/azext_aosm/common/templates/cnf/cnfhelmtemplateerrors.txt.j2 create mode 100644 src/aosm/azext_aosm/common/templates/nsd/nf_template.bicep.j2 create mode 100644 src/aosm/azext_aosm/common/templates/nsd/nsdartifactmanifest.bicep.j2 create mode 100644 src/aosm/azext_aosm/common/templates/nsd/nsdbase.bicep rename src/aosm/azext_aosm/{generate_nsd/templates/nsd_template.bicep.j2 => common/templates/nsd/nsddefinition.bicep.j2} (76%) create mode 100644 src/aosm/azext_aosm/common/templates/vnf/vnfartifactmanifest.bicep.j2 create mode 100644 src/aosm/azext_aosm/common/templates/vnf/vnfbase.bicep create mode 100644 src/aosm/azext_aosm/common/templates/vnf/vnfdefinition.bicep.j2 create mode 100644 src/aosm/azext_aosm/common/templates/vnf/vnfnexusbase.bicep create mode 100644 src/aosm/azext_aosm/common/utils.py rename src/aosm/azext_aosm/{generate_nsd => configuration_models}/__init__.py (100%) create mode 100644 src/aosm/azext_aosm/configuration_models/common_input.py create mode 100644 src/aosm/azext_aosm/configuration_models/common_parameters_config.py create mode 100644 src/aosm/azext_aosm/configuration_models/onboarding_base_input_config.py create mode 100644 src/aosm/azext_aosm/configuration_models/onboarding_cnf_input_config.py create mode 100644 src/aosm/azext_aosm/configuration_models/onboarding_nfd_base_input_config.py create mode 100644 src/aosm/azext_aosm/configuration_models/onboarding_nsd_input_config.py create mode 100644 src/aosm/azext_aosm/configuration_models/onboarding_vnf_input_config.py rename src/aosm/azext_aosm/{util => definition_folder}/__init__.py (100%) create mode 100644 src/aosm/azext_aosm/definition_folder/builder/__init__.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/artifact_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/base_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/bicep_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/definition_folder_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/json_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/builder/local_file_builder.py create mode 100644 src/aosm/azext_aosm/definition_folder/reader/__init__.py create mode 100644 src/aosm/azext_aosm/definition_folder/reader/artifact_definition.py create mode 100644 src/aosm/azext_aosm/definition_folder/reader/base_definition.py create mode 100644 src/aosm/azext_aosm/definition_folder/reader/bicep_definition.py create mode 100644 src/aosm/azext_aosm/definition_folder/reader/definition_folder.py delete mode 100644 src/aosm/azext_aosm/delete/delete.py delete mode 100644 src/aosm/azext_aosm/deploy/artifact.py delete mode 100644 src/aosm/azext_aosm/deploy/artifact_manifest.py delete mode 100644 src/aosm/azext_aosm/deploy/deploy_with_arm.py delete mode 100644 src/aosm/azext_aosm/deploy/pre_deploy.py delete mode 100644 src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py delete mode 100644 src/aosm/azext_aosm/generate_nfd/nfd_generator_base.py delete mode 100644 src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py delete mode 100644 src/aosm/azext_aosm/generate_nsd/nf_ret.py delete mode 100644 src/aosm/azext_aosm/generate_nsd/nsd_generator.py delete mode 100644 src/aosm/azext_aosm/generate_nsd/templates/artifact_manifest_template.bicep delete mode 100644 src/aosm/azext_aosm/generate_nsd/templates/nf_template.bicep.j2 create mode 100644 src/aosm/azext_aosm/inputs/__init__.py create mode 100644 src/aosm/azext_aosm/inputs/arm_template_input.py create mode 100644 src/aosm/azext_aosm/inputs/base_input.py create mode 100644 src/aosm/azext_aosm/inputs/helm_chart_input.py create mode 100644 src/aosm/azext_aosm/inputs/nexus_image_input.py create mode 100644 src/aosm/azext_aosm/inputs/nfd_input.py create mode 100644 src/aosm/azext_aosm/inputs/vhd_file_input.py create mode 100644 src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template-invalid-chart.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template.jsonc rename src/aosm/azext_aosm/tests/latest/{scenario_test_mocks => integration_tests/integration_test_mocks}/cnf_mocks/nginxdemo-0.1.0.tgz (100%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_input_template.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_nsd_input_template.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multi_nf_nsd.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multiple_instances.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/nsd_core_input.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_template.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_with_sas_token_template.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_nsd_input_template.jsonc rename src/aosm/azext_aosm/tests/latest/{scenario_test_mocks => integration_tests/integration_test_mocks}/vnf_mocks/ubuntu.vhd (100%) rename src/aosm/azext_aosm/tests/latest/{scenario_test_mocks => integration_tests/integration_test_mocks}/vnf_mocks/ubuntu_template.json (100%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/metaschema.json (100%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/metaschema_modified.json (100%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/all_deploy.parameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifactManifest/deploy.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/artifacts.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/ubuntu-vm.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/base/deploy.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/index.json rename src/aosm/azext_aosm/tests/latest/{nsd_output/test_build_multiple_instances/schemas/ubuntu_ConfigGroupSchema.json => integration_tests/nsd_output/test_build/nsdDefinition/config-group-schema.json} (52%) rename src/aosm/azext_aosm/tests/latest/{nsd_output/test_build_multiple_instances/nsd_definition.bicep => integration_tests/nsd_output/test_build/nsdDefinition/deploy.bicep} (83%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/ubuntu-mappings.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/all_deploy.parameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifactManifest/deploy.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/artifacts.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/ubuntu-vm.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/base/deploy.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/index.json create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/config-group-schema.json rename src/aosm/azext_aosm/tests/latest/{nsd_output/test_build/nsd_definition.bicep => integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/deploy.bicep} (83%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/ubuntu-mappings.json rename src/aosm/azext_aosm/tests/latest/{nsd_output/test_build => integration_tests/nsd_output/test_build_multiple_nfs}/artifact_manifest.bicep (100%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json (79%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json (73%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep (92%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/nsd_definition.bicep (100%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json (94%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep (91%) rename src/aosm/azext_aosm/tests/latest/{ => integration_tests/scenario_tests}/recording_processors.py (78%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_cnf_nfd_build_and_publish.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_vnf_nsd_build_and_publish.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_cnf_build_and_publish.py create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_vnf_build_and_publish.py create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/test_cnf.py rename src/aosm/azext_aosm/tests/latest/{ => integration_tests}/test_nsd.py (54%) create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/test_vnf.py create mode 100644 src/aosm/azext_aosm/tests/latest/integration_tests/utils.py create mode 100644 src/aosm/azext_aosm/tests/latest/mock_arm_templates/no-params-template.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_arm_templates/simple-template.json rename src/aosm/azext_aosm/tests/latest/{mock_vnf => mock_arm_templates}/ubuntu-template.json (100%) create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/.helmignore create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/Chart.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/NOTES.txt create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/_helpers.tpl create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/deployment.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/hpa.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/ingress.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/nf-agent-config-map.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/service.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/serviceaccount.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/tests/test-connection.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.mappings.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.schema.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf-template.jsonc delete mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf.json delete mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/mock_cnf/nf-agent-cnf-helm_template_output.yaml rename src/aosm/azext_aosm/tests/latest/{mock_vnf/input_with_fp.json => mock_core_vnf/input_with_filepath copy.json} (100%) create mode 100644 src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_fp.json rename src/aosm/azext_aosm/tests/latest/{mock_vnf => mock_core_vnf}/input_with_sas.json (100%) create mode 100644 src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas_token.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_core_vnf/ubuntu-template.json rename src/aosm/azext_aosm/tests/latest/{scenario_test_mocks/mock_input_templates/vnf_input.json => mock_nexus_vnf/input_with_filepath.json} (53%) rename src/aosm/azext_aosm/tests/latest/{scenario_test_mocks/mock_input_templates/vnf_input_template.json => mock_nexus_vnf/input_with_fp.json} (52%) create mode 100644 src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas_token.json create mode 100644 src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/ubuntu-template.json delete mode 100644 src/aosm/azext_aosm/tests/latest/mock_nsd/input.json delete mode 100644 src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json delete mode 100644 src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build/configMappings/ubuntu-vm-nfdg_config_mapping.json delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build/schemas/ubuntu_ConfigGroupSchema.json delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build/ubuntu-vm-nfdg_nf.bicep delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/artifact_manifest.bicep delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/configMappings/ubuntu-vm-nfdg_config_mapping.json delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/ubuntu-vm-nfdg_nf.bicep delete mode 100644 src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep delete mode 100644 src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_input_template.json delete mode 100644 src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json delete mode 100644 src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/nsd_input.json delete mode 100644 src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json create mode 100644 src/aosm/azext_aosm/tests/latest/schemas/schema-1-values.yaml create mode 100644 src/aosm/azext_aosm/tests/latest/schemas/schema-1.json delete mode 100644 src/aosm/azext_aosm/tests/latest/test_aosm_cnf_publish_and_delete.py delete mode 100644 src/aosm/azext_aosm/tests/latest/test_aosm_scenario.py delete mode 100644 src/aosm/azext_aosm/tests/latest/test_aosm_vnf_publish_and_delete.py delete mode 100644 src/aosm/azext_aosm/tests/latest/test_cnf.py delete mode 100644 src/aosm/azext_aosm/tests/latest/test_vnf.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/input_files/nsd-input.jsonc create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_artifact_builder.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_bicep_builder.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_definition_folder_builder.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_cnf_generate_config.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_core_vnf_generate_config.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nexus_vnf_generate_config.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nsd_generate_config.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_arm_template_input.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_helm_chart_input.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_core_arm_processor.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_helm_chart_processor.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_arm_processor.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_image_processor.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_vhd_processor.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_registry.py create mode 100644 src/aosm/azext_aosm/tests/latest/unit_test/test_utils.py create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/templateParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/deployParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt rename src/aosm/azext_aosm/{generate_nfd/templates => tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template}/vnfartifactmanifests.bicep (100%) rename src/aosm/azext_aosm/{generate_nfd/templates => tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template}/vnfdefinition.bicep (97%) create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/templateParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/deployParameters.json create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep create mode 100644 src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfdefinition.bicep delete mode 100644 src/aosm/azext_aosm/util/management_clients.py delete mode 100644 src/aosm/azext_aosm/util/utils.py diff --git a/src/aosm/HISTORY.rst b/src/aosm/HISTORY.rst index 77e225d5093..2741f715480 100644 --- a/src/aosm/HISTORY.rst +++ b/src/aosm/HISTORY.rst @@ -3,6 +3,39 @@ Release History =============== +2.0.0b1 +++++++++ +* Renamed nfdvName to nfdv in CGVs +* Added useful comments to input files +* Added 1:1 mapping between NFVIsFromSite and NF RETs +* Added expose_all parameter in input file to expose all parameters in deployParameters and CGS +* Removed multiple_instances and depends_on from input file +* Added: mutating webhook for injectArtifactStoreDetails +* Added: Users can specify multiple image sources from all types of registries (not just ACRs). General improvements in how CNF image sources are handled. +* Fixed: Namespace appeared twice in the `artifacts.json` file, leading to errors in the publish step of the CLI. +* Changed configurationType for NF Resources from Secret to Open +* Removed imageName from deployParameters +* Removed image name parameter from input file +* Fixed camel casing of VHD Parameters +* Fixed blob sas url bug +* Edited comment in input file to reflect RGs are created if they do not exist +* Added creating RG if it does not exist +* Removed use of permanent temp file for helm package +* Fixed: helm charts not uploading correctly +* Added creation of resource groups if does not exist +* Fixed: Manifest name built from ACR name, so clashes +* Fixed: Nexus image version must be semver +* Fixed: Sensible error when no type given in helm chart schema +* Fixed: customLocation missing from Nexus +* Fixed: helm charts not uploading correctly +* Added: Nexus support +* Add `publisher` command group for management of publisher resources. +* Changed the name of the `path_to_mappings` parameter in the CNF input file to `default_values` +* Added a `helm template` validation step to the `az aosm nfd build` command for the `cnf` definition type +* Added validation of the values file for helm charts when using the `az aosm nfd build` command for the `cnf` definition type +* Fixed helm chart image parsing in the `az aosm nfd build` command for the `cnf` definition type. This means that the images can now be extracted correctly from the helm chart. +* Fixed: infinite loop bug when retrying failed artifact uploads to the ACR + 1.0.0b4 ++++++++ * Fixed: Remove check for Allow-2023-09-01 feature flag that is no longer required (Bug #1063964) diff --git a/src/aosm/README.md b/src/aosm/README.md index ac1b4c62b03..b2e8a784965 100644 --- a/src/aosm/README.md +++ b/src/aosm/README.md @@ -49,12 +49,10 @@ For CNFs you must have these packages installed on the machine you are running t - `docker` installed only in some circumstances, those being if the source image is in your local docker repository, or you do not have subscription-wide permissions required to push charts and images. See the remainder of this section for further details. Docker provides packages that easily configure docker on [Windows](https://docs.docker.com/docker-for-windows/), or [Linux](https://docs.docker.com/engine/install/#supported-platforms) systems. For CNFs, you must provide: -* helm packages with an associated schema. These files must be on your disk and will be referenced in the `input.json` config file. -* images for your CNF. For these you have the following options: - - a reference to an existing Azure Container Registry which contains the images for your CNF. Currently, only one ACR and namespace is supported per CNF. The images to be copied from this ACR are populated automatically based on the helm package schema. You must have Reader/AcrPull permissions on this ACR. To use this option, fill in `source_registry` and optionally `source_registry_namespace` in the input.json file. - - or, the image name of the source docker image from local machine. This is for a limited use case where the CNF only requires a single docker image which exists in the local docker repository. To use this option, fill in `source_local_docker_image` in the input.json file. This requires docker to be installed. -* optionally, you can provide a file (on disk) path_to_mappings which is a copy of values.yaml with your chosen values replaced by deployment parameters, thus exposing them as parameters to the CNF. You can get this file auto-generated by leaving the value as a blank string, either having every value as a deployment parameter, or using `--interactive` to interactively choose. -When filling in the input.json file, you must list helm packages in the order they are to be deployed. For example, if A must be deployed before B, your input.json should look something like this: +* Helm packages with an associated schema. These files must be on your disk and will be referenced in the `cnf-input.jsonc` config file. +* A reference to an existing Azure Container Registry which contains the images for your CNF. Currently, only one ACR and namespace is supported per CNF. The images to be copied from this ACR are populated automatically based on the helm package schema. You must have Reader/AcrPull permissions on this ACR. To use this, fill in `source_registry` and optionally `source_registry_namespace` in the cnf-input.jsonc file. +* Optionally, you can provide a file (on disk) path_to_mappings which is a copy of values.yaml with your chosen values replaced by deployment parameters, thus exposing them as parameters to the CNF. +* When filling in the cnf-input.jsonc file, you must list helm packages in the order they are to be deployed. For example, if A must be deployed before B, your cnf-input.jsonc should look something like this: "helm_packages": [ { @@ -106,35 +104,17 @@ Create an example config file for building a definition `az aosm nfd generate-config` -This will output a file called `input.json` which must be filled in. +This will output a file called `cnf-input.jsonc` which must be filled in. Once the config file has been filled in the following commands can be run. Build an nfd definition locally -`az aosm nfd build --config-file input.json` - -More options on building an nfd definition locally: - -Choose which of the VNF ARM template parameters you want to expose as NFD deploymentParameters, with the option of interactively choosing each one. - -`az aosm nfd build --config-file input.json --definition_type vnf --order_params` -`az aosm nfd build --config-file input.json --definition_type vnf --order_params --interactive` - -Choose which of the CNF Helm values parameters you want to expose as NFD deploymentParameters. - -`az aosm nfd build --config-file input.json --definition_type cnf [--interactive]` +`az aosm nfd build --config-file cnf-input.jsonc` Publish a pre-built definition -`az aosm nfd publish --config-file input.json` - -Delete a published definition +`az aosm nfd publish --build-output-folder cnf-cli-output` -`az aosm nfd delete --config-file input.json` - -Delete a published definition and the publisher, artifact stores and NFD group - -`az aosm nfd delete --config-file input.json --clean` #### NSDs @@ -149,24 +129,17 @@ Create an example config file for building a definition `az aosm nsd generate-config` -This will output a file called `input.json` which must be filled in. +This will output a file called `nsd-input.jsonc` which must be filled in. Once the config file has been filled in the following commands can be run. Build an nsd locally -`az aosm nsd build --config-file input.json` +`az aosm nsd build --config-file nsd-input.jsonc` Publish a pre-built design -`az aosm nsd publish --config-file input.json` - -Delete a published design - -`az aosm nsd delete --config-file input.json` - -Delete a published design and the publisher, artifact stores and NSD group +`az aosm nsd publish --build-output-folder nsd-cli-output` -`az aosm nsd delete --config-file input.json --clean` ## Bug Reporting @@ -200,4 +173,4 @@ az config set logging.enable_log_file=false ``` ## Development -Information about setting up and maintaining a development environment for this extension can be found [here](./development.md). +Information about setting up and maintaining a development environment for this extension can be found [here](https://eng.ms/docs/strategic-missions-and-technologies/strategic-missions-and-technologies-organization/azure-for-operators/aiops/aiops-orchestration/aosm-product-docs/processes/cli_contributing). diff --git a/src/aosm/azext_aosm/__init__.py b/src/aosm/azext_aosm/__init__.py index c15badcb435..4314adb3c52 100644 --- a/src/aosm/azext_aosm/__init__.py +++ b/src/aosm/azext_aosm/__init__.py @@ -16,8 +16,17 @@ def __init__(self, cli_ctx=None): super().__init__(cli_ctx=cli_ctx, custom_command_type=aosm_custom) def load_command_table(self, args): + from azure.cli.core.aaz import load_aaz_command_table + from azext_aosm.commands import load_command_table + try: + from . import aaz + except ImportError: + aaz = None + if aaz: + load_aaz_command_table(loader=self, aaz_pkg_name=aaz.__name__, args=args) + load_command_table(self, args) return self.command_table diff --git a/src/aosm/azext_aosm/_client_factory.py b/src/aosm/azext_aosm/_client_factory.py index ea716a48b2b..be1c30b4b6b 100644 --- a/src/aosm/azext_aosm/_client_factory.py +++ b/src/aosm/azext_aosm/_client_factory.py @@ -14,7 +14,9 @@ def cf_aosm(cli_ctx, *_) -> HybridNetworkManagementClient: # By default, get_mgmt_service_client() sets a parameter called 'base_url' when creating # the client. For us, doing so results in a key error. Setting base_url_bound=False prevents # that from happening - return get_mgmt_service_client(cli_ctx, HybridNetworkManagementClient, base_url_bound=False) + return get_mgmt_service_client( + cli_ctx, HybridNetworkManagementClient, base_url_bound=False + ) def cf_resources(cli_ctx, subscription_id=None): diff --git a/src/aosm/azext_aosm/_configuration.py b/src/aosm/azext_aosm/_configuration.py deleted file mode 100644 index 3b7a408beb1..00000000000 --- a/src/aosm/azext_aosm/_configuration.py +++ /dev/null @@ -1,771 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Configuration class for input config file parsing,""" -import abc -import logging -import json -import os -from dataclasses import dataclass, field, asdict -from pathlib import Path -from typing import Any, Dict, List, Optional, Union - -from azure.cli.core.azclierror import ( - InvalidArgumentValueError, - ValidationError, -) -from azext_aosm.util.constants import ( - CNF, - NF_DEFINITION_OUTPUT_BICEP_PREFIX, - NF_DEFINITION_JSON_FILENAME, - NSD, - NSD_OUTPUT_BICEP_PREFIX, - VNF, -) - -logger = logging.getLogger(__name__) - - -@dataclass -class ArtifactConfig: - # artifact.py checks for the presence of the default descriptions, change - # there if you change the descriptions. - artifact_name: str = "" - version: Optional[str] = "" - file_path: Optional[str] = None - - @classmethod - def helptext(cls) -> "ArtifactConfig": - """ - Build an object where each value is helptext for that field. - """ - return ArtifactConfig( - artifact_name="Optional. Name of the artifact.", - version="Version of the artifact in A.B.C format.", - file_path=( - "File path of the artifact you wish to upload from your local disk. " - "Relative paths are relative to the configuration file. " - "On Windows escape any backslash with another backslash." - ), - - ) - - def validate(self): - """ - Validate the configuration. - """ - if not self.version: - raise ValidationError("version must be set.") - if not self.file_path: - raise ValidationError("file_path must be set.") - - -@dataclass -class VhdArtifactConfig(ArtifactConfig): - # If you add a new property to this class, consider updating EXTRA_VHD_PARAMETERS in - # constants.py - see comment there for details. - blob_sas_url: Optional[str] = None - image_disk_size_GB: Optional[Union[str, int]] = None - image_hyper_v_generation: Optional[str] = None - image_api_version: Optional[str] = None - - def __post_init__(self): - """ - Convert parameters to the correct types. - """ - if ( - isinstance(self.image_disk_size_GB, str) - and self.image_disk_size_GB.isdigit() - ): - self.image_disk_size_GB = int(self.image_disk_size_GB) - - @classmethod - def helptext(cls) -> "VhdArtifactConfig": - """ - Build an object where each value is helptext for that field. - """ - - artifact_config = ArtifactConfig.helptext() - artifact_config.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." - ) - artifact_config.version = ( - "Version of the artifact in A-B-C format." - ) - return VhdArtifactConfig( - blob_sas_url=( - "Optional. SAS URL of the blob artifact you wish to copy to your Artifact" - " Store. Delete if not required." - ), - image_disk_size_GB=( - "Optional. Specifies the size of empty data disks in gigabytes. " - "This value cannot be larger than 1023 GB. Delete if not required." - ), - image_hyper_v_generation=( - "Optional. Specifies the HyperVGenerationType of the VirtualMachine " - "created from the image. Valid values are V1 and V2. V1 is the default if " - "not specified. Delete if not required." - ), - image_api_version=( - "Optional. The ARM API version used to create the " - "Microsoft.Compute/images resource. Delete if not required." - ), - **asdict(artifact_config), - ) - - def validate(self): - """ - Validate the configuration. - """ - if not self.version: - raise ValidationError("version must be set for vhd.") - if self.blob_sas_url and self.file_path: - raise ValidationError("Only one of file_path or blob_sas_url may be set for vhd.") - if not (self.blob_sas_url or self.file_path): - raise ValidationError("One of file_path or sas_blob_url must be set for vhd.") - - -@dataclass -class Configuration(abc.ABC): - config_file: Optional[str] = None - publisher_name: str = "" - publisher_resource_group_name: str = "" - acr_artifact_store_name: str = "" - location: str = "" - - def __post_init__(self): - """ - Set defaults for resource group and ACR as the publisher name tagged with -rg or -acr - """ - if self.publisher_name: - if not self.publisher_resource_group_name: - self.publisher_resource_group_name = f"{self.publisher_name}-rg" - if not self.acr_artifact_store_name: - self.acr_artifact_store_name = f"{self.publisher_name}-acr" - - @classmethod - def helptext(cls): - """ - Build an object where each value is helptext for that field. - """ - return Configuration( - 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=( - "Optional. Resource group for the Publisher resource. " - "Will be created if it does not exist (with a default name if none is supplied)." - ), - acr_artifact_store_name=( - "Optional. Name of the ACR Artifact Store resource. " - "Will be created if it does not exist (with a default name if none is supplied)." - ), - location="Azure location to use when creating resources.", - ) - - 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: - """ - Convert path from config file to path from current directory. - - We assume that the path supplied in the config file is relative to the - configuration file. That isn't the same as the path relative to where ever the - CLI is being run from. This function fixes that up. - - :param path: The path relative to the config file. - """ - assert self.config_file - - # If no path has been supplied we shouldn't try to update it. - if path == "": - return "" - - # If it is an absolute path then we don't need to monkey around with it. - if os.path.isabs(path): - return path - - config_file_dir = Path(self.config_file).parent - - updated_path = str(config_file_dir / path) - - logger.debug("Updated path: %s", updated_path) - - return updated_path - - @property - def output_directory_for_build(self) -> Path: - """Base class method to ensure subclasses implement this function.""" - raise NotImplementedError("Subclass must define property") - - @property - def acr_manifest_names(self) -> List[str]: - """The list of ACR manifest names.""" - raise NotImplementedError("Subclass must define property") - - -@dataclass -class NFConfiguration(Configuration): - """Network Function configuration.""" - - 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="Name of NF definition", - version="Version of the NF definition in A.B.C format.", - **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: - """Return the NFD Group name from the NFD name.""" - return f"{self.nf_name}-nfdg" - - @property - def acr_manifest_names(self) -> List[str]: - """ - Return the ACR manifest name from the NFD name. - - This is returned in a list for consistency with the NSConfiguration, where there - can be multiple ACR manifests. - """ - sanitized_nf_name = self.nf_name.lower().replace("_", "-") - return [f"{sanitized_nf_name}-acr-manifest-{self.version.replace('.', '-')}"] - - -@dataclass -class VNFConfiguration(NFConfiguration): - blob_artifact_store_name: str = "" - image_name_parameter: str = "" - arm_template: Union[Dict[str, str], ArtifactConfig] = field(default_factory=ArtifactConfig) - vhd: Union[Dict[str, str], VhdArtifactConfig] = field(default_factory=VhdArtifactConfig) - - @classmethod - def helptext(cls) -> "VNFConfiguration": - """ - Build an object where each value is helptext for that field. - """ - return VNFConfiguration( - blob_artifact_store_name=( - "Optional. Name of the storage account Artifact Store resource. Will be created if it " - "does not exist (with a default name if none is supplied)." - ), - 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=VhdArtifactConfig.helptext(), - **asdict(NFConfiguration.helptext()), - ) - - def __post_init__(self): - """ - Cope with deserializing subclasses from dicts to ArtifactConfig. - - Used when creating VNFConfiguration object from a loaded json config file. - """ - super().__post_init__() - if self.publisher_name and not self.blob_artifact_store_name: - self.blob_artifact_store_name = f"{self.publisher_name}-sa" - - if isinstance(self.arm_template, dict): - if self.arm_template.get("file_path"): - self.arm_template["file_path"] = self.path_from_cli_dir( - self.arm_template["file_path"] - ) - self.arm_template = ArtifactConfig(**self.arm_template) - - if isinstance(self.vhd, dict): - if self.vhd.get("file_path"): - self.vhd["file_path"] = self.path_from_cli_dir(self.vhd["file_path"]) - self.vhd = VhdArtifactConfig(**self.vhd) - - def validate(self) -> None: - """ - Validate the configuration passed in. - - :raises ValidationError for any invalid config - """ - super().validate() - - assert isinstance(self.vhd, VhdArtifactConfig) - 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" - " 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" - ) - - @property - def sa_manifest_name(self) -> str: - """Return the Storage account manifest name from the NFD name.""" - sanitized_nf_name = self.nf_name.lower().replace("_", "-") - return f"{sanitized_nf_name}-sa-manifest-{self.version.replace('.', '-')}" - - @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}") - - -@dataclass -class HelmPackageConfig: - name: str = "" - path_to_chart: str = "" - path_to_mappings: str = "" - depends_on: List[str] = field(default_factory=lambda: []) - - @classmethod - def helptext(cls): - """ - Build an object where each value is helptext for that field. - """ - return HelmPackageConfig( - 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: - """CNF Image config settings.""" - - source_registry: str = "" - source_registry_namespace: str = "" - source_local_docker_image: str = "" - - def __post_init__(self): - """ - Ensure that all config is lower case. - - ACR names can be uppercase but the login server is always lower case and docker - and az acr import commands require lower case. Might as well do the namespace - and docker image too although much less likely that the user has accidentally - pasted these with upper case. - """ - self.source_registry = self.source_registry.lower() - self.source_registry_namespace = self.source_registry_namespace.lower() - self.source_local_docker_image = self.source_local_docker_image.lower() - - @classmethod - def helptext(cls) -> "CNFImageConfig": - """ - Build an object where each value is helptext for that field. - """ - return CNFImageConfig( - 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: Union[Dict[str, str], CNFImageConfig] = field(default_factory=CNFImageConfig) - helm_packages: List[Union[Dict[str, Any], HelmPackageConfig]] = field( - default_factory=lambda: [] - ) - - def __post_init__(self): - """ - Cope with deserializing subclasses from dicts to HelmPackageConfig. - - Used when creating CNFConfiguration object from a loaded json config file. - """ - super().__post_init__() - for package_index, package in enumerate(self.helm_packages): - if isinstance(package, dict): - package["path_to_chart"] = self.path_from_cli_dir( - package["path_to_chart"] - ) - package["path_to_mappings"] = self.path_from_cli_dir( - package["path_to_mappings"] - ) - self.helm_packages[package_index] = HelmPackageConfig(**dict(package)) - if isinstance(self.images, dict): - self.images = CNFImageConfig(**self.images) - - @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.""" - return Path(f"{NF_DEFINITION_OUTPUT_BICEP_PREFIX}{self.nf_name}") - - def validate(self): - """ - Validate the CNF config. - - :raises ValidationError: If source registry ID doesn't match the regex - """ - assert isinstance(self.images, CNFImageConfig) - super().validate() - - self.images.validate() - - for helm_package in self.helm_packages: - assert isinstance(helm_package, HelmPackageConfig) - helm_package.validate() - - -@dataclass -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_resource_group: str = "" - name: str = "" - version: str = "" - publisher_offering_location: str = "" - 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": - """ - Build an object where each value is helptext for that field. - """ - return NFDRETConfiguration( - 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.", - 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: - """ - Validate the configuration passed in. - - :raises ValidationError for any invalid config - """ - if not self.name: - raise ValidationError("Network function definition name must be set") - - if not self.publisher: - raise ValidationError(f"Publisher name must be set for {self.name}") - - if not self.publisher_resource_group: - raise ValidationError( - f"Publisher resource group name must be set for {self.name}" - ) - - if not self.version: - raise ValidationError( - f"Network function definition version must be set for {self.name}" - ) - - if not self.publisher_offering_location: - raise ValidationError( - f"Network function definition offering location must be set, for {self.name}" - ) - - if self.type not in [CNF, VNF]: - raise ValueError( - f"Network Function Type must be cnf or vnf for {self.name}" - ) - - if not isinstance(self.multiple_instances, bool): - raise ValueError( - f"multiple_instances must be a boolean for for {self.name}" - ) - - # There is currently a NFM bug that means that multiple copies of the same NF - # cannot be deployed to the same custom location: - # https://portal.microsofticm.com/imp/v3/incidents/details/405078667/home - if self.type == CNF and self.multiple_instances: - raise ValueError("Multiple instances is not supported on CNFs.") - - @property - def build_output_folder_name(self) -> Path: - """Return the local folder for generating the bicep template to.""" - current_working_directory = os.getcwd() - return Path(current_working_directory, NSD_OUTPUT_BICEP_PREFIX) - - @property - def arm_template(self) -> ArtifactConfig: - """Return the parameters of the ARM template for this RET to be uploaded as part of - the NSDV.""" - artifact = ArtifactConfig() - artifact.artifact_name = f"{self.name.lower()}_nf_artifact" - - # We want the ARM template version to match the NSD version, but we don't have - # that information here. - artifact.version = None - artifact.file_path = os.path.join( - self.build_output_folder_name, NF_DEFINITION_JSON_FILENAME - ) - return artifact - - @property - def nf_bicep_filename(self) -> str: - """Return the name of the bicep template for deploying the NFs.""" - return f"{self.name}_nf.bicep" - - @property - def resource_element_name(self) -> str: - """Return the name of the resource element.""" - artifact_name = self.arm_template.artifact_name - return f"{artifact_name}_resource_element" - - def acr_manifest_name(self, nsd_version: str) -> str: - """Return the ACR manifest name from the NFD name.""" - return ( - f"{self.name.lower().replace('_', '-')}" - f"-nf-acr-manifest-{nsd_version.replace('.', '-')}" - ) - - -@dataclass -class NSConfiguration(Configuration): - network_functions: List[Union[NFDRETConfiguration, Dict[str, Any]]] = field( - default_factory=lambda: [] - ) - nsd_name: str = "" - nsd_version: str = "" - nsdv_description: str = "" - - def __post_init__(self): - """Covert things to the correct format.""" - super().__post_init__() - if self.network_functions and isinstance(self.network_functions[0], dict): - nf_ret_list = [ - NFDRETConfiguration(**config) for config in self.network_functions - ] - 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( - 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." - ), - 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()), - ) - - return nsd_helptext - - def validate(self): - """ - Validate the configuration passed in. - - :raises ValueError for any invalid config - """ - super().validate() - if not self.network_functions: - 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") - if not self.nsd_version: - raise ValueError("nsd_version must be set") - - @property - def output_directory_for_build(self) -> Path: - """Return the local folder for generating the bicep template to.""" - current_working_directory = os.getcwd() - return Path(current_working_directory, NSD_OUTPUT_BICEP_PREFIX) - - @property - def nfvi_site_name(self) -> str: - """Return the name of the NFVI used for the NSDV.""" - return f"{self.nsd_name}_NFVI" - - @property - def cg_schema_name(self) -> str: - """Return the name of the Configuration Schema used for the NSDV.""" - return f"{self.nsd_name.replace('-', '_')}_ConfigGroupSchema" - - @property - def acr_manifest_names(self) -> List[str]: - """The list of ACR manifest names for all the NF ARM templates.""" - 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: - """ - Return the correct configuration object based on the type. - - :param configuration_type: The type of configuration to return - :param config_file: The path to the config file - :return: The configuration object - """ - 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: - if configuration_type == VNF: - config = VNFConfiguration(config_file=config_file, **config_as_dict) - elif configuration_type == CNF: - config = CNFConfiguration(config_file=config_file, **config_as_dict) - elif configuration_type == NSD: - config = NSConfiguration(config_file=config_file, **config_as_dict) - else: - raise InvalidArgumentValueError( - "Definition type not recognized, options are: vnf, cnf or nsd" - ) - except TypeError as typeerr: - raise InvalidArgumentValueError( - f"Config file {config_file} is not valid: {typeerr}" - ) from typeerr - - config.validate() - - return config diff --git a/src/aosm/azext_aosm/_help.py b/src/aosm/azext_aosm/_help.py index 64bc5e32940..a6a779dba07 100644 --- a/src/aosm/azext_aosm/_help.py +++ b/src/aosm/azext_aosm/_help.py @@ -10,6 +10,8 @@ ] = """ type: group short-summary: Commands to interact with Azure Operator Service Manager (AOSM). + long-summary: | + Azure Operator Service Manager (AOSM) is a service that enables you to manage and publish Network Function Definitions (NFD) and Network Service Definitions (NSD) to Azure. These commands allow you to build and publish NFDs and NSDs, including supporting infrastructure, from existing Helm charts and ARM templates. """ helps[ @@ -17,6 +19,8 @@ ] = """ type: group short-summary: Manage AOSM publisher Network Function Definitions. + long-summary: | + A Network Function Definition (NFD) is a collection of Helm charts or ARM templates that define a network function. This command group allows you to build and publish NFDs to Azure. """ helps[ @@ -24,6 +28,17 @@ ] = """ type: command short-summary: Generate configuration file for building an AOSM publisher Network Function Definition. + long-summary: | + Generates a configuration file that you can use to build an AOSM Network Function Definition (NFD). The configuration file is a JSONC file that contains the required parameters for building the NFD. You must complete the configuration file with your specific values before building the NFD. + examples: + - name: Generate a configuration file for a Containerised Network Function. + text: az aosm nfd generate-config --definition-type cnf + - name: Generate a configuration file for a Virtual Network Function. + text: az aosm nfd generate-config --definition-type vnf + - name: Generate a configuration file for a Virtual Network Function for use on Azure Nexus. + text: az aosm nfd generate-config --definition-type vnf-nexus + - name: Generate a configuration file for a Virtual Network Function and write to a specific file. + text: az aosm nfd generate-config --definition-type vnf --output-file my-vnf-input-config.jsonc """ helps[ @@ -31,6 +46,15 @@ ] = """ type: command short-summary: Build an AOSM Network Function Definition. + long-summary: | + Builds an AOSM Network Function Definition (NFD) based on the configuration file provided. The NFD is built from the Helm charts or ARM templates specified in the configuration file. The output is a directory which can either be published directly (using the aosm nfd publish command) or manually customized before publishing. + examples: + - name: Build a Containerised Network Function. + text: az aosm nfd build --definition-type cnf --config-file my-cnf-input-config.jsonc + - name: Build a Virtual Network Function for use on Azure Core. + text: az aosm nfd build --definition-type vnf --config-file my-vnf-input-config.jsonc + - name: Build a Virtual Network Function for use on Azure Nexus. + text: az aosm nfd build --definition-type vnf-nexus --config-file my-vnf-nexus-input-config.jsonc """ helps[ @@ -38,14 +62,17 @@ ] = """ type: command short-summary: Publish a pre-built AOSM Network Function definition. -""" - - -helps[ - "aosm nfd delete" -] = """ - type: command - short-summary: Delete AOSM Network Function Definition. + long-summary: | + Publishes a pre-built AOSM Network Function Definition (NFD) to Azure. The NFD must be built using the aosm nfd build command before it can be published. The NFD and other required resources (publisher resource, artifact manifest(s), storage account(s) etc.) is published to the specified resource group in the currently active Azure subscription. + examples: + - name: Publish a Containerised Network Function. + text: az aosm nfd publish --definition-type cnf --build-output-folder my-cnf-output-folder + - name: Publish a Virtual Network Function for use on Azure Core. + text: az aosm nfd publish --definition-type vnf --build-output-folder my-vnf-output-folder + - name: Publish a Virtual Network Function for use on Azure Nexus. + text: az aosm nfd publish --definition-type vnf-nexus --build-output-folder my-vnf-nexus-output-folder + - name: Publish a Containerised Network Function when you do not have the required import permissions. + text: az aosm nfd publish --definition-type cnf --build-output-folder my-cnf-output-folder --no-subscription-permissions """ helps[ @@ -53,6 +80,8 @@ ] = """ type: group short-summary: Manage AOSM publisher Network Service Designs. + long-summary: | + A Network Service Design (NSD) is a collection of Network Function Definitions (NFD) and any supporting infrastructure that define a network service. This command group allows you to build and publish NSDs to Azure. """ helps[ @@ -60,6 +89,13 @@ ] = """ type: command short-summary: Generate configuration file for building an AOSM publisher Network Service Design. + long-summary: | + Generates a configuration file that you can use to build an AOSM Network Service Design (NSD). The configuration file is a JSONC file that contains the required parameters for building the NSD. You must complete the configuration file with your specific values before building the NSD. + examples: + - name: Generate a configuration file for a Network Service Design. + text: az aosm nsd generate-config + - name: Generate a configuration file for a Network Service Design and write to a specific file. + text: az aosm nsd generate-config --output-file my-nsd-input-config.jsonc """ helps[ @@ -67,6 +103,11 @@ ] = """ type: command short-summary: Build an AOSM Network Service Design. + long-summary: | + Builds an AOSM Network Service Design (NSD) based on the configuration file provided. The NSD is built from the Network Function Definitions (NFD) and ARM templates specifying supporting infrastructure, as specified in the configuration file. The output is a directory which can either be published directly (using the aosm nsd publish command) or manually customized before publishing. + examples: + - name: Build a Network Service Design. + text: az aosm nsd build --config-file my-nsd-input-config.jsonc """ helps[ @@ -74,12 +115,9 @@ ] = """ type: command short-summary: Publish a pre-built AOSM Network Service Design. -""" - - -helps[ - "aosm nfd delete" -] = """ - type: command - short-summary: Delete AOSM Network Function Definition. + long-summary: | + Publishes a pre-built AOSM Network Service Design (NSD) to Azure. The NSD must be built using the aosm nsd build command before it can be published. The NSD and other required resources (publisher resource, artifact manifest(s), storage account(s) etc.) is published to the specified resource group in the currently active Azure subscription. + examples: + - name: Publish a Network Service Design. + text: az aosm nsd publish --build-output-folder my-nsd-output-folder """ diff --git a/src/aosm/azext_aosm/_params.py b/src/aosm/azext_aosm/_params.py index 7ecb64fce85..b0bcf35adc0 100644 --- a/src/aosm/azext_aosm/_params.py +++ b/src/aosm/azext_aosm/_params.py @@ -6,7 +6,15 @@ from argcomplete.completers import FilesCompleter from azure.cli.core import AzCommandsLoader -from .util.constants import CNF, VNF, BICEP_PUBLISH, ARTIFACT_UPLOAD, IMAGE_UPLOAD +from .common.constants import ( + CNF, + VNF, + VNF_NEXUS, + BICEP_PUBLISH, + ARTIFACT_UPLOAD, + HELM_TEMPLATE, + IMAGE_UPLOAD, +) def load_arguments(self: AzCommandsLoader, _): @@ -16,8 +24,10 @@ def load_arguments(self: AzCommandsLoader, _): get_three_state_flag, ) - definition_type = get_enum_type([VNF, CNF]) - nf_skip_steps = get_enum_type([BICEP_PUBLISH, ARTIFACT_UPLOAD, IMAGE_UPLOAD]) + definition_type = get_enum_type([VNF, CNF, VNF_NEXUS]) + nf_skip_steps = get_enum_type( + [BICEP_PUBLISH, ARTIFACT_UPLOAD, IMAGE_UPLOAD, HELM_TEMPLATE] + ) ns_skip_steps = get_enum_type([BICEP_PUBLISH, ARTIFACT_UPLOAD]) # Set the argument context so these options are only available when this specific command @@ -27,15 +37,27 @@ def load_arguments(self: AzCommandsLoader, _): c.argument( "definition_type", arg_type=definition_type, - help="Type of AOSM definition.", + help=( + "Type of AOSM definition to be published. The config file differs" + " depending on type." + ), required=True, ) + c.argument( + "output_file", + options_list=["--output-file"], + help="The name of the output file to write the generated config text to.", + required=False, + ) c.argument( "config_file", options_list=["--config-file", "-f"], type=file_type, - completer=FilesCompleter(allowednames="*.json"), - help="The path to the configuration file.", + completer=FilesCompleter(allowednames="*.jsonc"), + help=( + "The path to the configuration file. This is a JSONC file that contains" + " the required parameters for building the NFD." + ), ) c.argument( "clean", @@ -43,114 +65,83 @@ def load_arguments(self: AzCommandsLoader, _): help="Also delete artifact stores, NFD Group and Publisher. Use with care.", ) c.argument( - "definition_file", - options_list=["--definition-file", "-b"], + "build_output_folder", + options_list=["--build-output-folder", "-b"], type=file_type, completer=FilesCompleter(allowednames="*.json"), - help=( - "Optional path to a bicep file to publish. Use to override publish of" - " the built definition with an alternative file." - ), + help="Path to the folder to publish, created by the build command.", ) + # This will only ever output one string and will fail if more than one + # skip steps are provided. It might be good to change that. c.argument( - "design_file", - options_list=["--design-file", "-b"], - type=file_type, - completer=FilesCompleter(allowednames="*.bicep"), + "skip", + arg_type=nf_skip_steps, help=( - "Optional path to a bicep file to publish. Use to override publish of" - " the built design with an alternative file." + "Optional skip steps. 'bicep-publish' will skip deploying the bicep " + "template; 'artifact-upload' will skip uploading any artifacts; " + "'image-upload' will skip uploading the VHD image (for VNFs) or the " + "container images (for CNFs); 'helm-template' will skip templating the " + "helm charts (for CNFs)." ), ) c.argument( - "order_params", + "no_subscription_permissions", + options_list=["--no-subscription-permissions", "-u"], arg_type=get_three_state_flag(), help=( - "VNF definition_type only - ignored for CNF. Order deploymentParameters" - " schema and configMappings to have the parameters without default" - " values at the top and those with default values at the bottom. Can" - " make it easier to remove those with defaults which you do not want to" - " expose as NFD parameters." + "Used only for CNF publish - ignored in all other scenarios. Pass this" + " flag if you do not have permission to import to the Publisher" + " subscription (Contributor role + AcrPush role, or a custom role that" + " allows the importImage action and AcrPush over the whole" + " subscription). Using this flag causes image artifacts to be pulled to" + " your local machine and then pushed to the Artifact Store. This is" + " slower than a copy entirely within Azure, but is an alternative if" + " you do not have the required permissions. Requires Docker to be" + " installed locally." ), ) + + with self.argument_context("aosm nsd") as c: c.argument( - "interactive", - options_list=["--interactive", "-i"], - arg_type=get_three_state_flag(), - help=( - "Prompt user to choose every parameter to expose as an NFD parameter." - " Those without defaults are automatically included." - ), + "output_file", + options_list=["--output-file"], + help="The name of the output file to write the generated config text to.", + required=False, ) c.argument( - "parameters_json_file", - options_list=["--parameters-file", "-p"], + "config_file", + options_list=["--config-file", "-f"], type=file_type, - completer=FilesCompleter(allowednames="*.json"), + completer=FilesCompleter(allowednames="*.jsonc"), help=( - "Optional path to a parameters file for the bicep definition file. Use" - " to override publish of the built definition and config with" - " alternative parameters." + "The path to the configuration file. This is a JSONC file that contains" + " the required parameters for building the NSD." ), ) + c.argument("skip", arg_type=ns_skip_steps, help="Optional skip steps") c.argument( - "manifest_file", - options_list=["--manifest-file", "-m"], - type=file_type, - completer=FilesCompleter(allowednames="*.json"), - help=( - "Optional path to a bicep file to publish manifests. Use to override" - " publish of the built definition with an alternative file." - ), + "clean", + arg_type=get_three_state_flag(), + help="Also delete NSD Group. Use with care.", ) c.argument( - "manifest_params_file", - options_list=["--manifest-params-file"], + "build_output_folder", + options_list=["--build-output-folder", "-b"], type=file_type, completer=FilesCompleter(allowednames="*.json"), - help=( - "Optional path to a parameters file for the manifest definition file." - " Use to override publish of the built definition and config with" - " alternative parameters." - ), - ) - c.argument( - "skip", - arg_type=nf_skip_steps, - help=( - "Optional skip steps. 'bicep-publish' will skip deploying the bicep " - "template; 'artifact-upload' will skip uploading any artifacts; " - "'image-upload' will skip uploading the VHD image (for VNFs) or the " - "container images (for CNFs)." - ), + help="Path to the folder to publish, created by the build command.", ) c.argument( "no_subscription_permissions", options_list=["--no-subscription-permissions", "-u"], - arg_type=get_three_state_flag(), + completer=FilesCompleter(allowednames="*.json"), help=( - "CNF definition_type publish only - ignored for VNF." - " Set to True if you do not " - "have permission to import to the Publisher subscription (Contributor " - "role + AcrPush role, or a custom role that allows the importImage " - "action and AcrPush over the " + "CNF definition_type publish only - ignored for VNF. " + "Pass this flag if you do not have permission to import to the " + "Publisher subscription (Contributor role + AcrPush role, or a " + "custom role that allows the importImage action and AcrPush over the " "whole subscription). This means that the image artifacts will be " "pulled to your local machine and then pushed to the Artifact Store. " "Requires Docker to be installed locally." ), ) - - with self.argument_context("aosm nsd") as c: - c.argument( - "config_file", - options_list=["--config-file", "-f"], - type=file_type, - completer=FilesCompleter(allowednames="*.json"), - help="The path to the configuration file.", - ) - c.argument("skip", arg_type=ns_skip_steps, help="Optional skip steps") - c.argument( - "clean", - arg_type=get_three_state_flag(), - help="Also delete NSD Group. Use with care.", - ) diff --git a/src/aosm/azext_aosm/aaz/__init__.py b/src/aosm/azext_aosm/aaz/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/aosm/azext_aosm/aaz/latest/__init__.py b/src/aosm/azext_aosm/aaz/latest/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/__cmd_group.py new file mode 100644 index 00000000000..9913b2ee9e7 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage Azure Operator Service Manager resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__cmd_group.py new file mode 100644 index 00000000000..40e546f99f4 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage publisher resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__cmd_group.py new file mode 100644 index 00000000000..9afc79d2715 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher artifact-manifest", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage artifact manifest resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__init__.py new file mode 100644 index 00000000000..bda5dcd9fce --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/__init__.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._list_credential import * +from ._show import * +from ._update_state import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list.py new file mode 100644 index 00000000000..031b552d4a3 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list.py @@ -0,0 +1,258 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-manifest list", + is_preview=True, +) +class List(AAZCommand): + """List information about the artifact manifest. + + :example: List information about the artifact manifest in the 'contoso' artifact store of the 'contoso' publisher + az aosm publisher artifact-manifest list --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactmanifests", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ArtifactManifestsListByArtifactStore(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ArtifactManifestsListByArtifactStore(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactManifests", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.artifact_manifest_state = AAZStrType( + serialized_name="artifactManifestState", + ) + properties.artifacts = AAZListType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + + artifacts = cls._schema_on_200.value.Element.properties.artifacts + artifacts.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.artifacts.Element + _element.artifact_name = AAZStrType( + serialized_name="artifactName", + ) + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + ) + _element.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list_credential.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list_credential.py new file mode 100644 index 00000000000..94c3a78808a --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_list_credential.py @@ -0,0 +1,235 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-manifest list-credential", + is_preview=True, +) +class ListCredential(AAZCommand): + """List credential for publishing artifacts defined in artifact manifest. + + :example: List credential to use for publishing an artifact from the 'contoso-manifest' manifest + az aosm publisher artifact-manifest list-credential --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso --name contoso-manifest + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactmanifests/{}/listcredential", "2023-09-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the artifact manifest.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ArtifactManifestsListCredential(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ArtifactManifestsListCredential(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactManifests/{artifactManifestName}/listCredential", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactManifestName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.credential_type = AAZStrType( + serialized_name="credentialType", + flags={"required": True}, + ) + + disc_azure_container_registry_scoped_token = cls._schema_on_200.discriminate_by("credential_type", "AzureContainerRegistryScopedToken") + disc_azure_container_registry_scoped_token.acr_server_url = AAZStrType( + serialized_name="acrServerUrl", + ) + disc_azure_container_registry_scoped_token.acr_token = AAZStrType( + serialized_name="acrToken", + ) + disc_azure_container_registry_scoped_token.expiry = AAZStrType() + disc_azure_container_registry_scoped_token.repositories = AAZListType() + disc_azure_container_registry_scoped_token.username = AAZStrType() + + repositories = cls._schema_on_200.discriminate_by("credential_type", "AzureContainerRegistryScopedToken").repositories + repositories.Element = AAZStrType() + + disc_azure_storage_account_token = cls._schema_on_200.discriminate_by("credential_type", "AzureStorageAccountToken") + disc_azure_storage_account_token.container_credentials = AAZListType( + serialized_name="containerCredentials", + ) + disc_azure_storage_account_token.expiry = AAZStrType() + disc_azure_storage_account_token.storage_account_id = AAZStrType( + serialized_name="storageAccountId", + ) + + container_credentials = cls._schema_on_200.discriminate_by("credential_type", "AzureStorageAccountToken").container_credentials + container_credentials.Element = AAZObjectType() + + _element = cls._schema_on_200.discriminate_by("credential_type", "AzureStorageAccountToken").container_credentials.Element + _element.container_name = AAZStrType( + serialized_name="containerName", + ) + _element.container_sas_uri = AAZStrType( + serialized_name="containerSasUri", + ) + + return cls._schema_on_200 + + +class _ListCredentialHelper: + """Helper class for ListCredential""" + + +__all__ = ["ListCredential"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_show.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_show.py new file mode 100644 index 00000000000..545c557dd50 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_show.py @@ -0,0 +1,259 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-manifest show", + is_preview=True, +) +class Show(AAZCommand): + """Get information about a artifact manifest resource. + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactmanifests/{}", "2023-09-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the artifact manifest.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ArtifactManifestsGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ArtifactManifestsGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactManifests/{artifactManifestName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactManifestName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.location = AAZStrType( + flags={"required": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.tags = AAZDictType() + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.artifact_manifest_state = AAZStrType( + serialized_name="artifactManifestState", + ) + properties.artifacts = AAZListType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + + artifacts = cls._schema_on_200.properties.artifacts + artifacts.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.artifacts.Element + _element.artifact_name = AAZStrType( + serialized_name="artifactName", + ) + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + ) + _element.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + +__all__ = ["Show"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_update_state.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_update_state.py new file mode 100644 index 00000000000..ebb6fdcbcc4 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_manifest/_update_state.py @@ -0,0 +1,241 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-manifest update-state", + is_preview=True, +) +class UpdateState(AAZCommand): + """Update state for artifact manifest. + + :example: Update the 'contoso-manifest' artifact manifest's state to 'Uploaded' + az aosm publisher artifact-manifest update-state --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso --name contoso-manifest --state Uploaded + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactmanifests/{}/updatestate", "2023-09-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the artifact manifest.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + + # define Arg Group "Parameters" + + _args_schema = cls._args_schema + _args_schema.state = AAZStrArg( + options=["--state"], + arg_group="Parameters", + help="The artifact manifest state.", + enum={"Succeeded": "Succeeded", "Unknown": "Unknown", "Uploaded": "Uploaded", "Uploading": "Uploading", "Validating": "Validating", "ValidationFailed": "ValidationFailed"}, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.ArtifactManifestsUpdateState(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ArtifactManifestsUpdateState(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactManifests/{artifactManifestName}/updateState", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactManifestName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("artifactManifestState", AAZStrType, ".state") + + return self.serialize_content(_content_value) + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.artifact_manifest_state = AAZStrType( + serialized_name="artifactManifestState", + ) + + return cls._schema_on_200 + + +class _UpdateStateHelper: + """Helper class for UpdateState""" + + +__all__ = ["UpdateState"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__cmd_group.py new file mode 100644 index 00000000000..a90138e7552 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher artifact-store", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage artifact store resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__cmd_group.py new file mode 100644 index 00000000000..65517825f8b --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher artifact-store artifact", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage artifacts in a artifact store. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__init__.py new file mode 100644 index 00000000000..d63ae5a6fc9 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/__init__.py @@ -0,0 +1,12 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/_list.py new file mode 100644 index 00000000000..25f7c9e25c7 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/_list.py @@ -0,0 +1,228 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-store artifact list", + is_preview=True, +) +class List(AAZCommand): + """List all the available artifacts in the parent Artifact Store. + + :example: List all available artifacts in the 'contoso' artifact store of the 'contoso' publisher + az aosm publisher artifact-store artifact list --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifacts", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ProxyArtifactList(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ProxyArtifactList(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifacts", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType( + flags={"read_only": True}, + ) + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__cmd_group.py new file mode 100644 index 00000000000..9af27083958 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher artifact-store artifact version", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage artifact versions. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__init__.py new file mode 100644 index 00000000000..19a82d5eb62 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/__init__.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._update_state import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_list.py new file mode 100644 index 00000000000..d575af3ba93 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_list.py @@ -0,0 +1,253 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-store artifact version list", + is_preview=True, +) +class List(AAZCommand): + """List a Artifact overview information. + + :example: List overview information for the 'nginx' artifact in the 'contoso' artifact store of the 'contoso' publisher + az aosm publisher artifact-store artifact version list --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso --artifact-name nginx + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactversions", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.artifact_name = AAZStrArg( + options=["--artifact-name"], + help="The name of the artifact.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ProxyArtifactGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ProxyArtifactGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactVersions", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "artifactName", self.ctx.args.artifact_name, + required=True, + ), + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType( + flags={"read_only": True}, + ) + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.artifact_state = AAZStrType( + serialized_name="artifactState", + ) + properties.artifact_type = AAZStrType( + serialized_name="artifactType", + ) + properties.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_update_state.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_update_state.py new file mode 100644 index 00000000000..424d200fdd4 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/artifact_store/artifact/version/_update_state.py @@ -0,0 +1,302 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher artifact-store artifact version update-state", + is_preview=True, +) +class UpdateState(AAZCommand): + """Update artifact state defined in artifact store. + + :example: Deprecate the 1.0.0 version of the 'nginx' artifact in the 'contoso' artifact store of the 'contoso' publisher + az aosm publisher artifact-store artifact version update-state --resource-group contoso-aosm --publisher-name contoso --artifact-store-name contoso --artifact-name nginx --name 1.0.0 --artifact-state Deprecated + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/artifactstores/{}/artifactversions/{}", "2023-09-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.artifact_store_name = AAZStrArg( + options=["--artifact-store-name"], + help="The name of the artifact store.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the artifact version.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.artifact_name = AAZStrArg( + options=["--artifact-name"], + help="The name of the artifact.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[^\s]*[^\s]+[^\s]*$", + max_length=64, + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.artifact_state = AAZStrArg( + options=["--artifact-state"], + arg_group="Properties", + help="The artifact state", + enum={"Active": "Active", "Deprecated": "Deprecated", "Preview": "Preview", "Unknown": "Unknown"}, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.ProxyArtifactUpdateState(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ProxyArtifactUpdateState(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/artifactStores/{artifactStoreName}/artifactVersions/{artifactVersionName}", + **self.url_parameters + ) + + @property + def method(self): + return "PATCH" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "artifactStoreName", self.ctx.args.artifact_store_name, + required=True, + ), + **self.serialize_url_param( + "artifactVersionName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "artifactName", self.ctx.args.artifact_name, + required=True, + ), + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("artifactState", AAZStrType, ".artifact_state") + + return self.serialize_content(_content_value) + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType( + flags={"read_only": True}, + ) + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.artifact_state = AAZStrType( + serialized_name="artifactState", + ) + properties.artifact_type = AAZStrType( + serialized_name="artifactType", + ) + properties.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _UpdateStateHelper: + """Helper class for UpdateState""" + + +__all__ = ["UpdateState"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__cmd_group.py new file mode 100644 index 00000000000..a8e47a2692f --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher configuration-group-schema", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage configuration group schema resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__init__.py new file mode 100644 index 00000000000..93bb64cc975 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/__init__.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._show import * +from ._update_state import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_list.py new file mode 100644 index 00000000000..4474c8fc43f --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_list.py @@ -0,0 +1,234 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher configuration-group-schema list", + is_preview=True, +) +class List(AAZCommand): + """List information of the configuration group schemas under a publisher. + + :example: List the configuration group schemas defined under the contoso publisher + az aosm publisher configuration-group-schema list --resource-group contoso-aosm --publisher-name contoso + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/configurationgroupschemas", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ConfigurationGroupSchemasListByPublisher(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ConfigurationGroupSchemasListByPublisher(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/configurationGroupSchemas", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.description = AAZStrType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.schema_definition = AAZStrType( + serialized_name="schemaDefinition", + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_show.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_show.py new file mode 100644 index 00000000000..bfc03ada943 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_show.py @@ -0,0 +1,234 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher configuration-group-schema show", + is_preview=True, +) +class Show(AAZCommand): + """Get information about the specified configuration group schema. + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/configurationgroupschemas/{}", "2023-09-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the configuration group schema.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ConfigurationGroupSchemasGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ConfigurationGroupSchemasGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/configurationGroupSchemas/{configurationGroupSchemaName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "configurationGroupSchemaName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.location = AAZStrType( + flags={"required": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.tags = AAZDictType() + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.description = AAZStrType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.schema_definition = AAZStrType( + serialized_name="schemaDefinition", + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + +__all__ = ["Show"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_update_state.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_update_state.py new file mode 100644 index 00000000000..93afbd0d345 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/configuration_group_schema/_update_state.py @@ -0,0 +1,227 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher configuration-group-schema update-state", + is_preview=True, +) +class UpdateState(AAZCommand): + """Update configuration group schema state. + + :example: Change the 'nginx-cgs' config group schema to 'Active' state + az aosm publisher configuration-group-schema update-state --resource-group contoso-aosm --publisher-name contoso --name nginx-cgs --version-state Active + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/configurationgroupschemas/{}/updatestate", "2023-09-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="The name of the configuration group schema.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + + # define Arg Group "Parameters" + + _args_schema = cls._args_schema + _args_schema.version_state = AAZStrArg( + options=["--version-state"], + arg_group="Parameters", + help="The configuration group schema state.", + enum={"Active": "Active", "Deprecated": "Deprecated", "Preview": "Preview", "Unknown": "Unknown"}, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.ConfigurationGroupSchemasUpdateState(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ConfigurationGroupSchemasUpdateState(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/configurationGroupSchemas/{configurationGroupSchemaName}/updateState", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "configurationGroupSchemaName", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("versionState", AAZStrType, ".version_state") + + return self.serialize_content(_content_value) + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.version_state = AAZStrType( + serialized_name="versionState", + ) + + return cls._schema_on_200 + + +class _UpdateStateHelper: + """Helper class for UpdateState""" + + +__all__ = ["UpdateState"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__cmd_group.py new file mode 100644 index 00000000000..59166495280 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher network-function-definition", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage network function definition resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__cmd_group.py new file mode 100644 index 00000000000..438bd25e627 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher network-function-definition version", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage network function definition version resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__init__.py new file mode 100644 index 00000000000..93bb64cc975 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/__init__.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._show import * +from ._update_state import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_list.py new file mode 100644 index 00000000000..9f361ce6f6c --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_list.py @@ -0,0 +1,626 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-function-definition version list", + is_preview=True, +) +class List(AAZCommand): + """List information about the network function definition versions available in the specified network function definition group. + + :example: List information about the network function definition versions available in the 'nginx' network function definition group + az aosm publisher network-function-definition version list --resource-group contoso-aosm --publisher-name contoso --group-name nginx + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkfunctiondefinitiongroups/{}/networkfunctiondefinitionversions", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network function definition group.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.NetworkFunctionDefinitionVersionsListByNetworkFunctionDefinitionGroup(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class NetworkFunctionDefinitionVersionsListByNetworkFunctionDefinitionGroup(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkFunctionDefinitionGroups/{networkFunctionDefinitionGroupName}/networkFunctionDefinitionVersions", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkFunctionDefinitionGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.deploy_parameters = AAZStrType( + serialized_name="deployParameters", + ) + properties.description = AAZStrType() + properties.network_function_type = AAZStrType( + serialized_name="networkFunctionType", + flags={"required": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + disc_containerized_network_function = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction") + disc_containerized_network_function.network_function_template = AAZObjectType( + serialized_name="networkFunctionTemplate", + ) + + network_function_template = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template + network_function_template.nfvi_type = AAZStrType( + serialized_name="nfviType", + flags={"required": True}, + ) + + disc_azure_arc_kubernetes = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes") + disc_azure_arc_kubernetes.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ListHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_helm_package = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage") + disc_helm_package.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_helm_package.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ListHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.helm_artifact_profile = AAZObjectType( + serialized_name="helmArtifactProfile", + ) + + helm_artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile + helm_artifact_profile.helm_package_name = AAZStrType( + serialized_name="helmPackageName", + ) + helm_artifact_profile.helm_package_version_range = AAZStrType( + serialized_name="helmPackageVersionRange", + ) + helm_artifact_profile.image_pull_secrets_values_paths = AAZListType( + serialized_name="imagePullSecretsValuesPaths", + ) + helm_artifact_profile.registry_values_paths = AAZListType( + serialized_name="registryValuesPaths", + ) + + image_pull_secrets_values_paths = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile.image_pull_secrets_values_paths + image_pull_secrets_values_paths.Element = AAZStrType() + + registry_values_paths = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile.registry_values_paths + registry_values_paths.Element = AAZStrType() + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile = AAZObjectType( + serialized_name="helmMappingRuleProfile", + ) + + helm_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile + helm_mapping_rule_profile.helm_package_version = AAZStrType( + serialized_name="helmPackageVersion", + ) + helm_mapping_rule_profile.options = AAZObjectType() + helm_mapping_rule_profile.release_name = AAZStrType( + serialized_name="releaseName", + ) + helm_mapping_rule_profile.release_namespace = AAZStrType( + serialized_name="releaseNamespace", + ) + helm_mapping_rule_profile.values = AAZStrType() + + options = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options + options.install_options = AAZObjectType( + serialized_name="installOptions", + ) + options.upgrade_options = AAZObjectType( + serialized_name="upgradeOptions", + ) + + install_options = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options.install_options + install_options.atomic = AAZStrType() + install_options.timeout = AAZStrType() + install_options.wait = AAZStrType() + + upgrade_options = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options.upgrade_options + upgrade_options.atomic = AAZStrType() + upgrade_options.timeout = AAZStrType() + upgrade_options.wait = AAZStrType() + + disc_virtual_network_function = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction") + disc_virtual_network_function.network_function_template = AAZObjectType( + serialized_name="networkFunctionTemplate", + ) + + network_function_template = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template + network_function_template.nfvi_type = AAZStrType( + serialized_name="nfviType", + flags={"required": True}, + ) + + disc_azure_core = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore") + disc_azure_core.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ListHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_arm_template = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate") + disc_arm_template.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_arm_template.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ListHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.template_artifact_profile = AAZObjectType( + serialized_name="templateArtifactProfile", + ) + _ListHelper._build_schema_arm_template_artifact_profile_read(artifact_profile.template_artifact_profile) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.template_mapping_rule_profile = AAZObjectType( + serialized_name="templateMappingRuleProfile", + ) + _ListHelper._build_schema_arm_template_mapping_rule_profile_read(deploy_parameters_mapping_rule_profile.template_mapping_rule_profile) + + disc_vhd_image_file = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile") + disc_vhd_image_file.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_vhd_image_file.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ListHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.vhd_artifact_profile = AAZObjectType( + serialized_name="vhdArtifactProfile", + ) + + vhd_artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").artifact_profile.vhd_artifact_profile + vhd_artifact_profile.vhd_name = AAZStrType( + serialized_name="vhdName", + ) + vhd_artifact_profile.vhd_version = AAZStrType( + serialized_name="vhdVersion", + ) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.vhd_image_mapping_rule_profile = AAZObjectType( + serialized_name="vhdImageMappingRuleProfile", + ) + + vhd_image_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").deploy_parameters_mapping_rule_profile.vhd_image_mapping_rule_profile + vhd_image_mapping_rule_profile.user_configuration = AAZStrType( + serialized_name="userConfiguration", + ) + + disc_azure_operator_nexus = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus") + disc_azure_operator_nexus.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ListHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_arm_template = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate") + disc_arm_template.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_arm_template.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ListHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.template_artifact_profile = AAZObjectType( + serialized_name="templateArtifactProfile", + ) + _ListHelper._build_schema_arm_template_artifact_profile_read(artifact_profile.template_artifact_profile) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.template_mapping_rule_profile = AAZObjectType( + serialized_name="templateMappingRuleProfile", + ) + _ListHelper._build_schema_arm_template_mapping_rule_profile_read(deploy_parameters_mapping_rule_profile.template_mapping_rule_profile) + + disc_image_file = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile") + disc_image_file.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_image_file.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ListHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.image_artifact_profile = AAZObjectType( + serialized_name="imageArtifactProfile", + ) + + image_artifact_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").artifact_profile.image_artifact_profile + image_artifact_profile.image_name = AAZStrType( + serialized_name="imageName", + ) + image_artifact_profile.image_version = AAZStrType( + serialized_name="imageVersion", + ) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.image_mapping_rule_profile = AAZObjectType( + serialized_name="imageMappingRuleProfile", + ) + + image_mapping_rule_profile = cls._schema_on_200.value.Element.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").deploy_parameters_mapping_rule_profile.image_mapping_rule_profile + image_mapping_rule_profile.user_configuration = AAZStrType( + serialized_name="userConfiguration", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + _schema_arm_template_artifact_profile_read = None + + @classmethod + def _build_schema_arm_template_artifact_profile_read(cls, _schema): + if cls._schema_arm_template_artifact_profile_read is not None: + _schema.template_name = cls._schema_arm_template_artifact_profile_read.template_name + _schema.template_version = cls._schema_arm_template_artifact_profile_read.template_version + return + + cls._schema_arm_template_artifact_profile_read = _schema_arm_template_artifact_profile_read = AAZObjectType() + + arm_template_artifact_profile_read = _schema_arm_template_artifact_profile_read + arm_template_artifact_profile_read.template_name = AAZStrType( + serialized_name="templateName", + ) + arm_template_artifact_profile_read.template_version = AAZStrType( + serialized_name="templateVersion", + ) + + _schema.template_name = cls._schema_arm_template_artifact_profile_read.template_name + _schema.template_version = cls._schema_arm_template_artifact_profile_read.template_version + + _schema_arm_template_mapping_rule_profile_read = None + + @classmethod + def _build_schema_arm_template_mapping_rule_profile_read(cls, _schema): + if cls._schema_arm_template_mapping_rule_profile_read is not None: + _schema.template_parameters = cls._schema_arm_template_mapping_rule_profile_read.template_parameters + return + + cls._schema_arm_template_mapping_rule_profile_read = _schema_arm_template_mapping_rule_profile_read = AAZObjectType() + + arm_template_mapping_rule_profile_read = _schema_arm_template_mapping_rule_profile_read + arm_template_mapping_rule_profile_read.template_parameters = AAZStrType( + serialized_name="templateParameters", + ) + + _schema.template_parameters = cls._schema_arm_template_mapping_rule_profile_read.template_parameters + + _schema_depends_on_profile_read = None + + @classmethod + def _build_schema_depends_on_profile_read(cls, _schema): + if cls._schema_depends_on_profile_read is not None: + _schema.install_depends_on = cls._schema_depends_on_profile_read.install_depends_on + _schema.uninstall_depends_on = cls._schema_depends_on_profile_read.uninstall_depends_on + _schema.update_depends_on = cls._schema_depends_on_profile_read.update_depends_on + return + + cls._schema_depends_on_profile_read = _schema_depends_on_profile_read = AAZObjectType() + + depends_on_profile_read = _schema_depends_on_profile_read + depends_on_profile_read.install_depends_on = AAZListType( + serialized_name="installDependsOn", + ) + depends_on_profile_read.uninstall_depends_on = AAZListType( + serialized_name="uninstallDependsOn", + ) + depends_on_profile_read.update_depends_on = AAZListType( + serialized_name="updateDependsOn", + ) + + install_depends_on = _schema_depends_on_profile_read.install_depends_on + install_depends_on.Element = AAZStrType() + + uninstall_depends_on = _schema_depends_on_profile_read.uninstall_depends_on + uninstall_depends_on.Element = AAZStrType() + + update_depends_on = _schema_depends_on_profile_read.update_depends_on + update_depends_on.Element = AAZStrType() + + _schema.install_depends_on = cls._schema_depends_on_profile_read.install_depends_on + _schema.uninstall_depends_on = cls._schema_depends_on_profile_read.uninstall_depends_on + _schema.update_depends_on = cls._schema_depends_on_profile_read.update_depends_on + + _schema_referenced_resource_read = None + + @classmethod + def _build_schema_referenced_resource_read(cls, _schema): + if cls._schema_referenced_resource_read is not None: + _schema.id = cls._schema_referenced_resource_read.id + return + + cls._schema_referenced_resource_read = _schema_referenced_resource_read = AAZObjectType() + + referenced_resource_read = _schema_referenced_resource_read + referenced_resource_read.id = AAZStrType() + + _schema.id = cls._schema_referenced_resource_read.id + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_show.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_show.py new file mode 100644 index 00000000000..65dfcde4881 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_show.py @@ -0,0 +1,627 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-function-definition version show", + is_preview=True, +) +class Show(AAZCommand): + """Get information about a network function definition version. + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkfunctiondefinitiongroups/{}/networkfunctiondefinitionversions/{}", "2023-09-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network function definition group.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.version_name = AAZStrArg( + options=["--version-name"], + help="The name of the network function definition version. The name should conform to the SemVer 2.0.0 specification: https://semver.org/spec/v2.0.0.html.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.NetworkFunctionDefinitionVersionsGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class NetworkFunctionDefinitionVersionsGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkFunctionDefinitionGroups/{networkFunctionDefinitionGroupName}/networkFunctionDefinitionVersions/{networkFunctionDefinitionVersionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkFunctionDefinitionGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "networkFunctionDefinitionVersionName", self.ctx.args.version_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.location = AAZStrType( + flags={"required": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.tags = AAZDictType() + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.deploy_parameters = AAZStrType( + serialized_name="deployParameters", + ) + properties.description = AAZStrType() + properties.network_function_type = AAZStrType( + serialized_name="networkFunctionType", + flags={"required": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + disc_containerized_network_function = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction") + disc_containerized_network_function.network_function_template = AAZObjectType( + serialized_name="networkFunctionTemplate", + ) + + network_function_template = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template + network_function_template.nfvi_type = AAZStrType( + serialized_name="nfviType", + flags={"required": True}, + ) + + disc_azure_arc_kubernetes = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes") + disc_azure_arc_kubernetes.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ShowHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_helm_package = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage") + disc_helm_package.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_helm_package.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ShowHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.helm_artifact_profile = AAZObjectType( + serialized_name="helmArtifactProfile", + ) + + helm_artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile + helm_artifact_profile.helm_package_name = AAZStrType( + serialized_name="helmPackageName", + ) + helm_artifact_profile.helm_package_version_range = AAZStrType( + serialized_name="helmPackageVersionRange", + ) + helm_artifact_profile.image_pull_secrets_values_paths = AAZListType( + serialized_name="imagePullSecretsValuesPaths", + ) + helm_artifact_profile.registry_values_paths = AAZListType( + serialized_name="registryValuesPaths", + ) + + image_pull_secrets_values_paths = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile.image_pull_secrets_values_paths + image_pull_secrets_values_paths.Element = AAZStrType() + + registry_values_paths = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").artifact_profile.helm_artifact_profile.registry_values_paths + registry_values_paths.Element = AAZStrType() + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile = AAZObjectType( + serialized_name="helmMappingRuleProfile", + ) + + helm_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile + helm_mapping_rule_profile.helm_package_version = AAZStrType( + serialized_name="helmPackageVersion", + ) + helm_mapping_rule_profile.options = AAZObjectType() + helm_mapping_rule_profile.release_name = AAZStrType( + serialized_name="releaseName", + ) + helm_mapping_rule_profile.release_namespace = AAZStrType( + serialized_name="releaseNamespace", + ) + helm_mapping_rule_profile.values = AAZStrType() + + options = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options + options.install_options = AAZObjectType( + serialized_name="installOptions", + ) + options.upgrade_options = AAZObjectType( + serialized_name="upgradeOptions", + ) + + install_options = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options.install_options + install_options.atomic = AAZStrType() + install_options.timeout = AAZStrType() + install_options.wait = AAZStrType() + + upgrade_options = cls._schema_on_200.properties.discriminate_by("network_function_type", "ContainerizedNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureArcKubernetes").network_function_applications.Element.discriminate_by("artifact_type", "HelmPackage").deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.options.upgrade_options + upgrade_options.atomic = AAZStrType() + upgrade_options.timeout = AAZStrType() + upgrade_options.wait = AAZStrType() + + disc_virtual_network_function = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction") + disc_virtual_network_function.network_function_template = AAZObjectType( + serialized_name="networkFunctionTemplate", + ) + + network_function_template = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template + network_function_template.nfvi_type = AAZStrType( + serialized_name="nfviType", + flags={"required": True}, + ) + + disc_azure_core = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore") + disc_azure_core.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ShowHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_arm_template = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate") + disc_arm_template.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_arm_template.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ShowHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.template_artifact_profile = AAZObjectType( + serialized_name="templateArtifactProfile", + ) + _ShowHelper._build_schema_arm_template_artifact_profile_read(artifact_profile.template_artifact_profile) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.template_mapping_rule_profile = AAZObjectType( + serialized_name="templateMappingRuleProfile", + ) + _ShowHelper._build_schema_arm_template_mapping_rule_profile_read(deploy_parameters_mapping_rule_profile.template_mapping_rule_profile) + + disc_vhd_image_file = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile") + disc_vhd_image_file.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_vhd_image_file.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ShowHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.vhd_artifact_profile = AAZObjectType( + serialized_name="vhdArtifactProfile", + ) + + vhd_artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").artifact_profile.vhd_artifact_profile + vhd_artifact_profile.vhd_name = AAZStrType( + serialized_name="vhdName", + ) + vhd_artifact_profile.vhd_version = AAZStrType( + serialized_name="vhdVersion", + ) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.vhd_image_mapping_rule_profile = AAZObjectType( + serialized_name="vhdImageMappingRuleProfile", + ) + + vhd_image_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureCore").network_function_applications.Element.discriminate_by("artifact_type", "VhdImageFile").deploy_parameters_mapping_rule_profile.vhd_image_mapping_rule_profile + vhd_image_mapping_rule_profile.user_configuration = AAZStrType( + serialized_name="userConfiguration", + ) + + disc_azure_operator_nexus = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus") + disc_azure_operator_nexus.network_function_applications = AAZListType( + serialized_name="networkFunctionApplications", + ) + + network_function_applications = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications + network_function_applications.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element + _element.artifact_type = AAZStrType( + serialized_name="artifactType", + flags={"required": True}, + ) + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _ShowHelper._build_schema_depends_on_profile_read(_element.depends_on_profile) + _element.name = AAZStrType() + + disc_arm_template = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate") + disc_arm_template.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_arm_template.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ShowHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.template_artifact_profile = AAZObjectType( + serialized_name="templateArtifactProfile", + ) + _ShowHelper._build_schema_arm_template_artifact_profile_read(artifact_profile.template_artifact_profile) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ArmTemplate").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.template_mapping_rule_profile = AAZObjectType( + serialized_name="templateMappingRuleProfile", + ) + _ShowHelper._build_schema_arm_template_mapping_rule_profile_read(deploy_parameters_mapping_rule_profile.template_mapping_rule_profile) + + disc_image_file = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile") + disc_image_file.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + disc_image_file.deploy_parameters_mapping_rule_profile = AAZObjectType( + serialized_name="deployParametersMappingRuleProfile", + ) + + artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").artifact_profile + artifact_profile.artifact_store = AAZObjectType( + serialized_name="artifactStore", + ) + _ShowHelper._build_schema_referenced_resource_read(artifact_profile.artifact_store) + artifact_profile.image_artifact_profile = AAZObjectType( + serialized_name="imageArtifactProfile", + ) + + image_artifact_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").artifact_profile.image_artifact_profile + image_artifact_profile.image_name = AAZStrType( + serialized_name="imageName", + ) + image_artifact_profile.image_version = AAZStrType( + serialized_name="imageVersion", + ) + + deploy_parameters_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").deploy_parameters_mapping_rule_profile + deploy_parameters_mapping_rule_profile.application_enablement = AAZStrType( + serialized_name="applicationEnablement", + ) + deploy_parameters_mapping_rule_profile.image_mapping_rule_profile = AAZObjectType( + serialized_name="imageMappingRuleProfile", + ) + + image_mapping_rule_profile = cls._schema_on_200.properties.discriminate_by("network_function_type", "VirtualNetworkFunction").network_function_template.discriminate_by("nfvi_type", "AzureOperatorNexus").network_function_applications.Element.discriminate_by("artifact_type", "ImageFile").deploy_parameters_mapping_rule_profile.image_mapping_rule_profile + image_mapping_rule_profile.user_configuration = AAZStrType( + serialized_name="userConfiguration", + ) + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + _schema_arm_template_artifact_profile_read = None + + @classmethod + def _build_schema_arm_template_artifact_profile_read(cls, _schema): + if cls._schema_arm_template_artifact_profile_read is not None: + _schema.template_name = cls._schema_arm_template_artifact_profile_read.template_name + _schema.template_version = cls._schema_arm_template_artifact_profile_read.template_version + return + + cls._schema_arm_template_artifact_profile_read = _schema_arm_template_artifact_profile_read = AAZObjectType() + + arm_template_artifact_profile_read = _schema_arm_template_artifact_profile_read + arm_template_artifact_profile_read.template_name = AAZStrType( + serialized_name="templateName", + ) + arm_template_artifact_profile_read.template_version = AAZStrType( + serialized_name="templateVersion", + ) + + _schema.template_name = cls._schema_arm_template_artifact_profile_read.template_name + _schema.template_version = cls._schema_arm_template_artifact_profile_read.template_version + + _schema_arm_template_mapping_rule_profile_read = None + + @classmethod + def _build_schema_arm_template_mapping_rule_profile_read(cls, _schema): + if cls._schema_arm_template_mapping_rule_profile_read is not None: + _schema.template_parameters = cls._schema_arm_template_mapping_rule_profile_read.template_parameters + return + + cls._schema_arm_template_mapping_rule_profile_read = _schema_arm_template_mapping_rule_profile_read = AAZObjectType() + + arm_template_mapping_rule_profile_read = _schema_arm_template_mapping_rule_profile_read + arm_template_mapping_rule_profile_read.template_parameters = AAZStrType( + serialized_name="templateParameters", + ) + + _schema.template_parameters = cls._schema_arm_template_mapping_rule_profile_read.template_parameters + + _schema_depends_on_profile_read = None + + @classmethod + def _build_schema_depends_on_profile_read(cls, _schema): + if cls._schema_depends_on_profile_read is not None: + _schema.install_depends_on = cls._schema_depends_on_profile_read.install_depends_on + _schema.uninstall_depends_on = cls._schema_depends_on_profile_read.uninstall_depends_on + _schema.update_depends_on = cls._schema_depends_on_profile_read.update_depends_on + return + + cls._schema_depends_on_profile_read = _schema_depends_on_profile_read = AAZObjectType() + + depends_on_profile_read = _schema_depends_on_profile_read + depends_on_profile_read.install_depends_on = AAZListType( + serialized_name="installDependsOn", + ) + depends_on_profile_read.uninstall_depends_on = AAZListType( + serialized_name="uninstallDependsOn", + ) + depends_on_profile_read.update_depends_on = AAZListType( + serialized_name="updateDependsOn", + ) + + install_depends_on = _schema_depends_on_profile_read.install_depends_on + install_depends_on.Element = AAZStrType() + + uninstall_depends_on = _schema_depends_on_profile_read.uninstall_depends_on + uninstall_depends_on.Element = AAZStrType() + + update_depends_on = _schema_depends_on_profile_read.update_depends_on + update_depends_on.Element = AAZStrType() + + _schema.install_depends_on = cls._schema_depends_on_profile_read.install_depends_on + _schema.uninstall_depends_on = cls._schema_depends_on_profile_read.uninstall_depends_on + _schema.update_depends_on = cls._schema_depends_on_profile_read.update_depends_on + + _schema_referenced_resource_read = None + + @classmethod + def _build_schema_referenced_resource_read(cls, _schema): + if cls._schema_referenced_resource_read is not None: + _schema.id = cls._schema_referenced_resource_read.id + return + + cls._schema_referenced_resource_read = _schema_referenced_resource_read = AAZObjectType() + + referenced_resource_read = _schema_referenced_resource_read + referenced_resource_read.id = AAZStrType() + + _schema.id = cls._schema_referenced_resource_read.id + + +__all__ = ["Show"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_update_state.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_update_state.py new file mode 100644 index 00000000000..d60add3ee27 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_function_definition/version/_update_state.py @@ -0,0 +1,241 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-function-definition version update-state", + is_preview=True, +) +class UpdateState(AAZCommand): + """Update network function definition version state. + + :example: Change version 2.0.0 of the 'nginx' network function definition group to 'Active' state + az aosm publisher network-function-definition version update-state --resource-group contoso-aosm --publisher-name contoso --group-name nginx --version-name 2.0.0 --version-state Active + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkfunctiondefinitiongroups/{}/networkfunctiondefinitionversions/{}/updatestate", "2023-09-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network function definition group.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.version_name = AAZStrArg( + options=["--version-name"], + help="The name of the network function definition version. The name should conform to the SemVer 2.0.0 specification: https://semver.org/spec/v2.0.0.html.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + + # define Arg Group "Parameters" + + _args_schema = cls._args_schema + _args_schema.version_state = AAZStrArg( + options=["--version-state"], + arg_group="Parameters", + help="The network function definition version state. Only the 'Active' and 'Deprecated' states are allowed for updates. Other states are used for internal state transitioning.", + enum={"Active": "Active", "Deprecated": "Deprecated", "Preview": "Preview", "Unknown": "Unknown", "Validating": "Validating", "ValidationFailed": "ValidationFailed"}, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.NetworkFunctionDefinitionVersionsUpdateState(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class NetworkFunctionDefinitionVersionsUpdateState(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkFunctionDefinitionGroups/{networkFunctionDefinitionGroupName}/networkFunctionDefinitionVersions/{networkFunctionDefinitionVersionName}/updateState", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkFunctionDefinitionGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "networkFunctionDefinitionVersionName", self.ctx.args.version_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("versionState", AAZStrType, ".version_state") + + return self.serialize_content(_content_value) + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.version_state = AAZStrType( + serialized_name="versionState", + ) + + return cls._schema_on_200 + + +class _UpdateStateHelper: + """Helper class for UpdateState""" + + +__all__ = ["UpdateState"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__cmd_group.py new file mode 100644 index 00000000000..98e2935f91f --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher network-service-design", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage network service design resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__cmd_group.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__cmd_group.py new file mode 100644 index 00000000000..37d2080748f --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "aosm publisher network-service-design version", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Commands to manage network service design version resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__init__.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__init__.py new file mode 100644 index 00000000000..93bb64cc975 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/__init__.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._show import * +from ._update_state import * diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_list.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_list.py new file mode 100644 index 00000000000..8e7c34441a6 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_list.py @@ -0,0 +1,358 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-service-design version list", + is_preview=True, +) +class List(AAZCommand): + """List information about the network service design versions available under the specified network service design group. + + :example: List information about the network service design versions available in the 'contoso-service' network service design group + az aosm publisher network-function-definition version list --resource-group contoso-aosm --publisher-name contoso --group-name contoso-service + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkservicedesigngroups/{}/networkservicedesignversions", "2023-09-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network service design group.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.NetworkServiceDesignVersionsListByNetworkServiceDesignGroup(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class NetworkServiceDesignVersionsListByNetworkServiceDesignGroup(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkServiceDesignGroups/{networkServiceDesignGroupName}/networkServiceDesignVersions", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkServiceDesignGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + flags={"read_only": True}, + ) + _schema_on_200.value = AAZListType() + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.configuration_group_schema_references = AAZDictType( + serialized_name="configurationGroupSchemaReferences", + ) + properties.description = AAZStrType() + properties.nfvis_from_site = AAZDictType( + serialized_name="nfvisFromSite", + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.resource_element_templates = AAZListType( + serialized_name="resourceElementTemplates", + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + configuration_group_schema_references = cls._schema_on_200.value.Element.properties.configuration_group_schema_references + configuration_group_schema_references.Element = AAZObjectType() + _ListHelper._build_schema_referenced_resource_read(configuration_group_schema_references.Element) + + nfvis_from_site = cls._schema_on_200.value.Element.properties.nfvis_from_site + nfvis_from_site.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.nfvis_from_site.Element + _element.name = AAZStrType() + _element.type = AAZStrType() + + resource_element_templates = cls._schema_on_200.value.Element.properties.resource_element_templates + resource_element_templates.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.resource_element_templates.Element + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _element.name = AAZStrType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + depends_on_profile = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.depends_on_profile + depends_on_profile.install_depends_on = AAZListType( + serialized_name="installDependsOn", + ) + depends_on_profile.uninstall_depends_on = AAZListType( + serialized_name="uninstallDependsOn", + ) + depends_on_profile.update_depends_on = AAZListType( + serialized_name="updateDependsOn", + ) + + install_depends_on = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.depends_on_profile.install_depends_on + install_depends_on.Element = AAZStrType() + + uninstall_depends_on = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.depends_on_profile.uninstall_depends_on + uninstall_depends_on.Element = AAZStrType() + + update_depends_on = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.depends_on_profile.update_depends_on + update_depends_on.Element = AAZStrType() + + disc_arm_resource_definition = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.discriminate_by("type", "ArmResourceDefinition") + disc_arm_resource_definition.configuration = AAZObjectType() + _ListHelper._build_schema_arm_resource_definition_resource_element_template_read(disc_arm_resource_definition.configuration) + + disc_network_function_definition = cls._schema_on_200.value.Element.properties.resource_element_templates.Element.discriminate_by("type", "NetworkFunctionDefinition") + disc_network_function_definition.configuration = AAZObjectType() + _ListHelper._build_schema_arm_resource_definition_resource_element_template_read(disc_network_function_definition.configuration) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + _schema_arm_resource_definition_resource_element_template_read = None + + @classmethod + def _build_schema_arm_resource_definition_resource_element_template_read(cls, _schema): + if cls._schema_arm_resource_definition_resource_element_template_read is not None: + _schema.artifact_profile = cls._schema_arm_resource_definition_resource_element_template_read.artifact_profile + _schema.parameter_values = cls._schema_arm_resource_definition_resource_element_template_read.parameter_values + _schema.template_type = cls._schema_arm_resource_definition_resource_element_template_read.template_type + return + + cls._schema_arm_resource_definition_resource_element_template_read = _schema_arm_resource_definition_resource_element_template_read = AAZObjectType() + + arm_resource_definition_resource_element_template_read = _schema_arm_resource_definition_resource_element_template_read + arm_resource_definition_resource_element_template_read.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + arm_resource_definition_resource_element_template_read.parameter_values = AAZStrType( + serialized_name="parameterValues", + ) + arm_resource_definition_resource_element_template_read.template_type = AAZStrType( + serialized_name="templateType", + ) + + artifact_profile = _schema_arm_resource_definition_resource_element_template_read.artifact_profile + artifact_profile.artifact_name = AAZStrType( + serialized_name="artifactName", + ) + artifact_profile.artifact_store_reference = AAZObjectType( + serialized_name="artifactStoreReference", + ) + cls._build_schema_referenced_resource_read(artifact_profile.artifact_store_reference) + artifact_profile.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + _schema.artifact_profile = cls._schema_arm_resource_definition_resource_element_template_read.artifact_profile + _schema.parameter_values = cls._schema_arm_resource_definition_resource_element_template_read.parameter_values + _schema.template_type = cls._schema_arm_resource_definition_resource_element_template_read.template_type + + _schema_referenced_resource_read = None + + @classmethod + def _build_schema_referenced_resource_read(cls, _schema): + if cls._schema_referenced_resource_read is not None: + _schema.id = cls._schema_referenced_resource_read.id + return + + cls._schema_referenced_resource_read = _schema_referenced_resource_read = AAZObjectType() + + referenced_resource_read = _schema_referenced_resource_read + referenced_resource_read.id = AAZStrType() + + _schema.id = cls._schema_referenced_resource_read.id + + +__all__ = ["List"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_show.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_show.py new file mode 100644 index 00000000000..af3693152e1 --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_show.py @@ -0,0 +1,359 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-service-design version show", + is_preview=True, +) +class Show(AAZCommand): + """Get information about a network service design version. + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkservicedesigngroups/{}/networkservicedesignversions/{}", "2023-09-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network service design group.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.version_name = AAZStrArg( + options=["--version-name"], + help="The name of the network service design version. The name should conform to the SemVer 2.0.0 specification: https://semver.org/spec/v2.0.0.html.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.NetworkServiceDesignVersionsGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class NetworkServiceDesignVersionsGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkServiceDesignGroups/{networkServiceDesignGroupName}/networkServiceDesignVersions/{networkServiceDesignVersionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkServiceDesignGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "networkServiceDesignVersionName", self.ctx.args.version_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.location = AAZStrType( + flags={"required": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.tags = AAZDictType() + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.configuration_group_schema_references = AAZDictType( + serialized_name="configurationGroupSchemaReferences", + ) + properties.description = AAZStrType() + properties.nfvis_from_site = AAZDictType( + serialized_name="nfvisFromSite", + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.resource_element_templates = AAZListType( + serialized_name="resourceElementTemplates", + ) + properties.version_state = AAZStrType( + serialized_name="versionState", + ) + + configuration_group_schema_references = cls._schema_on_200.properties.configuration_group_schema_references + configuration_group_schema_references.Element = AAZObjectType() + _ShowHelper._build_schema_referenced_resource_read(configuration_group_schema_references.Element) + + nfvis_from_site = cls._schema_on_200.properties.nfvis_from_site + nfvis_from_site.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.nfvis_from_site.Element + _element.name = AAZStrType() + _element.type = AAZStrType() + + resource_element_templates = cls._schema_on_200.properties.resource_element_templates + resource_element_templates.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.resource_element_templates.Element + _element.depends_on_profile = AAZObjectType( + serialized_name="dependsOnProfile", + ) + _element.name = AAZStrType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + depends_on_profile = cls._schema_on_200.properties.resource_element_templates.Element.depends_on_profile + depends_on_profile.install_depends_on = AAZListType( + serialized_name="installDependsOn", + ) + depends_on_profile.uninstall_depends_on = AAZListType( + serialized_name="uninstallDependsOn", + ) + depends_on_profile.update_depends_on = AAZListType( + serialized_name="updateDependsOn", + ) + + install_depends_on = cls._schema_on_200.properties.resource_element_templates.Element.depends_on_profile.install_depends_on + install_depends_on.Element = AAZStrType() + + uninstall_depends_on = cls._schema_on_200.properties.resource_element_templates.Element.depends_on_profile.uninstall_depends_on + uninstall_depends_on.Element = AAZStrType() + + update_depends_on = cls._schema_on_200.properties.resource_element_templates.Element.depends_on_profile.update_depends_on + update_depends_on.Element = AAZStrType() + + disc_arm_resource_definition = cls._schema_on_200.properties.resource_element_templates.Element.discriminate_by("type", "ArmResourceDefinition") + disc_arm_resource_definition.configuration = AAZObjectType() + _ShowHelper._build_schema_arm_resource_definition_resource_element_template_read(disc_arm_resource_definition.configuration) + + disc_network_function_definition = cls._schema_on_200.properties.resource_element_templates.Element.discriminate_by("type", "NetworkFunctionDefinition") + disc_network_function_definition.configuration = AAZObjectType() + _ShowHelper._build_schema_arm_resource_definition_resource_element_template_read(disc_network_function_definition.configuration) + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + tags = cls._schema_on_200.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + _schema_arm_resource_definition_resource_element_template_read = None + + @classmethod + def _build_schema_arm_resource_definition_resource_element_template_read(cls, _schema): + if cls._schema_arm_resource_definition_resource_element_template_read is not None: + _schema.artifact_profile = cls._schema_arm_resource_definition_resource_element_template_read.artifact_profile + _schema.parameter_values = cls._schema_arm_resource_definition_resource_element_template_read.parameter_values + _schema.template_type = cls._schema_arm_resource_definition_resource_element_template_read.template_type + return + + cls._schema_arm_resource_definition_resource_element_template_read = _schema_arm_resource_definition_resource_element_template_read = AAZObjectType() + + arm_resource_definition_resource_element_template_read = _schema_arm_resource_definition_resource_element_template_read + arm_resource_definition_resource_element_template_read.artifact_profile = AAZObjectType( + serialized_name="artifactProfile", + ) + arm_resource_definition_resource_element_template_read.parameter_values = AAZStrType( + serialized_name="parameterValues", + ) + arm_resource_definition_resource_element_template_read.template_type = AAZStrType( + serialized_name="templateType", + ) + + artifact_profile = _schema_arm_resource_definition_resource_element_template_read.artifact_profile + artifact_profile.artifact_name = AAZStrType( + serialized_name="artifactName", + ) + artifact_profile.artifact_store_reference = AAZObjectType( + serialized_name="artifactStoreReference", + ) + cls._build_schema_referenced_resource_read(artifact_profile.artifact_store_reference) + artifact_profile.artifact_version = AAZStrType( + serialized_name="artifactVersion", + ) + + _schema.artifact_profile = cls._schema_arm_resource_definition_resource_element_template_read.artifact_profile + _schema.parameter_values = cls._schema_arm_resource_definition_resource_element_template_read.parameter_values + _schema.template_type = cls._schema_arm_resource_definition_resource_element_template_read.template_type + + _schema_referenced_resource_read = None + + @classmethod + def _build_schema_referenced_resource_read(cls, _schema): + if cls._schema_referenced_resource_read is not None: + _schema.id = cls._schema_referenced_resource_read.id + return + + cls._schema_referenced_resource_read = _schema_referenced_resource_read = AAZObjectType() + + referenced_resource_read = _schema_referenced_resource_read + referenced_resource_read.id = AAZStrType() + + _schema.id = cls._schema_referenced_resource_read.id + + +__all__ = ["Show"] diff --git a/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_update_state.py b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_update_state.py new file mode 100644 index 00000000000..1d1b4a37a2c --- /dev/null +++ b/src/aosm/azext_aosm/aaz/latest/aosm/publisher/network_service_design/version/_update_state.py @@ -0,0 +1,241 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "aosm publisher network-service-design version update-state", + is_preview=True, +) +class UpdateState(AAZCommand): + """Update network service design version state. + + :example: Change version 1.0.0 of the 'contoso-service' network service design group to 'Deprecated' state + az aosm publisher network-service-design version update-state --resource-group contoso-aosm --publisher-name contoso --group-name contoso-service --version-name 1.0.0 --version-state Deprecated + """ + + _aaz_info = { + "version": "2023-09-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.hybridnetwork/publishers/{}/networkservicedesigngroups/{}/networkservicedesignversions/{}/updatestate", "2023-09-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.group_name = AAZStrArg( + options=["-n", "--group-name"], + help="The name of the network service design group.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.version_name = AAZStrArg( + options=["--version-name"], + help="The name of the network service design version. The name should conform to the SemVer 2.0.0 specification: https://semver.org/spec/v2.0.0.html.", + required=True, + id_part="child_name_2", + fmt=AAZStrArgFormat( + pattern="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + max_length=64, + ), + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + help="The name of the publisher.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]*$", + max_length=64, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + + # define Arg Group "Parameters" + + _args_schema = cls._args_schema + _args_schema.version_state = AAZStrArg( + options=["--version-state"], + arg_group="Parameters", + help="The network service design version state.", + enum={"Active": "Active", "Deprecated": "Deprecated", "Preview": "Preview", "Unknown": "Unknown"}, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.NetworkServiceDesignVersionsUpdateState(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class NetworkServiceDesignVersionsUpdateState(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.HybridNetwork/publishers/{publisherName}/networkServiceDesignGroups/{networkServiceDesignGroupName}/networkServiceDesignVersions/{networkServiceDesignVersionName}/updateState", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "networkServiceDesignGroupName", self.ctx.args.group_name, + required=True, + ), + **self.serialize_url_param( + "networkServiceDesignVersionName", self.ctx.args.version_name, + required=True, + ), + **self.serialize_url_param( + "publisherName", self.ctx.args.publisher_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-09-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("versionState", AAZStrType, ".version_state") + + return self.serialize_content(_content_value) + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.version_state = AAZStrType( + serialized_name="versionState", + ) + + return cls._schema_on_200 + + +class _UpdateStateHelper: + """Helper class for UpdateState""" + + +__all__ = ["UpdateState"] diff --git a/src/aosm/azext_aosm/azext_metadata.json b/src/aosm/azext_aosm/azext_metadata.json index be5de02d927..4921a349027 100644 --- a/src/aosm/azext_aosm/azext_metadata.json +++ b/src/aosm/azext_aosm/azext_metadata.json @@ -1,4 +1,4 @@ { "azext.isPreview": true, - "azext.minCliCoreVersion": "2.45.0" + "azext.minCliCoreVersion": "2.54.0" } \ No newline at end of file diff --git a/src/aosm/azext_aosm/delete/__init__.py b/src/aosm/azext_aosm/build_processors/__init__.py similarity index 100% rename from src/aosm/azext_aosm/delete/__init__.py rename to src/aosm/azext_aosm/build_processors/__init__.py diff --git a/src/aosm/azext_aosm/build_processors/arm_processor.py b/src/aosm/azext_aosm/build_processors/arm_processor.py new file mode 100644 index 00000000000..dbbcfd0cc44 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/arm_processor.py @@ -0,0 +1,250 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from pathlib import Path +from abc import abstractmethod +from typing import List, Tuple, final + +from knack.log import get_logger + +from azext_aosm.build_processors.base_processor import BaseInputProcessor +from azext_aosm.common.artifact import (BaseArtifact, LocalFileACRArtifact) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.vendored_sdks.models import ( + ArtifactType, + AzureOperatorNexusNetworkFunctionArmTemplateApplication, + ApplicationEnablement, ArmResourceDefinitionResourceElementTemplate, + ArmResourceDefinitionResourceElementTemplateDetails, + ArmTemplateArtifactProfile, + ArmTemplateMappingRuleProfile, + AzureCoreArmTemplateArtifactProfile, + NetworkFunctionApplication, NSDArtifactProfile, + ResourceElementTemplate, TemplateType, AzureOperatorNexusArtifactType, + AzureOperatorNexusArmTemplateDeployMappingRuleProfile, AzureOperatorNexusArmTemplateArtifactProfile, + AzureCoreArmTemplateDeployMappingRuleProfile, + AzureCoreArtifactType, + AzureCoreNetworkFunctionArmTemplateApplication, + DependsOnProfile, + ManifestArtifactFormat, + ReferencedResource, +) + +from azext_aosm.common.constants import ( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + TEMPLATE_PARAMETERS_FILENAME) + + +logger = get_logger(__name__) + + +class BaseArmBuildProcessor(BaseInputProcessor): + """ + Base class for ARM template processors. + + This class loosely implements the Template Method pattern to define the steps required + to generate NF applications and RETs from a given ARM template. + + The steps are as follows: + - generate_schema + - generate_mappings + - generate_artifact_profile + - generate_nfvi_specific_nf_application + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + input_artifact: ArmTemplateInput + + def __init__(self, name: str, input_artifact: ArmTemplateInput, expose_all_params: bool): + super().__init__(name, input_artifact, expose_all_params) + + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + logger.debug( + "Getting artifact manifest list for ARM template input %s.", self.name + ) + return [ + ManifestArtifactFormat( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.ARM_TEMPLATE.value, + artifact_version=self.input_artifact.artifact_version, + ) + ] + + def get_artifact_details( + self, + ) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseArtifact], List[LocalFileBuilder]] + """ + logger.info("Getting artifact details for ARM template input.") + + # We only support local file artifacts for ARM templates and we don't need to build them so + # no local file builders are needed. + return ( + [ + LocalFileACRArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.ARM_TEMPLATE.value, + artifact_version=self.input_artifact.artifact_version, + file_path=self.input_artifact.template_path, + ) + ], + [], + ) + + @final + def generate_nf_application(self) -> NetworkFunctionApplication: + return self.generate_nfvi_specific_nf_application() + + @abstractmethod + def generate_nfvi_specific_nf_application(self): + pass + + def generate_resource_element_template(self) -> ResourceElementTemplate: + """Generate the resource element template. + + Note: There is no Nexus specific RET + """ + parameter_values = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults(), True + ) + + artifact_profile = NSDArtifactProfile( + artifact_store_reference=ReferencedResource(id=""), + artifact_name=self.input_artifact.artifact_name, + artifact_version=self.input_artifact.artifact_version, + ) + + return ArmResourceDefinitionResourceElementTemplateDetails( + name=self.name, + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + configuration=ArmResourceDefinitionResourceElementTemplate( + template_type=TemplateType.ARM_TEMPLATE.value, + parameter_values=json.dumps(parameter_values), + artifact_profile=artifact_profile, + ), + ) + + def generate_parameters_file(self) -> LocalFileBuilder: + """Generate parameters file.""" + mapping_rule_profile = self._generate_mapping_rule_profile() + params = ( + mapping_rule_profile.template_mapping_rule_profile.template_parameters + ) + logger.info( + "Created parameters file for arm template." + ) + return LocalFileBuilder( + Path( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + self.input_artifact.artifact_name + '-' + TEMPLATE_PARAMETERS_FILENAME, + ), + json.dumps(json.loads(params), indent=4), + ) + + @abstractmethod + def _generate_mapping_rule_profile(self): + pass + + +class AzureCoreArmBuildProcessor(BaseArmBuildProcessor): + """This class represents an ARM template processor for Azure Core.""" + + def generate_nfvi_specific_nf_application( + self, + ) -> AzureCoreNetworkFunctionArmTemplateApplication: + return AzureCoreNetworkFunctionArmTemplateApplication( + name=self.name, + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + artifact_type=AzureCoreArtifactType.ARM_TEMPLATE, + artifact_profile=self._generate_artifact_profile(), + deploy_parameters_mapping_rule_profile=self._generate_mapping_rule_profile(), + ) + + def _generate_mapping_rule_profile( + self, + ) -> AzureCoreArmTemplateDeployMappingRuleProfile: + template_parameters = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults() + ) + + mapping_profile = ArmTemplateMappingRuleProfile( + template_parameters=json.dumps(template_parameters) + ) + + return AzureCoreArmTemplateDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + template_mapping_rule_profile=mapping_profile, + ) + + def _generate_artifact_profile(self) -> AzureCoreArmTemplateArtifactProfile: + artifact_profile = ArmTemplateArtifactProfile( + template_name=self.input_artifact.artifact_name, + template_version=self.input_artifact.artifact_version, + ) + return AzureCoreArmTemplateArtifactProfile( + artifact_store=ReferencedResource(id=""), + template_artifact_profile=artifact_profile, + ) + + +class NexusArmBuildProcessor(BaseArmBuildProcessor): + """ + This class represents a processor for generating ARM templates specific to Nexus. + """ + def generate_nfvi_specific_nf_application( + self, + ) -> AzureOperatorNexusNetworkFunctionArmTemplateApplication: + return AzureOperatorNexusNetworkFunctionArmTemplateApplication( + name=self.name, + depends_on_profile=DependsOnProfile(install_depends_on=[], + uninstall_depends_on=[], update_depends_on=[]), + artifact_type=AzureOperatorNexusArtifactType.ARM_TEMPLATE, + artifact_profile=self._generate_artifact_profile(), + deploy_parameters_mapping_rule_profile=self._generate_mapping_rule_profile(), + ) + + def _generate_mapping_rule_profile( + self, + ) -> AzureOperatorNexusArmTemplateDeployMappingRuleProfile: + template_parameters = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults() + ) + + mapping_profile = ArmTemplateMappingRuleProfile( + template_parameters=json.dumps(template_parameters) + ) + + return AzureOperatorNexusArmTemplateDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + template_mapping_rule_profile=mapping_profile, + ) + + def _generate_artifact_profile(self) -> AzureOperatorNexusArmTemplateArtifactProfile: + artifact_profile = ArmTemplateArtifactProfile( + template_name=self.input_artifact.artifact_name, + template_version=self.input_artifact.artifact_version, + ) + return AzureOperatorNexusArmTemplateArtifactProfile( + artifact_store=ReferencedResource(id=""), + template_artifact_profile=artifact_profile, + ) diff --git a/src/aosm/azext_aosm/build_processors/base_processor.py b/src/aosm/azext_aosm/build_processors/base_processor.py new file mode 100644 index 00000000000..bcf92d6e357 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/base_processor.py @@ -0,0 +1,319 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from abc import ABC, abstractmethod +from typing import Any, Dict, List, Optional, Tuple + +from knack.log import get_logger +from azure.cli.core.azclierror import InvalidArgumentValueError +from azext_aosm.common.artifact import BaseArtifact +from azext_aosm.common.constants import CGS_NAME +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.base_input import BaseInput +from azext_aosm.vendored_sdks.models import ( + ManifestArtifactFormat, + NetworkFunctionApplication, + ResourceElementTemplate, +) + +logger = get_logger(__name__) + + +class BaseInputProcessor(ABC): + """ + A base class for input processors. + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + + def __init__(self, name: str, input_artifact: BaseInput, expose_all_params: bool): + self.name = name + self.input_artifact = input_artifact + self.expose_all_params = expose_all_params + + @abstractmethod + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + raise NotImplementedError + + @abstractmethod + def get_artifact_details(self) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseArtifact], List[LocalFileBuilder]] + """ + raise NotImplementedError + + @abstractmethod + def generate_nf_application(self) -> NetworkFunctionApplication: + """ + Generate the network function application from the input. + + :return: The network function application. + :rtype: NetworkFunctionApplication + """ + raise NotImplementedError + + @abstractmethod + def generate_resource_element_template(self) -> ResourceElementTemplate: + """ + Generate the resource element template from the input. + + :return: The resource element template. + :rtype: ResourceElementTemplate + """ + raise NotImplementedError + + def generate_schema(self) -> Dict[str, Any]: + """ + Generate the schema for values the user must provide. + + :return: A dictionary containing the schema. + :rtype: Dict[str, Any] + """ + logger.info( + "Generating parameter schema for %s with expose_all_params set to %s", + self.name, + self.expose_all_params, + ) + + base_params_schema = """ + { + "%s": { + "type": "object", + "properties": {}, + "required": [] + } + } + """ % ( + self.name + ) + + params_schema = json.loads(base_params_schema) + + self._generate_schema( + params_schema[self.name], + self.input_artifact.get_schema(), + self.input_artifact.get_defaults(), + ) + + # If there are no properties, return an empty dict as we don't need + # a schema for this input artifact. + if not bool(params_schema[self.name]["properties"]): + params_schema = {} + + logger.debug( + "Parameter schema for %s: %s", + self.name, + json.dumps(params_schema, indent=4), + ) + + return params_schema + + def _generate_schema( + self, + deploy_params_schema: Dict[str, Any], + source_schema: Dict[str, Any], + default_values: Dict[str, Any], + param_prefix: Optional[str] = None, + ) -> None: + """ + Generate the JSON schema for deployParameters. The NFDProcessor overrides this method to generate CGS for NSDs. + + This method recursively generates the deployParameters schema for the input artifact by updating + the schema parameter. + + Note there is no return value. The schema is passed by reference and modified in place. + + Parameters: + deploy_params_schema: + The schema to be modified. On first call of this method, + it should contain any base nodes for the schema. + This schema is passed by reference and modified in place. + This property is defined by the CLI in the base processor. + source_schema: + The source schema from which the deploy parameters schema is generated. + E.g., for a Helm chart this may be the schema generated from the values.yaml file. + For an ARM template this will be the schema generated from the template's parameters + default_values: + The default values used to determine whether a parameter should be hardcoded or provided by the user. + Defined by the input artifact classes from the config provided by the user. + E.g. for helm charts this can be the default values file or the values.yaml file in the chart. + param_prefix: + The prefix to be added to the parameter name. + This is used for namespacing nested properties in the schema. + On first call to this method this should be None. + """ + if "properties" not in source_schema.keys(): + return + # Abbreviated 'prop' because 'property' is built in. + for prop, details in source_schema["properties"].items(): + param_name = prop if not param_prefix else f"{param_prefix}_{prop}" + + # Note: We don't recurse into arrays. For arrays we just dump all the `details` as is. + # This is because arrays are 'tricky'. This approach may need to be revisited at some point. + + # We have three types of parameter to handle in the following if statements: + # 1. Required parameters (in 'required' array and no default given). + if ( + prop not in default_values + and "required" in source_schema + and prop in source_schema["required"] + ): + # Note: we only recurse into objects, not arrays. For now, this is sufficient. + if "properties" in details: + self._generate_schema( + deploy_params_schema, + details, + default_values={}, # In this branch property_key is not in defaults, so pass empty dict. + param_prefix=param_name, + ) + else: + deploy_params_schema["required"].append(param_name) + deploy_params_schema["properties"][param_name] = details + # 2. Optional parameters that have child properties. These aren't added to the schema here (default + # behaviour), but we check their children. + elif prop in default_values and "properties" in details: + self._generate_schema( + deploy_params_schema, details, default_values[prop], param_name + ) + # 3. Other optional parameters. By default these are excluded from the schema to minimise the number of + # parameters the user needs to deal with in the schemas, but expose_all_params means we include them. + elif self.expose_all_params: + if "properties" in details: + # default_values is an empty dict. If there were defaults, elif #2 would have caught them + self._generate_schema( + deploy_params_schema, + details, + default_values={}, + param_prefix=param_name, + ) + else: + if prop in default_values: + # AOSM wants null as a string + # TODO: Flag with RP that this is a bug? + if default_values[prop] is None: + default_values[prop] = "null" + details["default"] = default_values[prop] + # If there is a default of '[resourceGroup().location]' + # which is not allowed to be passed into CGVs + if "default" in details and details["default"] == '[resourceGroup().location]': + # Remove the default + del details["default"] + # Make the parameter required (to expose in CGVS without a default) + deploy_params_schema["required"].append(param_name) + deploy_params_schema["properties"][param_name] = details + + def generate_values_mappings( + self, + schema: Dict[str, Any], + mapping: Dict[str, Any], + is_ret: bool = False, + param_prefix: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Generates a mapping from user-provided values to the parameters of an ARM template. + + The mapping also allows for values to be "hardcoded", i.e., the value is provided in the dictionary, not as + a reference to a deployParameter / CGV. + + This method iterates over the properties in the provided schema. For each property, it checks if it's a + required parameter or optional, and if it has child properties. Based on these checks, it generates a + mapping for the parameter: + + - If a parameter is required and not present in the initial mappings, it adds a mapping for it. + The user must provide this value. + - If optional, the existing hardcoded value will be left in place (but see next point about + expose_all_params) and the user can't set this value. + - If expose_all_params is set, it generates mappings for all optional parameters. If the user doesn't + provide this value, AOSM will use the default value from the corresponding deployParameters schema. + - If the parameter has child properties, it recursively generates mappings (if necessary) for them. + + The mapping is returned as a dictionary where the keys are the parameter names and the values are either + hardcoded values or references to user-provided values. + + Parameters: + schema: The schema defining the user inputs from which the mapping will be generated. + mapping: Initially a dictionary of hardcoded default values. Any parameters required by the schema that are + not present in the mapping dictionary will be added (recursively) as a mapping to the user input. + is_ret: Whether the mapping is for a resource element template (RET). + """ + if "properties" not in schema.keys(): + return mapping + for prop, prop_schema in schema["properties"].items(): + if isinstance(prop_schema, dict) and "type" not in prop_schema: + if "oneOf" in prop_schema or "anyOf" in prop_schema: + raise InvalidArgumentValueError( + f"The subschema '{prop}' does not contain a type.\n" + "It contains 'anyOf' or 'oneOf' logic, which is not supported by AOSM.\n" + "Please remove this from your values.schema.json and provide a concrete type " + "or remove the schema and the CLI will generate a schema from your values.yaml file." + ) + raise InvalidArgumentValueError( + f"The subschema {prop} does not contain a type. This is a required field.\n" + "Please fix your values.schema.json or remove the schema and the CLI will generate a " + "schema from your values.yaml file." + ) + + param_name = prop if param_prefix is None else f"{param_prefix}_{prop}" + # We have three types of parameter to handle in the following if statements (analagous to + # generate_schema()): + # 1. Required parameters (in 'required' array and no default given). + if ( + "required" in schema + and prop in schema["required"] + and prop not in mapping + ): + if "properties" in prop_schema: + mapping[prop] = self.generate_values_mappings( + prop_schema, {}, is_ret, param_name + ) + else: + mapping[prop] = ( + f"{{configurationparameters('{CGS_NAME}').{self.name}.{param_name}}}" + if is_ret + else f"{{deployParameters.{self.name}.{param_name}}}" + ) + # 2. Optional parameters (i.e. they have a default in the mapping dict) that have child properties. + # These aren't added to the mapping here, but we check their children. + elif prop in mapping and "properties" in prop_schema: + # Python evaluates {} as False, so we need to explicitly set to {} + prop_mapping = mapping[prop] or {} + mapping[prop] = self.generate_values_mappings( + prop_schema, prop_mapping, is_ret, param_name + ) + # 3. Other optional parameters. By default these are hardcoded in the mapping to minimise the number of + # parameters the user needs to deal with, but expose_all_params means we map them from the user + # provided values. + elif self.expose_all_params: + if "properties" in prop_schema: + # Mapping is an empty dict. + # If there were defaults in the mapping dict, the elif above would have caught them + mapping[prop] = self.generate_values_mappings( + prop_schema, {}, is_ret, param_name + ) + else: + mapping[prop] = ( + f"{{configurationparameters('{CGS_NAME}').{self.name}.{param_name}}}" + if is_ret + else f"{{deployParameters.{self.name}.{param_name}}}" + ) + + logger.debug( + "Output of generate_values_mappings for %s:\n%s", + self.name, + json.dumps(mapping, indent=4), + ) + + return mapping diff --git a/src/aosm/azext_aosm/build_processors/helm_chart_processor.py b/src/aosm/azext_aosm/build_processors/helm_chart_processor.py new file mode 100644 index 00000000000..f13a3ac4555 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/helm_chart_processor.py @@ -0,0 +1,402 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import re +from typing import Any, Dict, List, Set, Tuple + +from knack.log import get_logger + +from azext_aosm.build_processors.base_processor import BaseInputProcessor +from azext_aosm.common.artifact import ( + BaseArtifact, + LocalFileACRArtifact, + RemoteACRArtifact, +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.helm_chart_input import HelmChartInput +from azext_aosm.common.registry import ContainerRegistryHandler +from azext_aosm.vendored_sdks.models import ( + ApplicationEnablement, + ArtifactType, + AzureArcKubernetesArtifactProfile, + AzureArcKubernetesDeployMappingRuleProfile, + AzureArcKubernetesHelmApplication, + DependsOnProfile, + HelmArtifactProfile, + HelmMappingRuleProfile, + ManifestArtifactFormat, + ReferencedResource, + ResourceElementTemplate, +) + +logger = get_logger(__name__) + +VALUE_PATH_REGEX = ( + r".Values\.([^\s})]*)" # Regex to find values paths in Helm chart templates +) +IMAGE_NAME_AND_VERSION_REGEX = r"\/(?P[^\s]*):(?P[^\s)\"}]*)" + + +class HelmChartProcessor(BaseInputProcessor): + """ + A class for processing Helm Chart inputs. + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + input_artifact: HelmChartInput + + def __init__( + self, + name: str, + input_artifact: HelmChartInput, + expose_all_params: bool, + registry_handler: ContainerRegistryHandler, + ): + super().__init__(name, input_artifact, expose_all_params) + self.registry_handler = registry_handler + self.input_artifact: HelmChartInput = input_artifact + + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + logger.debug( + "Getting artifact manifest list for Helm chart input %s.", self.name + ) + artifact_manifest_list = [] + artifact_manifest_list.append( + ManifestArtifactFormat( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.OCI_ARTIFACT.value, + artifact_version=self.input_artifact.artifact_version, + ) + ) + + # Add an artifact for each image used by the Helm chart + for image_name, image_version in self._find_chart_images(): + artifact_manifest_list.append( + ManifestArtifactFormat( + artifact_name=image_name, + artifact_type=ArtifactType.OCI_ARTIFACT.value, + artifact_version=image_version, + ) + ) + + return artifact_manifest_list + + def get_artifact_details( + self, + ) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseACRArtifact], List[LocalFileBuilder]] + """ + logger.debug("Getting artifact details for Helm chart input %s.", self.name) + artifact_details: List[BaseArtifact] = [] + + # Helm charts can only be local file artifacts + helm_chart_details = LocalFileACRArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.OCI_ARTIFACT.value, + artifact_version=self.input_artifact.artifact_version, + file_path=self.input_artifact.chart_path, + ) + artifact_details.append(helm_chart_details) + for image_name, image_version in self._find_chart_images(): + # Container images can only be remote ACR artifacts + registry, namespace = self.registry_handler.find_registry_for_image( + image_name, image_version + ) + if registry is None or namespace is None: + continue + + artifact_details.append( + RemoteACRArtifact( + artifact_name=image_name, + artifact_type=ArtifactType.OCI_ARTIFACT.value, + artifact_version=image_version, + source_registry=registry, + registry_namespace=namespace, + ) + ) + + # We do not need to return any local file builders as no artifacts are being built + # by the CLI for Helm charts + return artifact_details, [] + + def generate_resource_element_template(self) -> ResourceElementTemplate: + """ + Generate the resource element template from the input. + + :raises NotImplementedError: Helm charts do not support resource element templates. + """ + raise NotImplementedError("NSDs do not support deployment of Helm charts.") + + def generate_nf_application(self) -> AzureArcKubernetesHelmApplication: + """ + Generates an Azure Arc Kubernetes Helm application for the given Helm chart. + + :return: The generated Azure Arc Kubernetes Helm application. + :rtype: AzureArcKubernetesHelmApplication + """ + logger.debug("Generating NF application for Helm chart input %s.", self.name) + artifact_profile = self._generate_artifact_profile() + assert artifact_profile.helm_artifact_profile is not None + + # Remove the registry values paths and image pull secrets values paths from the values mappings + # as these values are supplied by NFM when it installs the chart. + registry_values_paths = ( + artifact_profile.helm_artifact_profile.registry_values_paths or [] + ) + image_pull_secrets_values_paths = ( + artifact_profile.helm_artifact_profile.image_pull_secrets_values_paths + or [] + ) + mapping_rule_profile = self._generate_mapping_rule_profile( + values_to_remove=registry_values_paths + image_pull_secrets_values_paths + ) + + return AzureArcKubernetesHelmApplication( + name=self.name, + # Current implementation is set all depends on profiles to empty lists + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + artifact_profile=artifact_profile, + deploy_parameters_mapping_rule_profile=mapping_rule_profile, + ) + + def _find_chart_images(self) -> Set[Tuple[str, str]]: + """ + Find the images used by the Helm chart. + + :return: A list of tuples containing the image name and version. + :rtype: Set[Tuple[str, str]] + """ + logger.debug("Finding images used by Helm chart %s", self.name) + image_lines: Set[str] = set() + self._find_image_lines(self.input_artifact, image_lines) + + images: Set[Tuple[str, str]] = set() + for line in image_lines: + name_and_tag = re.search(IMAGE_NAME_AND_VERSION_REGEX, line) + if name_and_tag and len(name_and_tag.groups()) == 2: + image_name = name_and_tag.group("name") + image_tag = name_and_tag.group("tag") + logger.debug( + "Found image %s:%s in Helm chart %s", + image_name, + image_tag, + self.name, + ) + images.add((image_name, image_tag)) + else: + logger.warning( + "Could not parse image name and tag in line %s in Helm chart %s", + line, + self.name, + ) + + return images + + @staticmethod + def _find_image_lines(chart: HelmChartInput, image_lines: Set[str]) -> None: + """ + Finds the lines containing image references in the given Helm chart and its dependencies. + + :param image_lines: A set of image lines found so far. + :type image_lines: Set[str] + """ + logger.debug("Finding image lines in Helm chart %s", chart.artifact_name) + # Find the image lines in the current chart + + template_lines = [] + + if chart.helm_template is not None: + template_lines = chart.helm_template.split("\n") + + for line in template_lines: + if "image:" in line: + logger.debug( + "Found image line %s in Helm chart %s", + line, + chart.artifact_name, + ) + image_lines.add(line.replace("image:", "").strip()) + + def _generate_artifact_profile(self) -> AzureArcKubernetesArtifactProfile: + """ + Generates an Azure Arc Kubernetes artifact profile for the given artifact store and Helm chart. + + :return: The generated Azure Arc Kubernetes artifact profile. + :rtype: AzureArcKubernetesArtifactProfile + """ + logger.debug("Generating artifact profile for Helm chart input.") + image_pull_secrets_values_paths: Set[str] = set() + # self._find_image_pull_secrets_values_paths( + # self.input_artifact, image_pull_secrets_values_paths + # ) + + registry_values_paths = self._find_registry_values_paths() + + helm_artifact_profile = HelmArtifactProfile( + helm_package_name=self.input_artifact.artifact_name, + helm_package_version_range=self.input_artifact.artifact_version, + registry_values_paths=list(registry_values_paths), + image_pull_secrets_values_paths=list(image_pull_secrets_values_paths), + ) + + # We set the artifact store ID as an empty string as the builder will + # set the artifact store ID when it writes the bicep template for the NFD. + return AzureArcKubernetesArtifactProfile( + artifact_store=ReferencedResource(id=""), + helm_artifact_profile=helm_artifact_profile, + ) + + def _find_image_pull_secrets_values_paths( + self, chart: HelmChartInput, matches: Set[str] + ) -> None: + """ + Find image pull secrets values paths in the Helm chart templates. + + Recursively searches the dependency charts for image pull secrets values paths and adds them to the matches set. + + :param chart: The Helm chart to search. + :type chart: HelmChartInput + :param matches: A set of image pull secrets values paths found so far. + :type matches: Set[str] + """ + logger.debug( + "Finding image pull secrets values paths in Helm chart %s", + chart.artifact_name, + ) + for template in chart.get_templates(): + # Loop through each line in the template. + for index in range(len(template.data)): + count = 0 + # If the line contains 'imagePullSecrets:' we check if there is a + # value path matching the regex. If there is, we add it to the + # matches set and break the loop. If there is not, we check the + # next line. We do this until we find a line that contains a match. + # NFM provides the image pull secrets parameter as a list. If we find + # a line that contains 'name:' we know that the image pull secrets + # parameter value path is for a string and not a list, and + # so we can break from the loop. + while ("imagePullSecrets:" in template.data[index]) and ( + "name:" not in template.data[index + count] + ): + new_matches = re.findall( + VALUE_PATH_REGEX, template.data[index + count] + ) + if len(new_matches) != 0: + logger.debug( + "Found image pull secrets values path %s in Helm chart %s", + new_matches, + chart.artifact_name, + ) + # Add the new matches to the matches set + matches.update(new_matches) + break + + count += 1 + + # Recursively search the dependency charts for image pull secrets parameters + for dep in chart.get_dependencies(): + self._find_image_pull_secrets_values_paths(dep, matches) + + def _find_registry_values_paths(self) -> Set[str]: + """ + Find registry values paths in the Helm chart templates. + + :return: A set of registry values paths found in the Helm chart templates. + :rtype: Set[str] + """ + logger.debug("Finding registry values paths in Helm chart %s", self.name) + matches: Set[str] = set() + + image_lines: Set[str] = set() + self._find_image_lines(self.input_artifact, image_lines) + + for line in image_lines: + # Images are expected to be specified in the format /: + # so we split the line on '/' and then find the value path + # in the first element of the resulting list, which corresponds to the + # part of the line. + new_matches = re.findall(VALUE_PATH_REGEX, line.split("/")[0]) + if len(new_matches) != 0: + logger.debug( + "Found registry values path %s in Helm chart %s", + new_matches, + self.name, + ) + # Add the new matches to the matches set + matches.update(new_matches) + + return matches + + def _generate_mapping_rule_profile( + self, values_to_remove: List[str] + ) -> AzureArcKubernetesDeployMappingRuleProfile: + """ + Generate the mappings for a Helm chart. + + :param values_to_remove: The values to remove from the generated values mappings. + :type values_to_remove: Set[str] + :return: The generated mapping rule profile. + :rtype: AzureArcKubernetesDeployMappingRuleProfile + """ + # Generate the values mappings for the Helm chart. + values_mappings = self.generate_values_mappings( + self.input_artifact.get_schema(), + self.input_artifact.get_defaults(), + ) + + # Remove the values from the values mappings. + # We want to remove the image registry and image pull secrets values paths from the values mappings + # as these values are supplied by NFM when it installs the chart. + for value_to_remove in values_to_remove: + self._remove_key_from_dict(values_mappings, value_to_remove) + + mapping_rule_profile = HelmMappingRuleProfile( + release_name=self.name, + release_namespace=self.name, + helm_package_version=self.input_artifact.artifact_version, + values=json.dumps(values_mappings), + ) + + return AzureArcKubernetesDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + helm_mapping_rule_profile=mapping_rule_profile, + ) + + def _remove_key_from_dict(self, dictionary: Dict[str, Any], path: str) -> None: + """ + Remove a key from a nested dictionary based on the given path. + + The path is in dot notation, e.g. "a.b.c" will remove the key "c" from the dictionary + + :param dictionary: The dictionary to remove the key from. + :type dictionary: Dict[str, Any] + :param path: The path to the key to remove. + :type path: str + """ + # Split the path by the dot character + keys = path.split(".") + # Check if the path is valid + if len(keys) == 0 or keys[0] not in dictionary: + return None # Invalid path + # If the path has only one key, remove it from the dictionary + if len(keys) == 1: + del dictionary[keys[0]] + return None # Key removed + # Otherwise, recursively call the function on the sub-dictionary + return self._remove_key_from_dict(dictionary[keys[0]], ".".join(keys[1:])) diff --git a/src/aosm/azext_aosm/build_processors/nexus_image_processor.py b/src/aosm/azext_aosm/build_processors/nexus_image_processor.py new file mode 100644 index 00000000000..7b0fa082fd1 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/nexus_image_processor.py @@ -0,0 +1,190 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from pathlib import Path +from typing import List, Tuple + +from knack.log import get_logger + +from azext_aosm.build_processors.base_processor import BaseInputProcessor +from azext_aosm.common.artifact import BaseArtifact, RemoteACRArtifact +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.nexus_image_input import NexusImageFileInput +from azext_aosm.vendored_sdks.models import ( + AzureOperatorNexusNetworkFunctionImageApplication, + AzureOperatorNexusImageArtifactProfile, + AzureOperatorNexusImageDeployMappingRuleProfile, + ImageArtifactProfile, + ImageMappingRuleProfile, + ApplicationEnablement, + ArtifactType, + DependsOnProfile, + ManifestArtifactFormat, + ReferencedResource, + ResourceElementTemplate, +) +from azext_aosm.common.registry import AzureContainerRegistry +from azext_aosm.common.constants import ( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + NEXUS_IMAGE_PARAMETERS_FILENAME, +) + +logger = get_logger(__name__) + + +class NexusImageProcessor(BaseInputProcessor): + """ + A class for processing Nexus image inputs. + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + input_artifact: NexusImageFileInput + + def __init__(self, name: str, input_artifact: NexusImageFileInput, expose_all_params: bool): + super().__init__(name, input_artifact, expose_all_params) + + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + logger.info("Getting artifact manifest list for Nexus image input.") + return [ + ManifestArtifactFormat( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.IMAGE_FILE.value, + artifact_version=self.input_artifact.artifact_version, + ) + ] + + def get_artifact_details( + self, + ) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseArtifact], List[LocalFileBuilder]] + """ + logger.info("Getting artifact details for Nexus image input.") + artifacts: List[BaseArtifact] = [] + file_builders: List[LocalFileBuilder] = [] + + # We only support remote ACR artifacts for nexus container images + source_registry = AzureContainerRegistry( + self.input_artifact.source_acr_registry + ) + + artifacts.append( + RemoteACRArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.IMAGE_FILE.value, + artifact_version=self.input_artifact.artifact_version, + source_registry=source_registry, + registry_namespace="", + ) + ) + return artifacts, file_builders + + def generate_nf_application( + self, + ) -> AzureOperatorNexusNetworkFunctionImageApplication: + """ + Generate the NF application. + + :return: The NF application. + :rtype: AzureOperatorNexusNetworkFunctionImageApplication + """ + logger.info("Generating NF application for Nexus image input.") + + return AzureOperatorNexusNetworkFunctionImageApplication( + name=self.name, + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + artifact_profile=self._generate_artifact_profile(), + deploy_parameters_mapping_rule_profile=self._generate_mapping_rule_profile(), + ) + + def generate_resource_element_template(self) -> ResourceElementTemplate: + """ + Generate the resource element template. + + :raises NotImplementedError: NSDs do not support deployment of Nexus images. + """ + raise NotImplementedError( + "NSDs do not support deployment of Nexus images directly, " + "they must be provided in the NF." + ) + + def _generate_artifact_profile(self) -> AzureOperatorNexusImageArtifactProfile: + """ + Generate the artifact profile. + + :return: The artifact profile. + :rtype: AzureOperatorNexusImageArtifactProfile + """ + logger.debug("Generating artifact profile for Nexus image input.") + artifact_profile = ImageArtifactProfile( + image_name=self.input_artifact.artifact_name, + image_version=self.input_artifact.artifact_version, + ) + + return AzureOperatorNexusImageArtifactProfile( + artifact_store=ReferencedResource(id=""), + image_artifact_profile=artifact_profile, + ) + + def _generate_mapping_rule_profile( + self, + ) -> AzureOperatorNexusImageDeployMappingRuleProfile: + """ + Generate the mapping rule profile. + + :return: The mapping rule profile. + :rtype: AzureOperatorNexusImageDeployMappingRuleProfile + """ + logger.debug("Generating mapping rule profile for Nexus image input.") + user_configuration = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults() + ) + + mapping = ImageMappingRuleProfile( + user_configuration=json.dumps(user_configuration), + ) + + return AzureOperatorNexusImageDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + image_mapping_rule_profile=mapping, + ) + + def generate_parameters_file(self) -> LocalFileBuilder: + """Generate parameters file.""" + mapping_rule_profile = self._generate_mapping_rule_profile() + if ( + mapping_rule_profile.image_mapping_rule_profile + and mapping_rule_profile.image_mapping_rule_profile.user_configuration + ): + params = mapping_rule_profile.image_mapping_rule_profile.user_configuration + logger.info("Created parameters file for Nexus image.") + # We still want to create an empty params file, + # otherwise the nf definition bicep will refer to files that don't exist + else: + params = "{}" + return LocalFileBuilder( + Path( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + self.input_artifact.artifact_name + + "-" + + NEXUS_IMAGE_PARAMETERS_FILENAME, + ), + json.dumps(json.loads(params), indent=4), + ) diff --git a/src/aosm/azext_aosm/build_processors/nfd_processor.py b/src/aosm/azext_aosm/build_processors/nfd_processor.py new file mode 100644 index 00000000000..55bbb708716 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/nfd_processor.py @@ -0,0 +1,341 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +from knack.log import get_logger +from azure.cli.core.azclierror import ResourceNotFoundError +from azext_aosm.build_processors.base_processor import BaseInputProcessor +from azext_aosm.common.artifact import BaseArtifact, LocalFileACRArtifact +from azext_aosm.common.constants import CGS_NAME +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.nfd_input import NFDInput +from azext_aosm.vendored_sdks.models import ( + ArmResourceDefinitionResourceElementTemplate, + ArtifactType, + DependsOnProfile, + ManifestArtifactFormat, + NetworkFunctionApplication, + NetworkFunctionDefinitionResourceElementTemplateDetails as NFDResourceElementTemplate, + NSDArtifactProfile, + ReferencedResource, + TemplateType, + ContainerizedNetworkFunctionDefinitionVersion, + VirtualNetworkFunctionDefinitionVersion, +) +from azext_aosm.common.constants import ( + NSD_OUTPUT_FOLDER_FILENAME, + NSD_NF_TEMPLATE_FILENAME, + NSD_TEMPLATE_FOLDER_NAME, + VNF_TYPE, CNF_TYPE +) +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path + +logger = get_logger(__name__) + + +class NFDProcessor(BaseInputProcessor): + """ + A class for processing NFD inputs. + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + + input_artifact: NFDInput + + def __init__(self, name: str, input_artifact: NFDInput): + super().__init__(name, input_artifact, expose_all_params=False) + + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + logger.debug("Getting artifact manifest list for NFD input %s.", self.name) + return [ + ManifestArtifactFormat( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.OCI_ARTIFACT.value, + artifact_version=self.input_artifact.artifact_version, + ) + ] + + def get_artifact_details( + self, + ) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseArtifact], List[LocalFileBuilder]] + """ + logger.info("Getting artifact details for NFD input.") + # The ARM template is written to a local file to be used as the artifact + # Path is relative to NSD_OUTPUT_FOLDER_FILENAME as this artifact is stored in the NSD output folder + artifact_details = LocalFileACRArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.ARM_TEMPLATE.value, + artifact_version=self.input_artifact.artifact_version, + file_path=self.input_artifact.arm_template_output_path.relative_to( + Path(NSD_OUTPUT_FOLDER_FILENAME) + ), + ) + + template_path = get_template_path( + NSD_TEMPLATE_FOLDER_NAME, NSD_NF_TEMPLATE_FILENAME + ) + + # This horrendous if statement is required because: + # - the 'properties' and 'network_function_template' attributes are optional + # - the isinstance check is because the base + # NetworkFunctionDefinitionVersionPropertiesFormat class + # doesn't define the network_function_template attribute, even though both subclasses do. + # Not switching to EAFP style because mypy doesn't + # account for `except AttributeError` (for good reason). + # Similar test required in the NFD input, but we can't deduplicate the code because mypy + # doesn't propagate type narrowing from isinstance(). + if ( + self.input_artifact.network_function_definition.properties + and isinstance( + self.input_artifact.network_function_definition.properties, + ( + ContainerizedNetworkFunctionDefinitionVersion, + VirtualNetworkFunctionDefinitionVersion, + ), + ) + and self.input_artifact.network_function_definition.properties.network_function_template + ): + # Split for line-too-long linting errors + nf_type = self.input_artifact.network_function_definition.properties.network_function_type + nf_templates = self.input_artifact.network_function_definition.properties.network_function_template + + if nf_type == CNF_TYPE: + nf_application_names = [nf_app.name for nf_app in nf_templates.network_function_applications] + params = { + "nfvi_type": + nf_templates.nfvi_type, + "is_cnf": True, + "nf_application_names": nf_application_names + } + elif nf_type == VNF_TYPE: + params = { + "nfvi_type": + nf_templates.nfvi_type, + "is_cnf": False + } + else: + raise ResourceNotFoundError(f"The NFDV provided has invalid network function type: {nf_type}") + else: + raise ResourceNotFoundError("The NFDV provided has no nfvi type.") + bicep_contents = render_bicep_contents_from_j2(template_path, params) + # Create a local file builder for the ARM template + file_builder = LocalFileBuilder( + self.input_artifact.arm_template_output_path, + bicep_contents, + ) + + return [artifact_details], [file_builder] + + def generate_nf_application(self) -> NetworkFunctionApplication: + """ + Generate the network function application from the input. + + :raises NotImplementedError: NFDs cannot be used to generate new NF application templates. + """ + raise NotImplementedError( + "NFDs cannot be used to generate new NF application templates." + ) + + def generate_resource_element_template(self) -> NFDResourceElementTemplate: + """ + Generate the resource element template from the input. + + :return: The resource element template. + :rtype: NFDResourceElementTemplate + """ + logger.info("Generating resource element template for NFD input.") + parameter_values_dict = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults(), True + ) + + artifact_profile = NSDArtifactProfile( + artifact_store_reference=ReferencedResource(id=""), + artifact_name=self.input_artifact.artifact_name, + artifact_version=self.input_artifact.artifact_version, + ) + + configuration = ArmResourceDefinitionResourceElementTemplate( + template_type=TemplateType.ARM_TEMPLATE.value, + artifact_profile=artifact_profile, + parameter_values=json.dumps(parameter_values_dict), + ) + + return NFDResourceElementTemplate( + name=self.name, + configuration=configuration, + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + ) + + def _generate_schema( + self, + cg_schema: Dict[str, Any], + source_schema: Dict[str, Any], + default_values: Dict[str, Any], + param_prefix: Optional[str] = None, + ) -> None: + """ + Generate the config group schema. + + This method recursively generates the config group schema for the input artifact by updating + the cg_schema parameter. + + Parameters: + cg_schema: + The schema to be modified. + On first call of this method, it should contain any base nodes for the schema. + This schema is passed by reference and modified in place. + This property is defined by the CLI in the base processor. + source_schema: + The source schema from which the config group schema is generated. + E.g., for an NFD this will be the deployParameters schema + and is created by the NFDInput class using a previously deployed NFDV properties. + For an ARM template this will be the schema generated from the templates parameters + default_values: + The default values used to determine whether a parameter + should be hardcoded or provided by the user. + These are generated by the CLI based on the NDFV properties. + param_prefix: + The prefix to be added to the parameter name. + This is used for namespacing nested properties in the schema. + On first call to this method this should be None. + """ + if "properties" not in source_schema.keys(): + return + + # configObject is the root object and not needed for namespacing. It just adds confusion, so remove it. + # There's an edge case where it's not the root object (by coincidence someone else is also using + # "configObject"). This could be solved by removing the root configObject before starting this recursive + # algorithm. For now, not handling this edge case is acceptable. + if param_prefix == "configObject": + param_prefix = None + + # Abbreviated 'prop' so as not to override built in 'property'. + for prop, details in source_schema["properties"].items(): + if prop == "deployParameters": + # These arise due to pulling deployParameters from the NFD schema, but we don't want them in the + # middle of the config group schema. + del details["items"]["$schema"] + del details["items"]["title"] + + param_name = prop if not param_prefix else f"{param_prefix}_{prop}" + + # Note: We don't recurse into deployParams because it's an array. For arrays we just dump all the `details` + # as is (in this case the NFDV deployParams). + # This means if NFDV is expose all, NSDV is expose all. That's the + # behaviour we want for now, but this will need to be changed if we want anything more sophisticated. + + # We have three types of parameter to handle in the following if statements: + # 1. Parameters that are always hardcoded + if prop in [ + "location", + "publisherName", + "nfdgName", + "publisherResourceGroup", + ]: + continue + # 2. Required parameters (in 'required' array and no default given). + if ( + "required" in source_schema + and prop in source_schema["required"] + and prop not in default_values + ): + if "properties" in details: + self._generate_schema(cg_schema, details, {}, param_name) + else: + cg_schema["required"].append(param_name) + cg_schema["properties"][param_name] = details + # 3. Optional parameters that have child properties. These aren't added to the CG schema here, but + # we check their children + # Note, given we only have a single depth of params for CGS, this is probably unnecessary, but leaving + # in case we want to add more nesting in the future (e.g. more sophisticated array handling). + elif prop in default_values and "properties" in details: + self._generate_schema( + cg_schema, details, default_values[prop], param_name + ) + + def generate_values_mappings( + self, + schema: Dict[str, Any], + mapping: Dict[str, Any], + is_ret: bool = False, + param_prefix: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Override the BaseInputProcessor's method, see there for full docstring. + + - Some parameters are always hardcoded, and therefore always in the initial mapping, so we skip these. + - There's no expose_all_params functionality as for NSD's we simply map `deployParameters` on to the CGV + `deployParameters`. + """ + + # configObject is the root object and not needed for namespacing. It just adds confusion, so remove it. + if param_prefix == "configObject": + param_prefix = None + + for prop, prop_schema in schema["properties"].items(): + + param_name = prop if param_prefix is None else f"{param_prefix}_{prop}" + + # We have three types of parameter to handle in the following if statements (analagous to + # generate_schema()): + # 1. Parameters that are always hardcoded + if prop in [ + "location", + "publisherName", + "nfdgName", + "publisherResourceGroup", + ]: + continue + # 2. Required parameters (in 'required' array and no default given). + if ( + "required" in schema + and prop in schema["required"] + and prop not in mapping + ): + if "properties" in prop_schema: + mapping[prop] = self.generate_values_mappings( + prop_schema, {}, is_ret, param_name + ) + else: + mapping[prop] = ( + f"{{configurationparameters('{CGS_NAME}').{self.name}.{param_name}}}" + if is_ret + else f"{{deployParameters.{self.name}.{param_name}}}" + ) + # 3. Optional parameters (i.e. they have a default in the mapping dict) that have child properties. + # These aren't added to the mapping here, but we check their children. + # Note, given we only have a single depth of params for CGS, this is probably unnecessary, but leaving + # in case we want to add more nesting in the future (e.g. more sophisticated array handling). + elif prop in mapping and "properties" in prop_schema: + # Python evaluates {} as False, so we need to explicitly set to {} + default_subschema_mapping = mapping[prop] or {} + mapping[prop] = self.generate_values_mappings( + prop_schema, default_subschema_mapping, is_ret, param_name + ) + + logger.debug( + "Output of generate_values_mappings for %s:\n%s", + self.name, + json.dumps(mapping, indent=4), + ) + + return mapping diff --git a/src/aosm/azext_aosm/build_processors/vhd_processor.py b/src/aosm/azext_aosm/build_processors/vhd_processor.py new file mode 100644 index 00000000000..9f7d6429183 --- /dev/null +++ b/src/aosm/azext_aosm/build_processors/vhd_processor.py @@ -0,0 +1,203 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from pathlib import Path +import json +from typing import List, Tuple + +from knack.log import get_logger + +from azext_aosm.build_processors.base_processor import BaseInputProcessor +from azext_aosm.common.artifact import ( + BaseArtifact, + BlobStorageAccountArtifact, + LocalFileStorageAccountArtifact +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.vhd_file_input import VHDFileInput +from azext_aosm.vendored_sdks.models import ( + ApplicationEnablement, + ArtifactType, + AzureCoreNetworkFunctionVhdApplication, + AzureCoreVhdImageArtifactProfile, + AzureCoreVhdImageDeployMappingRuleProfile, + DependsOnProfile, + ManifestArtifactFormat, + ReferencedResource, + ResourceElementTemplate, + VhdImageArtifactProfile, + VhdImageMappingRuleProfile, +) +from azext_aosm.common.constants import ( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + VHD_PARAMETERS_FILENAME) + +logger = get_logger(__name__) + + +class VHDProcessor(BaseInputProcessor): + """ + A class for processing VHD inputs. + + :param name: The name of the artifact. + :param input_artifact: The input artifact. + """ + input_artifact: VHDFileInput + + def __init__(self, name: str, input_artifact: VHDFileInput, expose_all_params: bool): + super().__init__(name, input_artifact, expose_all_params) + + def get_artifact_manifest_list(self) -> List[ManifestArtifactFormat]: + """ + Get the list of artifacts for the artifact manifest. + + :return: A list of artifacts for the artifact manifest. + :rtype: List[ManifestArtifactFormat] + """ + logger.debug("Getting artifact manifest list for VHD input %s.", self.name) + return [ + ManifestArtifactFormat( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.VHD_IMAGE_FILE.value, + artifact_version=self.input_artifact.artifact_version, + ) + ] + + def get_artifact_details( + self, + ) -> Tuple[List[BaseArtifact], List[LocalFileBuilder]]: + """ + Get the artifact details for publishing. + + :return: A tuple containing the list of artifacts and the list of local file builders. + :rtype: Tuple[List[BaseArtifact], List[LocalFileBuilder]] + """ + logger.info("Getting artifact details for VHD input.") + artifacts: List[BaseArtifact] = [] + file_builders: List[LocalFileBuilder] = [] + + if self.input_artifact.file_path: + logger.debug( + "VHD input has a file path. Adding LocalFileStorageAccountArtifact." + ) + artifacts.append( + LocalFileStorageAccountArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.VHD_IMAGE_FILE.value, + artifact_version=self.input_artifact.artifact_version, + file_path=self.input_artifact.file_path, + ) + ) + elif self.input_artifact.blob_sas_uri: + logger.debug( + "VHD input has a blob SAS URI. Adding BlobStorageAccountArtifact." + ) + artifacts.append( + BlobStorageAccountArtifact( + artifact_name=self.input_artifact.artifact_name, + artifact_type=ArtifactType.VHD_IMAGE_FILE.value, + artifact_version=self.input_artifact.artifact_version, + blob_sas_uri=self.input_artifact.blob_sas_uri, + ) + ) + else: + logger.error("VHDFileInput must have either a file path or a blob SAS URI.") + raise ValueError( + "VHDFileInput must have either a file path or a blob SAS URI." + ) + + return artifacts, file_builders + + def generate_nf_application(self) -> AzureCoreNetworkFunctionVhdApplication: + """ + Generate the NF application. + + :return: The NF application. + :rtype: AzureCoreNetworkFunctionVhdApplication + """ + logger.info("Generating NF application for VHD input.") + + return AzureCoreNetworkFunctionVhdApplication( + name=self.name, + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + artifact_profile=self._generate_artifact_profile(), + deploy_parameters_mapping_rule_profile=self._generate_mapping_rule_profile(), + ) + + def generate_resource_element_template(self) -> ResourceElementTemplate: + """ + Generate the resource element template. + + :raises NotImplementedError: NSDs do not support deployment of VHDs. + """ + raise NotImplementedError("NSDs do not support deployment of VHDs directly, " + "they must be provided in the NF.") + + def _generate_artifact_profile(self) -> AzureCoreVhdImageArtifactProfile: + """ + Generate the artifact profile. + + :return: The artifact profile. + :rtype: AzureCoreVhdImageArtifactProfile + """ + logger.debug("Generating artifact profile for VHD input.") + artifact_profile = VhdImageArtifactProfile( + vhd_name=self.input_artifact.artifact_name, + vhd_version=self.input_artifact.artifact_version, + ) + + return AzureCoreVhdImageArtifactProfile( + artifact_store=ReferencedResource(id=""), + vhd_artifact_profile=artifact_profile, + ) + + def _generate_mapping_rule_profile( + self, + ) -> AzureCoreVhdImageDeployMappingRuleProfile: + """ + Generate the mapping rule profile. + + :return: The mapping rule profile. + :rtype: AzureCoreVhdImageDeployMappingRuleProfile + """ + logger.debug("Generating mapping rule profile for VHD input.") + user_configuration = self.generate_values_mappings( + self.input_artifact.get_schema(), self.input_artifact.get_defaults() + ) + + mapping = VhdImageMappingRuleProfile( + user_configuration=json.dumps(user_configuration), + ) + + return AzureCoreVhdImageDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + vhd_image_mapping_rule_profile=mapping, + ) + + def generate_parameters_file(self) -> LocalFileBuilder: + """ Generate parameters file. """ + mapping_rule_profile = self._generate_mapping_rule_profile() + if (mapping_rule_profile.vhd_image_mapping_rule_profile + and mapping_rule_profile.vhd_image_mapping_rule_profile.user_configuration): + params = ( + mapping_rule_profile.vhd_image_mapping_rule_profile.user_configuration + ) + # We still want to create an empty params file, + # otherwise the nf definition bicep will refer to files that don't exist + else: + params = '{}' + logger.info( + "Created parameters file for VHD image." + ) + return LocalFileBuilder( + Path( + VNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + VHD_PARAMETERS_FILENAME, + ), + json.dumps(json.loads(params), indent=4), + ) diff --git a/src/aosm/azext_aosm/deploy/__init__.py b/src/aosm/azext_aosm/cli_handlers/__init__.py similarity index 100% rename from src/aosm/azext_aosm/deploy/__init__.py rename to src/aosm/azext_aosm/cli_handlers/__init__.py diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_base_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_base_handler.py new file mode 100644 index 00000000000..b432256aa5e --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_base_handler.py @@ -0,0 +1,347 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from dataclasses import fields, is_dataclass +from pathlib import Path +from typing import Optional +from json.decoder import JSONDecodeError +from azure.cli.core.azclierror import InvalidArgumentValueError, UnclassifiedUserFault +from jinja2 import StrictUndefined, Template +from knack.log import get_logger + +from azext_aosm.common.command_context import CommandContext +from azext_aosm.common.constants import DEPLOY_PARAMETERS_FILENAME +from azext_aosm.configuration_models.common_parameters_config import ( + BaseCommonParametersConfig, +) +from azext_aosm.configuration_models.onboarding_base_input_config import ( + OnboardingBaseInputConfig, +) +from azext_aosm.definition_folder.builder.definition_folder_builder import ( + DefinitionFolderBuilder, +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.definition_folder.reader.definition_folder import DefinitionFolder +from azext_aosm.vendored_sdks import HybridNetworkManagementClient + +logger = get_logger(__name__) + + +class OnboardingBaseCLIHandler(ABC): + """Abstract base class for CLI handlers.""" + + config: OnboardingBaseInputConfig | BaseCommonParametersConfig + + def __init__( + self, + config_file_path: Optional[Path] = None, + all_deploy_params_file_path: Optional[Path] = None, + aosm_client: Optional[HybridNetworkManagementClient] = None, + skip: Optional[str] = None, + ): + """Initialize the CLI handler.""" + self.aosm_client = aosm_client + self.skip = skip + # If input.jsonc file provided (therefore if build command run) + if config_file_path: + config_dict = self._read_input_config_from_file(config_file_path) + try: + self.config = self._get_input_config(config_dict) + except TypeError as e: + raise InvalidArgumentValueError( + "The input file provided contains an incorrect input.\n" + f"Please fix the problem parameter:\n{e}") from e + # Validate config before getting processor list, + # in case error with input artifacts i.e helm package + self.config.validate() + self.processors = self._get_processor_list() + # If all_deploy.parameters.json file provided (therefore if publish/delete command run) + elif all_deploy_params_file_path: + try: + self.config = self._get_params_config(all_deploy_params_file_path) + except TypeError as e: + raise InvalidArgumentValueError( + "The all_deploy.parameters.json in the folder " + "provided contains an incorrect input.\nPlease check if you have provided " + f"the correct folder for the definition/design type:\n{e}") from e + # If no config file provided (for generate-config) + else: + self.config = self._get_input_config() + self.definition_folder_builder = DefinitionFolderBuilder( + Path.cwd() / self.output_folder_file_name + ) + + @property + @abstractmethod + def default_config_file_name(self) -> str: + """Get the default configuration file name.""" + raise NotImplementedError + + @property + @abstractmethod + def output_folder_file_name(self) -> str: + """Get the output folder file name.""" + raise NotImplementedError + + def generate_config(self, output_file: str | None = None): + """Generate the configuration file for the command.""" + if not output_file: + output_file = self.default_config_file_name + + # Make Path object and ensure it has .jsonc extension + output_path = Path(output_file).with_suffix(".jsonc") + + self._check_for_overwrite(output_path) + self._write_config_to_file(output_path) + + def build(self): + """Build the definition.""" + self.pre_validate_build() + self.definition_folder_builder.add_element(self.build_base_bicep()) + self.definition_folder_builder.add_element(self.build_manifest_bicep()) + self.definition_folder_builder.add_element(self.build_artifact_list()) + self.definition_folder_builder.add_element(self.build_resource_bicep()) + self.definition_folder_builder.add_element(self.build_all_parameters_json()) + self.definition_folder_builder.write() + + def publish(self, command_context: CommandContext): + """Publish the definition contained in the specified definition folder.""" + definition_folder = DefinitionFolder( + command_context.cli_options["definition_folder"] + ) + assert isinstance(self.config, BaseCommonParametersConfig) + definition_folder.deploy(config=self.config, command_context=command_context) + + def delete(self, command_context: CommandContext): + """Delete the definition.""" + # Takes folder, deletes to Azure + # - Read folder/ create folder object + # - For each element (reversed): + # - Do element.delete() + # TODO: Implement + + def pre_validate_build(self): + """ + Perform all validations that need to be done before running the build command. + + This method must be overwritten by subclasses to be of use, but is not abstract as it's + allowed to not perform any pre-validation, in which case this base method just does nothing. + """ + + @abstractmethod + def build_base_bicep(self): + """Build bicep file for underlying resources.""" + raise NotImplementedError + + @abstractmethod + def build_manifest_bicep(self): + """Build the manifest bicep file.""" + raise NotImplementedError + + @abstractmethod + def build_artifact_list(self): + """Build the artifact list.""" + raise NotImplementedError + + @abstractmethod + def build_resource_bicep(self): + """Build the resource bicep file.""" + raise NotImplementedError + + @abstractmethod + def build_all_parameters_json(self): + """Build parameters file to be used to create parameters.json for each bicep template.""" + raise NotImplementedError + + @abstractmethod + def _get_processor_list(self): + """Get list of processors for use in build.""" + raise NotImplementedError + + @abstractmethod + def _get_input_config( + self, input_config: Optional[dict] = None + ) -> OnboardingBaseInputConfig: + """Get the configuration for the command.""" + raise NotImplementedError + + @abstractmethod + def _get_params_config(self, config_file: Path) -> BaseCommonParametersConfig: + """Get the parameters config for publish/delete.""" + raise NotImplementedError + + @staticmethod + def _read_input_config_from_file(input_json_path: Path) -> dict: + """Reads the input JSONC file, removes comments. + + Returns config as dictionary. + """ + try: + lines = input_json_path.read_text().splitlines() + lines = [line for line in lines if not line.strip().startswith("//")] + config_dict = json.loads("".join(lines)) + return config_dict + except FileNotFoundError as e: + raise UnclassifiedUserFault(f"Invalid config file provided.\nError: {e} ") from e + except JSONDecodeError as e: + raise UnclassifiedUserFault("Invalid JSON found in the config file provided.\n" + f"Error: {e} ") from e + + @staticmethod + def _render_base_bicep_contents(template_path): + """Write the base bicep file from given template.""" + with open(template_path, "r", encoding="UTF-8") as f: + template: Template = Template( + f.read(), + undefined=StrictUndefined, + ) + + bicep_contents: str = template.render() + return bicep_contents + + def _serialize(self, dataclass, indent_count=1): + """ + Convert a dataclass instance to a JSONC string. + + This function recursively iterates over the fields of the dataclass and serializes them. + + We expect the dataclass to contain values of type string, list or another dataclass. + Lists may only contain dataclasses. + For example, + { + "param1": "value1", + "param2": [ + { ... }, + { ... } + ], + "param3": { ... } + } + """ + indent = " " * indent_count + double_indent = indent * 2 + jsonc_string = [] + + for field_info in fields(dataclass): + # Get the value of the current field. + field_value = getattr(dataclass, field_info.name) + # Get comment, if it exists + add it to the result + comment = field_info.metadata.get("comment", "") + if comment: + for line in comment.split("\n"): + jsonc_string.append(f"{indent}// {line}") + + if is_dataclass(field_value): + # Serialize the nested dataclass and add it as a nested JSON object. + # Checks if it is last field to omit trailing comma + if field_info == fields(dataclass)[-1]: + nested_json = ( + "{\n" + + self._serialize(field_value, indent_count + 1) + + "\n" + + indent + + "}" + ) + else: + nested_json = ( + "{\n" + + self._serialize(field_value, indent_count + 1) + + "\n" + + indent + + "}," + ) + jsonc_string.append(f'{indent}"{field_info.name}": {nested_json}') + elif isinstance(field_value, list): + # If the value is a list, iterate over the items. + jsonc_string.append(f'{indent}"{field_info.name}": [') + for item in field_value: + # Check if the item is a dataclass and serialize it. + if is_dataclass(item): + inner_dataclass = self._serialize(item, indent_count + 2) + if item == field_value[-1]: + jsonc_string.append( + double_indent + + "{\n" + + inner_dataclass + + "\n" + + double_indent + + "}" + ) + else: + jsonc_string.append( + double_indent + + "{\n" + + inner_dataclass + + "\n" + + double_indent + + "}," + ) + else: + jsonc_string.append(json.dumps(item, indent=4) + ",") + # If the field is the last field, omit the trailing comma. + if field_info == fields(dataclass)[-1]: + jsonc_string.append(indent + "]") + else: + jsonc_string.append(indent + "],") + else: + # If the value is a string, serialize it directly. + if field_info == fields(dataclass)[-1]: + jsonc_string.append( + f'{indent}"{field_info.name}": {json.dumps(field_value,indent=4)}' + ) + else: + jsonc_string.append( + f'{indent}"{field_info.name}": {json.dumps(field_value,indent=4)},' + ) + return "\n".join(jsonc_string) + + def _write_config_to_file(self, output_path: Path): + """Write the configuration to a file.""" + # Serialize the top-level dataclass instance and wrap it in curly braces to form a valid JSONC string. + jsonc_str = "{\n" + self._serialize(self.config) + "\n}" + output_path.write_text(jsonc_str) + + print(f"Empty configuration has been written to {output_path.name}") + logger.info("Empty configuration has been written to %s", output_path.name) + + @staticmethod + def _check_for_overwrite(output_path: Path): + """Check that the input file exists.""" + if output_path.exists(): + carry_on = input( + f"The file {output_path.name} already exists in this location - do you want to overwrite it?" + " (y/n)" + ) + if carry_on != "y": + raise UnclassifiedUserFault("User aborted!") + + def _render_deploy_params_schema( + self, complete_params_schema, output_folder_name, definition_folder_name + ): + """Render the schema for deployParameters.json.""" + return LocalFileBuilder( + Path( + output_folder_name, + definition_folder_name, + DEPLOY_PARAMETERS_FILENAME, + ), + json.dumps( + self._build_deploy_params_schema(complete_params_schema), indent=4 + ), + ) + + @staticmethod + def _build_deploy_params_schema(schema_properties): + """Build the schema for deployParameters.json.""" + schema_contents = { + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", + "type": "object", + "properties": {}, + } + schema_contents["properties"] = schema_properties + return schema_contents diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_cnf_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_cnf_handler.py new file mode 100644 index 00000000000..f778b1c52d6 --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_cnf_handler.py @@ -0,0 +1,317 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +import json +import warnings +from pathlib import Path +from typing import Optional + +import ruamel.yaml +from azure.cli.core.azclierror import ValidationError +from jinja2 import Template +from knack.log import get_logger +from ruamel.yaml.error import ReusedAnchorWarning + +from azext_aosm.build_processors.helm_chart_processor import HelmChartProcessor +from azext_aosm.common.constants import ( + ARTIFACT_LIST_FILENAME, + BASE_FOLDER_NAME, + CNF_BASE_TEMPLATE_FILENAME, + CNF_DEFINITION_TEMPLATE_FILENAME, + CNF_HELM_VALIDATION_ERRORS_TEMPLATE_FILENAME, + CNF_INPUT_FILENAME, + CNF_MANIFEST_TEMPLATE_FILENAME, + CNF_OUTPUT_FOLDER_FILENAME, + CNF_TEMPLATE_FOLDER_NAME, + DEPLOY_PARAMETERS_FILENAME, + HELM_TEMPLATE, + MANIFEST_FOLDER_NAME, + NF_DEFINITION_FOLDER_NAME, +) +from azext_aosm.common.exceptions import TemplateValidationError +from azext_aosm.configuration_models.common_parameters_config import ( + CNFCommonParametersConfig, +) +from azext_aosm.configuration_models.onboarding_cnf_input_config import ( + OnboardingCNFInputConfig, +) +from azext_aosm.definition_folder.builder.artifact_builder import ( + ArtifactDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.json_builder import ( + JSONDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.helm_chart_input import HelmChartInput + +from .onboarding_nfd_base_handler import OnboardingNFDBaseCLIHandler +from azext_aosm.common.registry import ContainerRegistryHandler +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path + +logger = get_logger(__name__) +yaml_processor = ruamel.yaml.YAML(typ="safe", pure=True) +warnings.simplefilter("ignore", ReusedAnchorWarning) + + +class OnboardingCNFCLIHandler(OnboardingNFDBaseCLIHandler): + """CLI handler for publishing NFDs.""" + + config: OnboardingCNFInputConfig + processors: list[HelmChartProcessor] + + @property + def default_config_file_name(self) -> str: + """Get the default configuration file name.""" + return CNF_INPUT_FILENAME + + @property + def output_folder_file_name(self) -> str: + """Get the output folder file name.""" + return CNF_OUTPUT_FOLDER_FILENAME + + def _get_input_config( + self, input_config: Optional[dict] = None + ) -> OnboardingCNFInputConfig: + """Get the configuration for the command.""" + if input_config is None: + input_config = {} + return OnboardingCNFInputConfig(**input_config) + + def _get_params_config(self, config_file: Path) -> CNFCommonParametersConfig: + """Get the configuration for the command.""" + with open(config_file, "r", encoding="utf-8") as _file: + params_dict = json.load(_file) + if params_dict is None: + params_dict = {} + return CNFCommonParametersConfig(**params_dict) + + def _get_processor_list(self) -> list[HelmChartProcessor]: + processor_list = [] + assert isinstance(self.config, OnboardingCNFInputConfig) + + registry_handler = ContainerRegistryHandler(self.config.image_sources) + + # for each helm package, instantiate helm processor + for helm_package in self.config.helm_packages: + if helm_package.default_values: + if Path(helm_package.default_values).exists(): + user_override_default_config = yaml_processor.load( + open(helm_package.default_values) + ) + else: + raise FileNotFoundError( + f"ERROR: The default values file '{helm_package.default_values}' does not exist" + ) + else: + user_override_default_config = None + + helm_input = HelmChartInput.from_chart_path( + chart_path=Path(helm_package.path_to_chart).absolute(), + default_config=user_override_default_config, + default_config_path=helm_package.default_values, + ) + helm_processor = HelmChartProcessor( + name=helm_package.name, + input_artifact=helm_input, + registry_handler=registry_handler, + expose_all_params=self.config.expose_all_parameters, + ) + processor_list.append(helm_processor) + return processor_list + + def _validate_helm_template(self): + """Validate the helm packages.""" + validation_errors = {} + + for helm_processor in self.processors: + try: + helm_processor.input_artifact.validate_template() + except TemplateValidationError as error: + validation_errors[helm_processor.input_artifact.artifact_name] = str( + error + ) + + if validation_errors: + # Create an error file using a j2 template + error_output_template_path = get_template_path( + CNF_TEMPLATE_FOLDER_NAME, CNF_HELM_VALIDATION_ERRORS_TEMPLATE_FILENAME + ) + + with open( + error_output_template_path, + "r", + encoding="utf-8", + ) as file: + error_output_template = Template(file.read()) + + rendered_error_output_template = error_output_template.render( + errors=validation_errors + ) + + logger.info(rendered_error_output_template) + + error_message = ( + "Could not validate all the provided Helm charts. " + "Please run the CLI command again with the --verbose flag " + "to see the validation errors." + ) + + raise ValidationError(error_message) + + def _validate_helm_values(self): + for helm_processor in self.processors: + helm_processor.input_artifact.validate_values() + + def pre_validate_build(self): + """Run all validation functions required before building the cnf.""" + logger.debug("Pre-validating build") + + self._validate_helm_values() + + if self.skip != HELM_TEMPLATE: + self._validate_helm_template() + + def build_base_bicep(self) -> BicepDefinitionElementBuilder: + """Build the base bicep file.""" + # Build manifest bicep contents, with j2 template + template_path = get_template_path( + CNF_TEMPLATE_FOLDER_NAME, CNF_BASE_TEMPLATE_FILENAME + ) + bicep_contents = render_bicep_contents_from_j2(template_path, {}) + # Create Bicep element with base contents + bicep_file = BicepDefinitionElementBuilder( + Path(CNF_OUTPUT_FOLDER_FILENAME, BASE_FOLDER_NAME), bicep_contents + ) + return bicep_file + + def build_manifest_bicep(self) -> BicepDefinitionElementBuilder: + """Build the manifest bicep file.""" + artifact_list = [] + logger.info("Creating artifact manifest bicep") + for processor in self.processors: + artifacts = processor.get_artifact_manifest_list() + + # Add artifacts to a list of unique artifacts + if artifacts not in artifact_list: + artifact_list.extend(artifacts) + logger.debug( + "Created list of artifacts from %s helm package(s) provided: %s", + len(self.config.helm_packages), + artifact_list, + ) + # Build manifest bicep contents, with j2 template + template_path = get_template_path( + CNF_TEMPLATE_FOLDER_NAME, CNF_MANIFEST_TEMPLATE_FILENAME + ) + + params = {"acr_artifacts": artifact_list, "sa_artifacts": []} + bicep_contents = render_bicep_contents_from_j2(template_path, params) + + # Create Bicep element with manifest contents + bicep_file = BicepDefinitionElementBuilder( + Path(CNF_OUTPUT_FOLDER_FILENAME, MANIFEST_FOLDER_NAME), bicep_contents + ) + + return bicep_file + + def build_artifact_list(self) -> ArtifactDefinitionElementBuilder: + """Build the artifact list.""" + logger.info("Creating artifacts list for artifacts.json") + artifact_list = [] + # For each helm package, get list of artifacts and combine + # For each arm template, get list of artifacts and combine + for processor in self.processors: + (artifacts, _) = processor.get_artifact_details() + if artifacts not in artifact_list: + artifact_list.extend(artifacts) + logger.debug( + "Created list of artifact details from %s helm packages(s) and the vhd image provided: %s", + len(self.config.helm_packages), + artifact_list, + ) + # Generate Artifact Element with artifact list + return ArtifactDefinitionElementBuilder( + Path(CNF_OUTPUT_FOLDER_FILENAME, ARTIFACT_LIST_FILENAME), artifact_list + ) + + def build_resource_bicep(self) -> BicepDefinitionElementBuilder: + """Build the resource bicep file.""" + logger.info("Creating NF definition bicep template") + nf_application_list = [] + mappings_files = [] + deploy_params_schema = {} + # For each helm package, generate nf application, params schema and mappings profile + for processor in self.processors: + nf_application = processor.generate_nf_application() + nf_application_list.append(nf_application) + + # The AOSM API models are very permissive with None values. Our code should never set these to None, + # so we use these asserts to ensure that, and to keep type checking happy. + assert nf_application.name is not None + assert nf_application.deploy_parameters_mapping_rule_profile is not None + assert nf_application.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile is not None + assert nf_application.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.values is not None + + deploy_params_schema.update(processor.generate_schema()) + + # Add supporting file: config mappings + mapping_rules = ( + nf_application.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.values + ) + mapping_file = LocalFileBuilder( + Path( + CNF_OUTPUT_FOLDER_FILENAME, + NF_DEFINITION_FOLDER_NAME, + nf_application.name + "-mappings.json", + ), + json.dumps(json.loads(mapping_rules), indent=4), + ) + mappings_files.append(mapping_file) + + # Create bicep contents using cnf defintion j2 template + template_path = get_template_path( + CNF_TEMPLATE_FOLDER_NAME, CNF_DEFINITION_TEMPLATE_FILENAME + ) + params = { + "acr_nf_applications": nf_application_list, + "deploy_parameters_file": DEPLOY_PARAMETERS_FILENAME, + } + bicep_contents = render_bicep_contents_from_j2(template_path, params) + + # Create a bicep element + add its supporting mapping files + bicep_element_builder = BicepDefinitionElementBuilder( + Path(CNF_OUTPUT_FOLDER_FILENAME, NF_DEFINITION_FOLDER_NAME), bicep_contents + ) + for mappings_file in mappings_files: + bicep_element_builder.add_supporting_file(mappings_file) + + # Add the deployParameters schema file + bicep_element_builder.add_supporting_file( + self._render_deploy_params_schema( + deploy_params_schema, CNF_OUTPUT_FOLDER_FILENAME, NF_DEFINITION_FOLDER_NAME + ) + ) + return bicep_element_builder + + def build_all_parameters_json(self) -> JSONDefinitionElementBuilder: + """Build the all parameters json file.""" + params_content = { + "location": self.config.location, + "publisherName": self.config.publisher_name, + "publisherResourceGroupName": self.config.publisher_resource_group_name, + "acrArtifactStoreName": self.config.acr_artifact_store_name, + "acrManifestName": self.config.acr_manifest_name, + "nfDefinitionGroup": self.config.nf_name, + "nfDefinitionVersion": self.config.version, + } + + base_file = JSONDefinitionElementBuilder( + Path(CNF_OUTPUT_FOLDER_FILENAME), json.dumps(params_content, indent=4) + ) + return base_file diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_core_vnf_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_core_vnf_handler.py new file mode 100644 index 00000000000..15eb131481e --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_core_vnf_handler.py @@ -0,0 +1,203 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +import json +from pathlib import Path +from typing import Dict, Any, List, Tuple, Optional +from knack.log import get_logger + +from azext_aosm.build_processors.arm_processor import AzureCoreArmBuildProcessor +from azext_aosm.build_processors.vhd_processor import VHDProcessor +from azext_aosm.common.constants import ( + BASE_FOLDER_NAME, + VNF_CORE_BASE_TEMPLATE_FILENAME, + VNF_TEMPLATE_FOLDER_NAME, + VNF_OUTPUT_FOLDER_FILENAME, + DEPLOY_PARAMETERS_FILENAME, + VHD_PARAMETERS_FILENAME, + TEMPLATE_PARAMETERS_FILENAME +) +from azext_aosm.configuration_models.onboarding_vnf_input_config import ( + OnboardingCoreVNFInputConfig, +) +from azext_aosm.configuration_models.common_parameters_config import ( + CoreVNFCommonParametersConfig, +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.json_builder import ( + JSONDefinitionElementBuilder, +) +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.inputs.vhd_file_input import VHDFileInput +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path + +from .onboarding_vnf_handler import OnboardingVNFCLIHandler +logger = get_logger(__name__) + + +class OnboardingCoreVNFCLIHandler(OnboardingVNFCLIHandler): + """CLI handler for publishing NFDs.""" + + config: OnboardingCoreVNFInputConfig + + def _get_input_config( + self, input_config: Optional[dict] = None + ) -> OnboardingCoreVNFInputConfig: + """Get the configuration for the command.""" + if input_config is None: + input_config = {} + return OnboardingCoreVNFInputConfig(**input_config) + + def _get_params_config( + self, config_file: Path + ) -> CoreVNFCommonParametersConfig: + """Get the configuration for the command.""" + with open(config_file, "r", encoding="utf-8") as _file: + params_dict = json.load(_file) + if params_dict is None: + params_dict = {} + return CoreVNFCommonParametersConfig(**params_dict) + + def _get_processor_list(self) -> List[AzureCoreArmBuildProcessor | VHDProcessor]: + """Get the list of processors.""" + processor_list: List[AzureCoreArmBuildProcessor | VHDProcessor] = [] + # for each arm template, instantiate arm processor + for arm_template in self.config.arm_templates: + arm_input = ArmTemplateInput( + artifact_name=arm_template.artifact_name, + artifact_version=arm_template.version, + default_config={"imageName": self.config.nf_name + "Image"}, + template_path=Path(arm_template.file_path).absolute(), + ) + processor_list.append( + AzureCoreArmBuildProcessor( + arm_input.artifact_name, arm_input, + expose_all_params=self.config.expose_all_parameters) + ) + + # Instantiate vhd processor + if not self.config.vhd.artifact_name: + self.config.vhd.artifact_name = self.config.nf_name + "-vhd" + + if self.config.vhd.file_path: + file_path = Path(self.config.vhd.file_path).absolute() + else: + file_path = None + + vhd_processor = VHDProcessor( + name=self.config.vhd.artifact_name, + input_artifact=VHDFileInput( + artifact_name=self.config.vhd.artifact_name, + artifact_version=self.config.vhd.version, + default_config=self._get_default_config(self.config.vhd), + file_path=file_path, + blob_sas_uri=self.config.vhd.blob_sas_url, + ), + expose_all_params=self.config.expose_all_parameters + ) + processor_list.append(vhd_processor) + return processor_list + + def build_base_bicep(self) -> BicepDefinitionElementBuilder: + """Build the base bicep file.""" + # Build manifest bicep contents, with j2 template + template_path = get_template_path( + VNF_TEMPLATE_FOLDER_NAME, VNF_CORE_BASE_TEMPLATE_FILENAME + ) + bicep_contents = render_bicep_contents_from_j2(template_path, {}) + # Create Bicep element with manifest contents + bicep_file = BicepDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME, BASE_FOLDER_NAME), bicep_contents + ) + return bicep_file + + def build_all_parameters_json(self) -> JSONDefinitionElementBuilder: + """Create object for all_parameters.json.""" + params_content = { + "location": self.config.location, + "publisherName": self.config.publisher_name, + "publisherResourceGroupName": self.config.publisher_resource_group_name, + "acrArtifactStoreName": self.config.acr_artifact_store_name, + "saArtifactStoreName": self.config.blob_artifact_store_name, + "acrManifestName": self.config.acr_manifest_name, + "saManifestName": self.config.sa_manifest_name, + "nfDefinitionGroup": self.config.nf_name, + "nfDefinitionVersion": self.config.version + } + base_file = JSONDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME), json.dumps(params_content, indent=4) + ) + return base_file + + def _get_default_config(self, vhd) -> Dict[str, Any]: + """Get default VHD config for Azure Core VNF.""" + default_config = {} + if vhd.image_disk_size_GB: + default_config.update({"image_disk_size_GB": vhd.image_disk_size_GB}) + if vhd.image_hyper_v_generation: + default_config.update( + {"image_hyper_v_generation": vhd.image_hyper_v_generation} + ) + else: + # Default to V1 if not specified + default_config.update({"image_hyper_v_generation": "V1"}) + if vhd.image_api_version: + default_config.update({"image_api_version": vhd.image_api_version}) + + # Add imageName + default_config["imageName"] = self.config.nf_name + 'Image' + return default_config + + def _generate_type_specific_nf_application(self, processor) -> Tuple[List, List]: + """Generate the type specific nf application.""" + arm_nf = [] + image_nf = [] + nf_application = processor.generate_nf_application() + + if isinstance(processor, AzureCoreArmBuildProcessor): + arm_nf.append(nf_application) + elif isinstance(processor, VHDProcessor): + image_nf.append(nf_application) + else: + raise TypeError(f"Type: {type(processor)} is not valid") + logger.debug("Created nf application %s", nf_application.name) + return (arm_nf, image_nf) + + def _generate_type_specific_artifact_manifest(self, processor): + """Generate the type specific artifact manifest list.""" + arm_artifacts = [] + sa_artifacts = [] + + if isinstance(processor, AzureCoreArmBuildProcessor): + arm_artifacts = processor.get_artifact_manifest_list() + logger.debug( + "Created list of artifacts from %s arm template(s) provided: %s", + len(self.config.arm_templates), + arm_artifacts, + ) + elif isinstance(processor, VHDProcessor): + sa_artifacts = processor.get_artifact_manifest_list() + logger.debug( + "Created list of artifacts from vhd image provided: %s", + sa_artifacts, + ) + + return (arm_artifacts, sa_artifacts) + + def _get_nfd_template_params( + self, arm_nf_application_list, image_nf_application_list) -> Dict[str, Any]: + """Get the nfd template params.""" + return { + "nfvi_type": 'AzureCore', + "acr_nf_applications": arm_nf_application_list, + "sa_nf_applications": image_nf_application_list, + "nexus_image_nf_applications": [], + "deploy_parameters_file": DEPLOY_PARAMETERS_FILENAME, + "vhd_parameters_file": VHD_PARAMETERS_FILENAME, + "template_parameters_file": TEMPLATE_PARAMETERS_FILENAME + } diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_nexus_vnf_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_nexus_vnf_handler.py new file mode 100644 index 00000000000..db74b97bc81 --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_nexus_vnf_handler.py @@ -0,0 +1,158 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +import json +from pathlib import Path +from typing import Dict, Any, List, Optional +from knack.log import get_logger + +from azext_aosm.build_processors.arm_processor import NexusArmBuildProcessor +from azext_aosm.build_processors.nexus_image_processor import NexusImageProcessor +from azext_aosm.common.constants import ( + BASE_FOLDER_NAME, + VNF_TEMPLATE_FOLDER_NAME, + VNF_OUTPUT_FOLDER_FILENAME, + DEPLOY_PARAMETERS_FILENAME, + NEXUS_IMAGE_PARAMETERS_FILENAME, + TEMPLATE_PARAMETERS_FILENAME, + VNF_NEXUS_BASE_TEMPLATE_FILENAME, +) +from azext_aosm.configuration_models.onboarding_vnf_input_config import ( + OnboardingNexusVNFInputConfig, +) +from azext_aosm.configuration_models.common_parameters_config import ( + NexusVNFCommonParametersConfig, +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.json_builder import ( + JSONDefinitionElementBuilder, +) +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.inputs.nexus_image_input import NexusImageFileInput +from .onboarding_vnf_handler import OnboardingVNFCLIHandler +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path, split_image_path + +logger = get_logger(__name__) + + +class OnboardingNexusVNFCLIHandler(OnboardingVNFCLIHandler): + """CLI handler for publishing NFDs.""" + + config: OnboardingNexusVNFInputConfig + + def _get_input_config( + self, input_config: Optional[dict] = None + ) -> OnboardingNexusVNFInputConfig: + """Get the configuration for the command.""" + if input_config is None: + input_config = {} + return OnboardingNexusVNFInputConfig(**input_config) + + def _get_params_config( + self, config_file: Path + ) -> NexusVNFCommonParametersConfig: + """Get the configuration for the command.""" + with open(config_file, "r", encoding="utf-8") as _file: + params_dict = json.load(_file) + if params_dict is None: + params_dict = {} + return NexusVNFCommonParametersConfig(**params_dict) + + def _get_processor_list(self) -> List[NexusArmBuildProcessor | NexusImageProcessor]: + processor_list: List[NexusArmBuildProcessor | NexusImageProcessor] = [] + # for each arm template, instantiate arm processor + for arm_template in self.config.arm_templates: + arm_input = ArmTemplateInput( + artifact_name=arm_template.artifact_name, + artifact_version=arm_template.version, + default_config=None, + template_path=Path(arm_template.file_path).absolute(), + ) + processor_list.append( + NexusArmBuildProcessor(arm_input.artifact_name, arm_input, + expose_all_params=self.config.expose_all_parameters) + ) + # For each image, instantiate image processor + for image in self.config.images: + (source_acr_registry, name, version) = split_image_path(image) + image_input = NexusImageFileInput( + artifact_name=name, + artifact_version=version, + default_config=None, + source_acr_registry=source_acr_registry, + ) + processor_list.append( + NexusImageProcessor(image_input.artifact_name, image_input, + expose_all_params=self.config.expose_all_parameters) + ) + + return processor_list + + def build_base_bicep(self) -> BicepDefinitionElementBuilder: + """Build the base bicep file.""" + # Build manifest bicep contents, with j2 template + template_path = get_template_path( + VNF_TEMPLATE_FOLDER_NAME, VNF_NEXUS_BASE_TEMPLATE_FILENAME + ) + bicep_contents = render_bicep_contents_from_j2(template_path, {}) + # Create Bicep element with manifest contents + bicep_file = BicepDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME, BASE_FOLDER_NAME), bicep_contents + ) + return bicep_file + + def build_all_parameters_json(self) -> JSONDefinitionElementBuilder: + """Build the all parameters json file.""" + params_content = { + "location": self.config.location, + "publisherName": self.config.publisher_name, + "publisherResourceGroupName": self.config.publisher_resource_group_name, + "acrArtifactStoreName": self.config.acr_artifact_store_name, + "acrManifestName": self.config.acr_manifest_name, + "nfDefinitionGroup": self.config.nf_name, + "nfDefinitionVersion": self.config.version + } + base_file = JSONDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME), json.dumps(params_content, indent=4) + ) + return base_file + + def _generate_type_specific_nf_application(self, processor) -> "tuple[list, list]": + """Generate the type specific nf application.""" + arm_nf = [] + image_nf = [] + nf_application = processor.generate_nf_application() + + if isinstance(processor, NexusArmBuildProcessor): + arm_nf.append(nf_application) + elif isinstance(processor, NexusImageProcessor): + image_nf.append(nf_application) + else: + raise TypeError(f"Type: {type(processor)} is not valid") + logger.debug("Created nf application %s", nf_application.name) + return (arm_nf, image_nf) + + def _generate_type_specific_artifact_manifest(self, processor): + """Generate the type specific artifact manifest list""" + arm_manifest = processor.get_artifact_manifest_list() + sa_manifest = [] + + return (arm_manifest, sa_manifest) + + def _get_nfd_template_params( + self, arm_nf_application_list, image_nf_application_list) -> Dict[str, Any]: + """Get the nfd template params.""" + return { + "nfvi_type": 'AzureOperatorNexus', + "acr_nf_applications": arm_nf_application_list, + "sa_nf_applications": [], + "nexus_image_nf_applications": image_nf_application_list, + "deploy_parameters_file": DEPLOY_PARAMETERS_FILENAME, + "template_parameters_file": TEMPLATE_PARAMETERS_FILENAME, + "image_parameters_file": NEXUS_IMAGE_PARAMETERS_FILENAME + } diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_nfd_base_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_nfd_base_handler.py new file mode 100644 index 00000000000..d1834fc3ddc --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_nfd_base_handler.py @@ -0,0 +1,18 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azext_aosm.cli_handlers.onboarding_base_handler import OnboardingBaseCLIHandler + + +class OnboardingNFDBaseCLIHandler(OnboardingBaseCLIHandler): + """Abstract base class for NFD CLI handlers.""" + + @property + def default_config_file_name(self) -> str: + """Get the default configuration file name.""" + raise NotImplementedError + + def build_base_bicep(self): + # TODO: Implement + raise NotImplementedError diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_nsd_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_nsd_handler.py new file mode 100644 index 00000000000..bd3b8bcacc6 --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_nsd_handler.py @@ -0,0 +1,333 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +import json +from pathlib import Path +from typing import Literal, Optional + +from knack.log import get_logger +from azext_aosm.build_processors.nfd_processor import NFDProcessor +from azext_aosm.build_processors.arm_processor import AzureCoreArmBuildProcessor +from azext_aosm.cli_handlers.onboarding_nfd_base_handler import OnboardingBaseCLIHandler +from azext_aosm.common.constants import ( # NSD_DEFINITION_TEMPLATE_FILENAME, + ARTIFACT_LIST_FILENAME, + BASE_FOLDER_NAME, + CGS_FILENAME, + CGS_NAME, + DEPLOY_PARAMETERS_FILENAME, + MANIFEST_FOLDER_NAME, + NSD_BASE_TEMPLATE_FILENAME, + NSD_DEFINITION_FOLDER_NAME, + NSD_DEFINITION_TEMPLATE_FILENAME, + NSD_INPUT_FILENAME, + NSD_MANIFEST_TEMPLATE_FILENAME, + NSD_OUTPUT_FOLDER_FILENAME, + NSD_TEMPLATE_FOLDER_NAME, + TEMPLATE_PARAMETERS_FILENAME, +) +from azext_aosm.configuration_models.common_parameters_config import ( + NSDCommonParametersConfig, +) +from azext_aosm.configuration_models.onboarding_nsd_input_config import ( + OnboardingNSDInputConfig, +) +from azext_aosm.definition_folder.builder.artifact_builder import ( + ArtifactDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.json_builder import ( + JSONDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.inputs.nfd_input import NFDInput +from azext_aosm.vendored_sdks.models import NetworkFunctionDefinitionVersion +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path +from azext_aosm.vendored_sdks import HybridNetworkManagementClient +from azext_aosm.configuration_models.common_input import ArmTemplatePropertiesConfig +from azext_aosm.configuration_models.onboarding_nsd_input_config import NetworkFunctionPropertiesConfig +logger = get_logger(__name__) + + +class OnboardingNSDCLIHandler(OnboardingBaseCLIHandler): + """CLI handler for publishing NFDs.""" + + config: OnboardingNSDInputConfig + processors: list[AzureCoreArmBuildProcessor | NFDProcessor] + nfvi_types: list[Literal["AzureArcKubernetes"] | Literal["AzureOperatorNexus"] | + Literal["AzureCore"] | Literal["Unknown"]] = [] + + @property + def default_config_file_name(self) -> str: + """Get the default configuration file name.""" + return NSD_INPUT_FILENAME + + @property + def output_folder_file_name(self) -> str: + """Get the output folder file name.""" + return NSD_OUTPUT_FOLDER_FILENAME + + @property + def nfvi_site_name(self) -> str: + """Return the name of the NFVI used for the NSDV.""" + assert isinstance(self.config, OnboardingNSDInputConfig) + return f"{self.config.nsd_name}_NFVI" + + def _get_input_config(self, input_config: Optional[dict] = None) -> OnboardingNSDInputConfig: + """Get the configuration for the command.""" + if input_config is None: + input_config = {} + return OnboardingNSDInputConfig(**input_config) + + def _get_params_config(self, config_file: Path) -> NSDCommonParametersConfig: + """Get the configuration for the command.""" + with open(config_file, "r", encoding="utf-8") as _file: + params_dict = json.load(_file) + if params_dict is None: + params_dict = {} + return NSDCommonParametersConfig(**params_dict) + + def _get_processor_list(self) -> list: + processor_list: list[AzureCoreArmBuildProcessor | NFDProcessor] = [] + # for each resource element template, instantiate processor + for resource_element in self.config.resource_element_templates: + if resource_element.resource_element_type == "ArmTemplate": + assert isinstance(resource_element.properties, ArmTemplatePropertiesConfig) + arm_input = ArmTemplateInput( + artifact_name=resource_element.properties.artifact_name, + artifact_version=resource_element.properties.version, + default_config=None, + template_path=Path( + resource_element.properties.file_path + ).absolute(), + ) + # TODO: generalise for nexus in nexus ready stories + # For NSDs, we don't have the option to expose ARM template parameters. This could be supported by + # adding an 'expose_all_parameters' option to NSD input.jsonc file, as we have for NFD input files. + # For now, we prefer this simpler interface for NSDs, but we might need to revisit in the future. + processor_list.append( + AzureCoreArmBuildProcessor(arm_input.artifact_name, arm_input, expose_all_params=False) + ) + elif resource_element.resource_element_type == "NF": + assert isinstance(resource_element.properties, NetworkFunctionPropertiesConfig) + # TODO: change artifact name and version to the nfd name and version or justify why it was this + # in the first place + # AC4 note: I couldn't find a reference in the old code, but this + # does ring a bell. Was it so the artifact manifest didn't get broken with changes to NF versions? + # I.e., you could make an NF version change in CGV, and the artifact manifest, which is immutable, + # would still be valid? + # I am concerned that if we have multiple NFs we will have clashing artifact names. + # I'm not changing the behaviour right now as it's too high risk, but we should look again here. + nfdv_object = self._get_nfdv(resource_element.properties) + + # Add nfvi_type to list for creating nfvisFromSite later + # There is a 1:1 mapping between NF RET and nfvisFromSite object, + # as we shouldn't build NSDVs that deploy different RETs against the same custom location + self.nfvi_types.append(nfdv_object.properties.network_function_template.nfvi_type) + + nfd_input = NFDInput( + # This would be the alternative if we swap from nsd name/version to nfd. + # artifact_name=resource_element.properties.name, + # artifact_version=resource_element.properties.version, + artifact_name=self.config.nsd_name, + artifact_version=self.config.nsd_version, + default_config={"location": self.config.location}, + network_function_definition=nfdv_object, + arm_template_output_path=Path( + NSD_OUTPUT_FOLDER_FILENAME, + ARTIFACT_LIST_FILENAME, + resource_element.properties.name + ".bicep", + ), + ) + nfd_processor = NFDProcessor( + name=self.config.nsd_name, input_artifact=nfd_input + ) + processor_list.append(nfd_processor) + else: + # TODO: raise more specific error + raise ValueError( + f"Invalid resource element type: {resource_element.resource_element_type}" + ) + return processor_list + + def build_base_bicep(self) -> BicepDefinitionElementBuilder: + """Build the base bicep file.""" + # Build base bicep contents, with bicep template + template_path = get_template_path( + NSD_TEMPLATE_FOLDER_NAME, NSD_BASE_TEMPLATE_FILENAME + ) + bicep_contents = render_bicep_contents_from_j2(template_path, {}) + # Create Bicep element with manifest contents + bicep_file = BicepDefinitionElementBuilder( + Path(NSD_OUTPUT_FOLDER_FILENAME, BASE_FOLDER_NAME), bicep_contents + ) + return bicep_file + + def build_manifest_bicep(self) -> BicepDefinitionElementBuilder: + """Build the manifest bicep file.""" + artifact_list = [] + for processor in self.processors: + artifact_list.extend(processor.get_artifact_manifest_list()) + logger.debug( + "Created list of artifacts from resource element(s) provided: %s", + artifact_list, + ) + template_path = get_template_path( + NSD_TEMPLATE_FOLDER_NAME, NSD_MANIFEST_TEMPLATE_FILENAME + ) + params = { + "acr_artifacts": artifact_list, + "sa_artifacts": [] + } + bicep_contents = render_bicep_contents_from_j2(template_path, params) + + bicep_file = BicepDefinitionElementBuilder( + Path(NSD_OUTPUT_FOLDER_FILENAME, MANIFEST_FOLDER_NAME), bicep_contents + ) + return bicep_file + + def build_artifact_list(self) -> ArtifactDefinitionElementBuilder: + """Build the artifact list.""" + # Build artifact list for ArmTemplates + artifact_list = [] + nf_files = [] + for processor in self.processors: + (artifacts, files) = processor.get_artifact_details() + if artifacts not in artifact_list: + artifact_list.extend(artifacts) + # If NF, file is local file builder, if ARM, file is empty + nf_files.extend(files) + + artifact_file = ArtifactDefinitionElementBuilder( + Path(NSD_OUTPUT_FOLDER_FILENAME, ARTIFACT_LIST_FILENAME), artifact_list + ) + for nf_arm_template in nf_files: + artifact_file.add_supporting_file(nf_arm_template) + + return artifact_file + + def build_resource_bicep(self) -> BicepDefinitionElementBuilder: + """Build the resource bicep file.""" + schema_properties = {} + nf_names = [] + ret_list = [] + supporting_files = [] + + # For each RET (arm template or NF), generate RET + for processor in self.processors: + nf_ret = processor.generate_resource_element_template() + ret_list.append(nf_ret) + + # Add supporting file: config mappings + deploy_values = nf_ret.configuration.parameter_values + mapping_file = LocalFileBuilder( + Path( + NSD_OUTPUT_FOLDER_FILENAME, + NSD_DEFINITION_FOLDER_NAME, + processor.name + "-mappings.json", + ), + json.dumps(json.loads(deploy_values), indent=4), + ) + supporting_files.append(mapping_file) + + # Generate deployParameters schema properties + params_schema = processor.generate_schema() + schema_properties.update(params_schema) + + # List of NF RET names, for adding to required part of CGS + nf_names.append(processor.name) + + # If all NF RETs nfvi_types are AzureCore, only make one nfviFromSite object + # This is a design decision, for simplification of nfvisFromSite and also + # so that users are discouraged from deploying NFs across multiple locations + # within a single SNS + if all(nfvi_type == "AzureCore" for nfvi_type in self.nfvi_types): + self.nfvi_types = ["AzureCore"] + + template_path = get_template_path( + NSD_TEMPLATE_FOLDER_NAME, NSD_DEFINITION_TEMPLATE_FILENAME + ) + + params = { + "nsdv_description": self.config.nsdv_description, + "nfvi_types": self.nfvi_types, + "cgs_name": CGS_NAME, + "nfvi_site_name": self.nfvi_site_name, + "nf_rets": ret_list, + "cgs_file": CGS_FILENAME, + "deploy_parameters_file": DEPLOY_PARAMETERS_FILENAME, + "template_parameters_file": TEMPLATE_PARAMETERS_FILENAME, + } + + bicep_contents = render_bicep_contents_from_j2( + template_path, params + ) + # Generate the nsd bicep file + bicep_file = BicepDefinitionElementBuilder( + Path(NSD_OUTPUT_FOLDER_FILENAME, NSD_DEFINITION_FOLDER_NAME), bicep_contents + ) + + # Add the config mappings for each nf + for mappings_file in supporting_files: + bicep_file.add_supporting_file(mappings_file) + + # Add the accompanying cgs + bicep_file.add_supporting_file( + self._render_config_group_schema_contents(schema_properties, nf_names) + ) + + return bicep_file + + def build_all_parameters_json(self) -> JSONDefinitionElementBuilder: + """Build all parameters json.""" + params_content = { + "location": self.config.location, + "publisherName": self.config.publisher_name, + "publisherResourceGroupName": self.config.publisher_resource_group_name, + "acrArtifactStoreName": self.config.acr_artifact_store_name, + "acrManifestName": self.config.acr_manifest_name, + "nsDesignGroup": self.config.nsd_name, + "nsDesignVersion": self.config.nsd_version, + "nfviSiteName": self.nfvi_site_name, + } + base_file = JSONDefinitionElementBuilder( + Path(NSD_OUTPUT_FOLDER_FILENAME), json.dumps(params_content, indent=4) + ) + return base_file + + @staticmethod + def _render_config_group_schema_contents(complete_schema, nf_names): + params_content = { + "$schema": "https://json-schema.org/draft-07/schema#", + "title": CGS_NAME, + "type": "object", + "properties": complete_schema, + "required": nf_names, + } + return LocalFileBuilder( + Path( + NSD_OUTPUT_FOLDER_FILENAME, + NSD_DEFINITION_FOLDER_NAME, + CGS_FILENAME, + ), + json.dumps(params_content, indent=4), + ) + + def _get_nfdv(self, nf_properties) -> NetworkFunctionDefinitionVersion: + """Get the existing NFDV resource object.""" + print( + f"Reading existing NFDV resource object {nf_properties.version} from group {nf_properties.name}" + ) + assert isinstance(self.aosm_client, HybridNetworkManagementClient) + nfdv_object = self.aosm_client.network_function_definition_versions.get( + resource_group_name=nf_properties.publisher_resource_group, + publisher_name=nf_properties.publisher, + network_function_definition_group_name=nf_properties.name, + network_function_definition_version_name=nf_properties.version, + ) + return nfdv_object diff --git a/src/aosm/azext_aosm/cli_handlers/onboarding_vnf_handler.py b/src/aosm/azext_aosm/cli_handlers/onboarding_vnf_handler.py new file mode 100644 index 00000000000..0c3cc226c30 --- /dev/null +++ b/src/aosm/azext_aosm/cli_handlers/onboarding_vnf_handler.py @@ -0,0 +1,180 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +from pathlib import Path +from abc import abstractmethod + +from .onboarding_nfd_base_handler import OnboardingNFDBaseCLIHandler +from knack.log import get_logger +from azext_aosm.build_processors.arm_processor import BaseArmBuildProcessor +from azext_aosm.build_processors.vhd_processor import VHDProcessor +from azext_aosm.common.utils import render_bicep_contents_from_j2, get_template_path +from azext_aosm.configuration_models.onboarding_vnf_input_config import ( + OnboardingCoreVNFInputConfig, OnboardingNexusVNFInputConfig +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.artifact_builder import ArtifactDefinitionElementBuilder + +from azext_aosm.common.constants import ( + ARTIFACT_LIST_FILENAME, + NF_DEFINITION_FOLDER_NAME, + VNF_DEFINITION_TEMPLATE_FILENAME, + VNF_INPUT_FILENAME, + VNF_OUTPUT_FOLDER_FILENAME, + VNF_TEMPLATE_FOLDER_NAME, + MANIFEST_FOLDER_NAME, + VNF_MANIFEST_TEMPLATE_FILENAME +) + +logger = get_logger(__name__) + + +class OnboardingVNFCLIHandler(OnboardingNFDBaseCLIHandler): + """CLI handler for publishing NFDs.""" + + config: OnboardingCoreVNFInputConfig | OnboardingNexusVNFInputConfig + processors: list[BaseArmBuildProcessor | VHDProcessor] + + @property + def default_config_file_name(self) -> str: + """Get the default configuration file name.""" + return VNF_INPUT_FILENAME + + @property + def output_folder_file_name(self) -> str: + """Get the output folder file name.""" + return VNF_OUTPUT_FOLDER_FILENAME + + def build_artifact_list(self) -> ArtifactDefinitionElementBuilder: + """Build the artifact list. + + Gets list of artifacts to be including in the artifacts.json. + This is used during the publish command, to upload the artifacts correctly. + + """ + logger.info("Creating artifacts list for artifacts.json") + # assert isinstance(self.config, OnboardingBaseVNFInputConfig) + artifact_list = [] + # For each arm template, get list of artifacts and combine + for processor in self.processors: + (artifacts, _) = processor.get_artifact_details() + if artifacts not in artifact_list: + artifact_list.extend(artifacts) + logger.debug( + "Created list of artifact details from %s arm template(s) and the image provided: %s", + len(self.config.arm_templates), + artifact_list, + ) + + # Generate Artifact Element with artifact list (of arm template and vhd images) + return ArtifactDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME, ARTIFACT_LIST_FILENAME), artifact_list + ) + + def build_resource_bicep(self) -> BicepDefinitionElementBuilder: + """Build the resource bicep file. + + Creates nfDefinition.bicep and its supporting files. + + For each processor: + - Generates NF application for each processor + - Generates deployParameters (flattened to be one schema overall) + - Generates supporting parameters files (to avoid stringified JSON in template) + + """ + logger.info("Creating artifacts list for artifacts.json") + arm_nf_application_list = [] + image_nf_application_list = [] + supporting_files = [] + schema_properties = {} + + for processor in self.processors: + # Generate NF Application + add to correct list for nfd template + (arm_nf_application, image_nf_application) = self._generate_type_specific_nf_application(processor) + arm_nf_application_list.extend(arm_nf_application) + image_nf_application_list.extend(image_nf_application) + + # Generate deployParameters schema properties + params_schema = processor.generate_schema() + schema_properties.update(params_schema) + + # Generate local file for parameters, i.e imageParameters + parameters_file = processor.generate_parameters_file() + supporting_files.append(parameters_file) + + # Create bicep contents using vnf defintion j2 template + template_path = get_template_path( + VNF_TEMPLATE_FOLDER_NAME, VNF_DEFINITION_TEMPLATE_FILENAME + ) + + params = self._get_nfd_template_params(arm_nf_application_list, image_nf_application_list) + bicep_contents = render_bicep_contents_from_j2( + template_path, params + ) + + # Create the bicep element + bicep_file = BicepDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME, NF_DEFINITION_FOLDER_NAME), + bicep_contents, + ) + # Add deployParameters, vhdParameters and templateParameters as supporting files + for supporting_file in supporting_files: + bicep_file.add_supporting_file(supporting_file) + + # Add the deployParameters schema file + bicep_file.add_supporting_file( + self._render_deploy_params_schema( + schema_properties, VNF_OUTPUT_FOLDER_FILENAME, NF_DEFINITION_FOLDER_NAME + ) + ) + return bicep_file + + def build_manifest_bicep(self) -> BicepDefinitionElementBuilder: + """Build the manifest bicep file.""" + acr_artifact_list = [] + sa_artifact_list = [] + + logger.info("Creating artifact manifest bicep") + + for processor in self.processors: + (arm_artifact, sa_artifact) = self._generate_type_specific_artifact_manifest(processor) + acr_artifact_list.extend(arm_artifact) + sa_artifact_list.extend(sa_artifact) + + # Build manifest bicep contents, with j2 template + template_path = get_template_path( + VNF_TEMPLATE_FOLDER_NAME, VNF_MANIFEST_TEMPLATE_FILENAME + ) + params = { + "acr_artifacts": acr_artifact_list, + "sa_artifacts": sa_artifact_list + } + bicep_contents = render_bicep_contents_from_j2( + template_path, params + ) + + # Create Bicep element with manifest contents + bicep_file = BicepDefinitionElementBuilder( + Path(VNF_OUTPUT_FOLDER_FILENAME, MANIFEST_FOLDER_NAME), + bicep_contents, + ) + + logger.info("Created artifact manifest bicep element") + return bicep_file + + @abstractmethod + def _get_nfd_template_params(self, arm_nf_application_list, image_nf_application_list): + return NotImplementedError + + @abstractmethod + def _generate_type_specific_nf_application(self, processor): + return NotImplementedError + + @abstractmethod + def _generate_type_specific_artifact_manifest(self, processor): + return NotImplementedError diff --git a/src/aosm/azext_aosm/commands.py b/src/aosm/azext_aosm/commands.py index abc33f8444b..e0d3a4fee8e 100644 --- a/src/aosm/azext_aosm/commands.py +++ b/src/aosm/azext_aosm/commands.py @@ -5,22 +5,21 @@ from azure.cli.core import AzCommandsLoader -from azext_aosm._client_factory import cf_aosm - def load_command_table(self: AzCommandsLoader, _): - 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", "generate_definition_config") - g.custom_command("build", "build_definition") - g.custom_command("delete", "delete_published_definition") - g.custom_command("publish", "publish_definition") - with self.command_group("aosm nsd", client_factory=cf_aosm) as g: + 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") + + 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", "generate_design_config") - g.custom_command("build", "build_design") - g.custom_command("delete", "delete_published_design") - g.custom_command("publish", "publish_design") + g.custom_command("generate-config", "onboard_nsd_generate_config") + g.custom_command("build", "onboard_nsd_build") + g.custom_command("publish", "onboard_nsd_publish") + # g.custom_command("delete", "onboard_nsd_delete") with self.command_group("aosm", is_preview=True): pass diff --git a/src/aosm/azext_aosm/generate_nfd/__init__.py b/src/aosm/azext_aosm/common/__init__.py similarity index 100% rename from src/aosm/azext_aosm/generate_nfd/__init__.py rename to src/aosm/azext_aosm/common/__init__.py diff --git a/src/aosm/azext_aosm/common/artifact.py b/src/aosm/azext_aosm/common/artifact.py new file mode 100644 index 00000000000..a498f4dd961 --- /dev/null +++ b/src/aosm/azext_aosm/common/artifact.py @@ -0,0 +1,603 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +import math +import shutil +from abc import ABC, abstractmethod +from functools import lru_cache +from pathlib import Path +import tempfile +from time import sleep +from typing import Any, MutableMapping, Optional + +from azext_aosm.common.command_context import CommandContext +from azext_aosm.common.utils import ( + convert_bicep_to_arm, + clean_registry_name, + push_image_from_local_registry_to_acr, + call_subprocess_raise_output, + check_tool_installed, +) +from azext_aosm.configuration_models.common_parameters_config import ( + BaseCommonParametersConfig, + NFDCommonParametersConfig, + CoreVNFCommonParametersConfig, +) +from azext_aosm.vendored_sdks.azure_storagev2.blob.v2022_11_02 import ( + BlobClient, + BlobType, +) +from azext_aosm.common.registry import ContainerRegistry, AzureContainerRegistry +from azext_aosm.vendored_sdks.models import ArtifactType +from azext_aosm.vendored_sdks import HybridNetworkManagementClient +from azure.core.exceptions import ServiceResponseError +from knack.log import get_logger +from oras.client import OrasClient + +logger = get_logger(__name__) + + +# TODO: Split these out into separate files, probably in a new artifacts module +class BaseArtifact(ABC): + """Abstract base class for artifacts.""" + + def __init__(self, artifact_name: str, artifact_type: str, artifact_version: str): + self.artifact_name = artifact_name + self.artifact_type = artifact_type + self.artifact_version = artifact_version + + def to_dict(self) -> dict: + """Convert an instance to a dict.""" + output_dict = {"type": ARTIFACT_CLASS_TO_TYPE[type(self)]} + output_dict.update({k: vars(self)[k] for k in vars(self)}) + return output_dict + + @classmethod + @abstractmethod + def from_dict(cls, artifact_dict): + """Create an instance from a dict.""" + raise NotImplementedError + + @abstractmethod + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + + +class BaseACRArtifact(BaseArtifact): + """Abstract base class for ACR artifacts.""" + + @abstractmethod + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + + @staticmethod + @lru_cache(maxsize=32) + def _manifest_credentials( + config: BaseCommonParametersConfig, + aosm_client: HybridNetworkManagementClient, + ) -> MutableMapping[str, Any]: + """Gets the details for uploading the artifacts in the manifest.""" + retries = 0 + # This retry logic is to handle the ServiceResponseError that is hit in the integration tests. + # This error is not hit when running the cli normally because the CLI framework automatically retries, + # the testing framework does not support automatic retries. + while retries < 2: + try: + credential_dict = aosm_client.artifact_manifests.list_credential( + resource_group_name=config.publisherResourceGroupName, + publisher_name=config.publisherName, + artifact_store_name=config.acrArtifactStoreName, + artifact_manifest_name=config.acrManifestName, + ).as_dict() + break + except ServiceResponseError as error: + retries += 1 + if retries == 2: + logger.debug(error, exc_info=True) + raise ServiceResponseError("Failed to get manifest credentials.") + + return credential_dict + + @staticmethod + def _get_oras_client(manifest_credentials: MutableMapping[str, Any]) -> OrasClient: + client = OrasClient(hostname=manifest_credentials["acr_server_url"]) + client.login( + username=manifest_credentials["username"], + password=manifest_credentials["acr_token"], + ) + return client + + @staticmethod + def _get_acr(upload_client: OrasClient) -> str: + """ + Get the name of the ACR. + + :return: The name of the ACR + """ + assert hasattr(upload_client, "remote") + if not upload_client.remote.hostname: + raise ValueError( + "Cannot upload artifact. Oras client has no remote hostname." + ) + return clean_registry_name(upload_client.remote.hostname) + + +class LocalFileACRArtifact(BaseACRArtifact): + """Class for ACR artifacts from a local file.""" + + def __init__(self, artifact_name, artifact_type, artifact_version, file_path: Path): + super().__init__(artifact_name, artifact_type, artifact_version) + self.file_path = file_path + + def to_dict(self) -> dict: + """Convert an instance to a dict.""" + # Take the output_dict from the parent class + output_dict = super().to_dict() + # Add the file_path to the output_dict + output_dict["file_path"] = str(self.file_path) + return output_dict + + @classmethod + def from_dict(cls, artifact_dict): + try: + artifact_name = artifact_dict["artifact_name"] + artifact_type = artifact_dict["artifact_type"] + artifact_version = artifact_dict["artifact_version"] + file_path = Path(artifact_dict["file_path"]) + return LocalFileACRArtifact( + artifact_name=artifact_name, + artifact_type=artifact_type, + artifact_version=artifact_version, + file_path=file_path, + ) + except KeyError as error: + raise ValueError( + f"Artifact is missing required field {error}.\n" + f"Artifact is: {artifact_dict}.\n" + "This is unexpected and most likely comes from manual editing " + "of the definition folder." + ) + + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + logger.debug("LocalFileACRArtifact config: %s", config) + + # TODO: remove, this is temporary until we fix in artifact reader + self.file_path = Path(self.file_path) + # For NSDs, we provide paths relative to the artifacts folder, resolve them to absolute paths + if not self.file_path.is_absolute(): + output_folder_path = command_context.cli_options["definition_folder"] + resolved_path = output_folder_path.resolve() + absolute_file_path = resolved_path / self.file_path + self.file_path = absolute_file_path + + if self.file_path.suffix == ".bicep": + # Uploading the nf_template as part of the NSD will use this code path + # This does mean we can never have a bicep file as an artifact, but that should be OK + logger.debug("Converting self.file_path to ARM") + arm_template = convert_bicep_to_arm(self.file_path) + self.file_path = self.file_path.with_suffix(".json") + json.dump(arm_template, self.file_path.open("w")) + logger.debug("Converted bicep file to ARM as: %s", self.file_path) + + 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}" + logger.debug("Uploading %s to %s", self.file_path, target) + + if self.artifact_type == ArtifactType.ARM_TEMPLATE.value: + retries = 0 + while True: + try: + oras_client.push(files=[self.file_path], target=target) + break + except ValueError as error: + if retries < 20: + logger.info( + "Retrying pushing local artifact to ACR. Retries so far: %s", + retries, + ) + retries += 1 + sleep(3) + continue + + logger.error("Failed to upload %s to %s.", self.file_path, target) + logger.debug(error, exc_info=True) + raise error + + logger.info( + "LocalFileACRArtifact uploaded %s to %s using oras push", + self.file_path, + target, + ) + + elif self.artifact_type == ArtifactType.OCI_ARTIFACT.value: + + target_acr_name = target_acr.replace(".azurecr.io", "") + target_acr_with_protocol = f"oci://{target_acr}" + username = manifest_credentials["username"] + password = manifest_credentials["acr_token"] + + check_tool_installed("docker") + check_tool_installed("helm") + + # tmpdir is only used if file_path is dir, but `with` context manager is cleaner to use, so we always + # set up the tmpdir, even if it doesn't end up being used. + with tempfile.TemporaryDirectory() as tmpdir: + if self.file_path.is_dir(): + helm_package_cmd = [ + str(shutil.which("helm")), + "package", + self.file_path, + "--destination", + tmpdir, + ] + call_subprocess_raise_output(helm_package_cmd) + self.file_path = Path( + tmpdir, f"{self.artifact_name}-{self.artifact_version}.tgz" + ) + + # This seems to prevent occasional helm login failures + acr_login_cmd = [ + str(shutil.which("az")), + "acr", + "login", + "--name", + target_acr_name, + "--username", + username, + "--password", + password, + ] + call_subprocess_raise_output(acr_login_cmd) + + try: + helm_login_cmd = [ + str(shutil.which("helm")), + "registry", + "login", + target_acr, + "--username", + username, + "--password", + password, + ] + call_subprocess_raise_output(helm_login_cmd) + + push_command = [ + str(shutil.which("helm")), + "push", + self.file_path, + target_acr_with_protocol, + ] + call_subprocess_raise_output(push_command) + finally: + helm_logout_cmd = [ + str(shutil.which("helm")), + "registry", + "logout", + target_acr, + ] + call_subprocess_raise_output(helm_logout_cmd) + + logger.info( + "LocalFileACRArtifact uploaded %s to %s using helm push", + self.file_path, + target, + ) + + else: # TODO: Make this one of the allowed Azure CLI exceptions + raise ValueError( + f"Unexpected artifact type. Got {self.artifact_type}. " + "Expected {ArtifactType.ARM_TEMPLATE.value} or {ArtifactType.OCI_ARTIFACT.value}" + ) + + +class RemoteACRArtifact(BaseACRArtifact): + """Class for ACR artifacts from a remote ACR image.""" + + def __init__( + self, + artifact_name, + artifact_type, + artifact_version, + source_registry: ContainerRegistry, + registry_namespace: str = "", + ): + super().__init__(artifact_name, artifact_type, artifact_version) + self.source_registry = source_registry + self.registry_namespace = registry_namespace + + def to_dict(self) -> dict: + """Convert an instance to a dict.""" + # Take the output_dict from the parent class + output_dict = super().to_dict() + # Add the source_registry to the output_dict + if self.source_registry: + output_dict["source_registry"] = self.source_registry.to_dict() + if self.registry_namespace: + output_dict["registry_namespace"] = self.registry_namespace + return output_dict + + @classmethod + def from_dict(cls, artifact_dict): + try: + artifact_name = artifact_dict["artifact_name"] + artifact_type = artifact_dict["artifact_type"] + artifact_version = artifact_dict["artifact_version"] + source_registry = ContainerRegistry.from_dict( + registry_dict=artifact_dict["source_registry"] + ) + registry_namespace = artifact_dict["registry_namespace"] + return RemoteACRArtifact( + artifact_name=artifact_name, + artifact_type=artifact_type, + artifact_version=artifact_version, + source_registry=source_registry, + registry_namespace=registry_namespace, + ) + except KeyError as error: + raise ValueError( + f"Artifact is missing required field {error}.\n" + f"Artifact is: {artifact_dict}.\n" + "This is unexpected and most likely comes from manual editing " + "of the definition folder." + ) from error + + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + + logger.debug("RemoteACRArtifact config: %s", config) + + manifest_credentials = self._manifest_credentials( + config=config, aosm_client=command_context.aosm_client + ) + + target_acr = clean_registry_name(manifest_credentials["acr_server_url"]) + target_username = manifest_credentials["username"] + target_password = manifest_credentials["acr_token"] + + source_image = ( + f"{self.source_registry.registry_name}/" + f"{self.registry_namespace}" + f"{self.artifact_name}" + f":{self.artifact_version}" + ) + + if command_context.cli_options["no_subscription_permissions"] or not isinstance( + self.source_registry, AzureContainerRegistry + ): + print( + f"Using docker pull and push to copy image artifact: {self.artifact_name}" + ) + check_tool_installed("docker") + self.source_registry.pull_image_to_local_registry(source_image=source_image) + + # We do not want the namespace to be included in the target image + push_image_from_local_registry_to_acr( + target_acr=target_acr, + target_image=f"{self.artifact_name}:{self.artifact_version}", + target_username=target_username, + target_password=target_password, + local_docker_image=source_image, + ) + else: + print(f"Using az acr import to copy image artifact: {self.artifact_name}") + + self.source_registry.copy_image_to_target_acr( + source_image=source_image, + target_acr=target_acr, + image_name=self.artifact_name, + image_version=self.artifact_version, + ) + + +class BaseStorageAccountArtifact(BaseArtifact): + """Abstract base class for storage account artifacts.""" + + @abstractmethod + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + + def _get_blob_client( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ) -> BlobClient: + container_basename = self.artifact_name.replace("-", "") + container_name = f"{container_basename}-{self.artifact_version}" + # For AOSM to work VHD blobs must have the suffix .vhd + if self.artifact_name.endswith("-vhd"): + blob_name = f"{self.artifact_name[:-4].replace('-', '')}-{self.artifact_version}.vhd" + else: + blob_name = container_name + + logger.debug("container name: %s, blob name: %s", container_name, blob_name) + # Liskov substitution dictates we must accept BaseCommonParametersConfig, but we should + # never be calling upload on this class unless we've got CoreVNFCommonParametersConfig + assert isinstance(config, CoreVNFCommonParametersConfig) + manifest_credentials = ( + command_context.aosm_client.artifact_manifests.list_credential( + resource_group_name=config.publisherResourceGroupName, + publisher_name=config.publisherName, + artifact_store_name=config.saArtifactStoreName, + artifact_manifest_name=config.saManifestName, + ).as_dict() + ) + + for container_credential in manifest_credentials["container_credentials"]: + if container_credential["container_name"] == container_name: + sas_uri = str(container_credential["container_sas_uri"]) + sas_uri_prefix, sas_uri_token = sas_uri.split("?", maxsplit=1) + + blob_url = f"{sas_uri_prefix}/{blob_name}?{sas_uri_token}" + logger.debug("Blob URL: %s", blob_url) + + return BlobClient.from_blob_url(blob_url) + + +class LocalFileStorageAccountArtifact(BaseStorageAccountArtifact): + """Class for storage account artifacts from a local file.""" + + 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) + + def to_dict(self) -> dict: + """Convert an instance to a dict.""" + # Take the output_dict from the parent class + output_dict = super().to_dict() + # Add the file_path to the output_dict + output_dict["file_path"] = str(self.file_path) + return output_dict + + @classmethod + def from_dict(cls, artifact_dict): + try: + artifact_name = artifact_dict["artifact_name"] + artifact_type = artifact_dict["artifact_type"] + artifact_version = artifact_dict["artifact_version"] + file_path = Path(artifact_dict["file_path"]) + return LocalFileStorageAccountArtifact( + artifact_name=artifact_name, + artifact_type=artifact_type, + artifact_version=artifact_version, + file_path=file_path, + ) + except KeyError as error: + raise ValueError( + f"Artifact is missing required field {error}.\n" + f"Artifact is: {artifact_dict}.\n" + "This is unexpected and most likely comes from manual editing " + "of the definition folder." + ) + + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + # Liskov substitution dictates we must accept BaseCommonParametersConfig, but we should + # never be calling upload on this class unless we've got NFDCommonParametersConfig + assert isinstance(config, NFDCommonParametersConfig) + logger.debug("LocalFileStorageAccountArtifact config: %s", config) + blob_client = self._get_blob_client( + config=config, command_context=command_context + ) + logger.info("Uploading local file '%s' to blob store", self.file_path) + with open(self.file_path, "rb") as artifact: + blob_client.upload_blob( + data=artifact, + overwrite=True, + blob_type=BlobType.PAGEBLOB, + progress_hook=self._vhd_upload_progress_callback, + ) + + logger.info( + "Successfully uploaded %s to %s", self.file_path, blob_client.container_name + ) + + def _vhd_upload_progress_callback( + self, current_bytes: int, total_bytes: Optional[int] + ) -> None: + """Callback function for VHD upload progress.""" + current_readable = self._convert_to_readable_size(current_bytes) + total_readable = self._convert_to_readable_size(total_bytes) + message = f"Uploaded {current_readable} of {total_readable} bytes" + logger.info(message) + print(message) + + @staticmethod + def _convert_to_readable_size(size_in_bytes: Optional[int]) -> str: + """Converts a size in bytes to a human readable size.""" + if size_in_bytes is None: + return "Unknown bytes" + size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + index = int(math.floor(math.log(size_in_bytes, 1024))) + power = math.pow(1024, index) + readable_size = round(size_in_bytes / power, 2) + return f"{readable_size} {size_name[index]}" + + +class BlobStorageAccountArtifact(BaseStorageAccountArtifact): + # TODO (Rename): Rename class, e.g. RemoteBlobStorageAccountArtifact + """Class for storage account artifacts from a remote blob.""" + + def __init__( + self, artifact_name, artifact_type, artifact_version, blob_sas_uri: str + ): + super().__init__(artifact_name, artifact_type, artifact_version) + self.blob_sas_uri = blob_sas_uri + + @classmethod + def from_dict(cls, artifact_dict): + try: + artifact_name = artifact_dict["artifact_name"] + artifact_type = artifact_dict["artifact_type"] + artifact_version = artifact_dict["artifact_version"] + blob_sas_uri = artifact_dict["blob_sas_uri"] + return BlobStorageAccountArtifact( + artifact_name=artifact_name, + artifact_type=artifact_type, + artifact_version=artifact_version, + blob_sas_uri=blob_sas_uri, + ) + except KeyError as error: + raise ValueError( + f"Artifact is missing required field {error}.\n" + f"Artifact is: {artifact_dict}.\n" + "This is unexpected and most likely comes from manual editing " + "of the definition folder." + ) + + def upload( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Upload the artifact.""" + # Liskov substitution dictates we must accept BaseCommonParametersConfig, but we should + # never be calling upload on this class unless we've got NFDCommonParametersConfig + assert isinstance(config, NFDCommonParametersConfig) + logger.info("Copy from SAS URL to blob store") + source_blob = BlobClient.from_blob_url(self.blob_sas_uri) + + if source_blob.exists(): + target_blob = self._get_blob_client( + config=config, command_context=command_context + ) + logger.debug(source_blob.url) + target_blob.start_copy_from_url(source_blob.url) + logger.info( + "Successfully copied %s from %s to %s", + source_blob.blob_name, + source_blob.account_name, + target_blob.account_name, + ) + else: + raise RuntimeError( + f"{source_blob.blob_name} does not exist in" + f" {source_blob.account_name}." + ) + + +# Mapping of artifact type names to their classes. +ARTIFACT_TYPE_TO_CLASS = { + "ACRFromLocalFile": LocalFileACRArtifact, + "ACRFromRemote": RemoteACRArtifact, + "StorageAccountFromLocalFile": LocalFileStorageAccountArtifact, + "StorageAccountFromBlob": BlobStorageAccountArtifact, +} + +# Generated mapping of artifact classes to type names. +ARTIFACT_CLASS_TO_TYPE = {v: k for k, v in ARTIFACT_TYPE_TO_CLASS.items()} diff --git a/src/aosm/azext_aosm/common/command_context.py b/src/aosm/azext_aosm/common/command_context.py new file mode 100644 index 00000000000..e65c612e7c8 --- /dev/null +++ b/src/aosm/azext_aosm/common/command_context.py @@ -0,0 +1,25 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from dataclasses import dataclass, field +from azure.cli.core import AzCli +from azure.cli.core.commands.client_factory import get_mgmt_service_client +from azure.cli.core.profiles import ResourceType +from azure.mgmt.resource import ResourceManagementClient + +from azext_aosm.vendored_sdks import HybridNetworkManagementClient + + +@dataclass +class CommandContext: + cli_ctx: AzCli + cli_options: dict = field(default_factory=dict) + + def __post_init__(self): + self.aosm_client: HybridNetworkManagementClient = get_mgmt_service_client( + self.cli_ctx, HybridNetworkManagementClient, base_url_bound=False + ) + self.resources_client: ResourceManagementClient = get_mgmt_service_client( + self.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES + ) diff --git a/src/aosm/azext_aosm/util/constants.py b/src/aosm/azext_aosm/common/constants.py similarity index 56% rename from src/aosm/azext_aosm/util/constants.py rename to src/aosm/azext_aosm/common/constants.py index 252fb41ae32..d5d86b568ca 100644 --- a/src/aosm/azext_aosm/util/constants.py +++ b/src/aosm/azext_aosm/common/constants.py @@ -5,11 +5,13 @@ """Constants used across aosm cli extension.""" from enum import Enum +from typing import Any, Dict # The types of definition that can be generated VNF = "vnf" CNF = "cnf" NSD = "nsd" +VNF_NEXUS = "vnf-nexus" class DeployableResourceTypes(str, Enum): @@ -22,57 +24,77 @@ class DeployableResourceTypes(str, Enum): BICEP_PUBLISH = "bicep-publish" ARTIFACT_UPLOAD = "artifact-upload" IMAGE_UPLOAD = "image-upload" +HELM_TEMPLATE = "helm-template" class SkipSteps(Enum): BICEP_PUBLISH = BICEP_PUBLISH ARTIFACT_UPLOAD = ARTIFACT_UPLOAD IMAGE_UPLOAD = IMAGE_UPLOAD + HELM_TEMPLATE = HELM_TEMPLATE -# Names of files used in the repo -NF_TEMPLATE_JINJA2_SOURCE_TEMPLATE = "nf_template.bicep.j2" -NF_DEFINITION_JSON_FILENAME = "nf_definition.json" -NF_DEFINITION_OUTPUT_BICEP_PREFIX = "nfd-bicep-" -NSD_DEFINITION_JINJA2_SOURCE_TEMPLATE = "nsd_template.bicep.j2" -NSD_BICEP_FILENAME = "nsd_definition.bicep" -NSD_OUTPUT_BICEP_PREFIX = "nsd-bicep-templates" -NSD_ARTIFACT_MANIFEST_BICEP_FILENAME = "artifact_manifest.bicep" -NSD_ARTIFACT_MANIFEST_SOURCE_TEMPLATE_FILENAME = ( - "artifact_manifest_template.bicep" -) +class ManifestsExist(str, Enum): + ALL = "all" + NONE = "none" + SOME = "some" + -VNF_DEFINITION_BICEP_TEMPLATE_FILENAME = "vnfdefinition.bicep" -VNF_MANIFEST_BICEP_TEMPLATE_FILENAME = "vnfartifactmanifests.bicep" +CNF_TYPE = "ContainerizedNetworkFunction" +VNF_TYPE = "VirtualNetworkFunction" -CNF_DEFINITION_JINJA2_SOURCE_TEMPLATE_FILENAME = "cnfdefinition.bicep.j2" -CNF_MANIFEST_JINJA2_SOURCE_TEMPLATE_FILENAME = "cnfartifactmanifest.bicep.j2" -CNF_DEFINITION_BICEP_TEMPLATE_FILENAME = "cnfdefinition.bicep" -CNF_MANIFEST_BICEP_TEMPLATE_FILENAME = "cnfartifactmanifest.bicep" +# Names of files used in the repo +# TODO: remove unused constants +BASE_FOLDER_NAME = "base" +ARTIFACT_LIST_FILENAME = "artifacts" +MANIFEST_FOLDER_NAME = "artifactManifest" +NF_DEFINITION_FOLDER_NAME = "nfDefinition" +ALL_PARAMETERS_FILE_NAME = "all_deploy.parameters.json" +CGS_FILENAME = "config-group-schema.json" +CGS_NAME = "ConfigGroupSchema" +DEPLOY_PARAMETERS_FILENAME = "deployParameters.json" +TEMPLATE_PARAMETERS_FILENAME = "templateParameters.json" +VHD_PARAMETERS_FILENAME = "vhdParameters.json" +NEXUS_IMAGE_PARAMETERS_FILENAME = "imageParameters.json" + +NSD_OUTPUT_FOLDER_FILENAME = "nsd-cli-output" +NSD_INPUT_FILENAME = "nsd-input.jsonc" +NSD_DEFINITION_TEMPLATE_FILENAME = "nsddefinition.bicep.j2" +NSD_MANIFEST_TEMPLATE_FILENAME = "nsdartifactmanifest.bicep.j2" +NSD_BASE_TEMPLATE_FILENAME = "nsdbase.bicep" +NSD_TEMPLATE_FOLDER_NAME = "nsd" +NSD_DEFINITION_FOLDER_NAME = "nsdDefinition" +NSD_NF_TEMPLATE_FILENAME = "nf_template.bicep.j2" + +VNF_OUTPUT_FOLDER_FILENAME = "vnf-cli-output" +VNF_INPUT_FILENAME = "vnf-input.jsonc" +VNF_DEFINITION_TEMPLATE_FILENAME = "vnfdefinition.bicep.j2" +VNF_MANIFEST_TEMPLATE_FILENAME = "vnfartifactmanifest.bicep.j2" +VNF_CORE_BASE_TEMPLATE_FILENAME = "vnfbase.bicep" +VNF_NEXUS_BASE_TEMPLATE_FILENAME = "vnfnexusbase.bicep" +VNF_TEMPLATE_FOLDER_NAME = "vnf" + +CNF_OUTPUT_FOLDER_FILENAME = "cnf-cli-output" +CNF_INPUT_FILENAME = "cnf-input.jsonc" +CNF_DEFINITION_TEMPLATE_FILENAME = "cnfdefinition.bicep.j2" +CNF_MANIFEST_TEMPLATE_FILENAME = "cnfartifactmanifest.bicep.j2" +CNF_HELM_VALIDATION_ERRORS_TEMPLATE_FILENAME = "cnfhelmtemplateerrors.txt.j2" +CNF_BASE_TEMPLATE_FILENAME = "cnfbase.bicep" CNF_VALUES_SCHEMA_FILENAME = "values.schema.json" +CNF_TEMPLATE_FOLDER_NAME = "cnf" + +NEXUS_IMAGE_REGEX = r"^[\~]?(\d+)\.(\d+)\.(\d+)$" +################# +# OLD CONSTANTS # +################# # Names of directories used in the repo -CONFIG_MAPPINGS_DIR_NAME = "configMappings" -SCHEMAS_DIR_NAME = "schemas" -TEMPLATES_DIR_NAME = "templates" +# CONFIG_MAPPINGS_DIR_NAME = "configMappings" +# SCHEMAS_DIR_NAME = "schemas" +# TEMPLATES_DIR_NAME = "templates" GENERATED_VALUES_MAPPINGS_DIR_NAME = "generatedValuesMappings" -# Items used when building NFDs/NSDs -DEPLOYMENT_PARAMETERS_FILENAME = "deploymentParameters.json" -OPTIONAL_DEPLOYMENT_PARAMETERS_FILENAME = "optionalDeploymentParameters.txt" -TEMPLATE_PARAMETERS_FILENAME = "templateParameters.json" -VHD_PARAMETERS_FILENAME = "vhdParameters.json" -OPTIONAL_DEPLOYMENT_PARAMETERS_HEADING = ( - "# The following parameters are optional as they have default values.\n" - "# If you do not wish to expose them in the NFD, find and remove them from both\n" - f"# {DEPLOYMENT_PARAMETERS_FILENAME} and {TEMPLATE_PARAMETERS_FILENAME} (and {VHD_PARAMETERS_FILENAME} if\n" - "they are there)\n" - "# You can re-run the build command with the --order-params flag to order those\n" - "# files with the optional parameters at the end of the file, and with the \n" - "# --interactive flag to interactively choose y/n for each parameter to expose.\n\n" -) - # Deployment Schema SCHEMA_PREFIX = { "$schema": "https://json-schema.org/draft-07/schema#", @@ -93,7 +115,7 @@ class SkipSteps(Enum): EXTRA_VHD_PARAMETERS = [ "image_disk_size_GB", "image_hyper_v_generation", - "image_api_version" + "image_api_version", ] # For CNF NFD Generator @@ -105,8 +127,6 @@ class SkipSteps(Enum): IMAGE_PULL_SECRETS_START_STRING = "imagePullSecrets:" IMAGE_NAME_AND_VERSION_REGEX = r"\/(?P[^\s]*):(?P[^\s)\"}]*)" -DEPLOYMENT_PARAMETER_MAPPING_REGEX = r"\{deployParameters.(.+?)\}" - # Assume that the registry id is of the form: # /subscriptions//resourceGroups//providers/ # Microsoft.ContainerRegistry/registries/ @@ -121,3 +141,10 @@ class SkipSteps(Enum): AOSM_REQUIRED_FEATURES = [ "Allow-Publisher", ] + +BASE_SCHEMA: Dict[str, Any] = { + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "properties": {}, + "required": [], +} diff --git a/src/aosm/azext_aosm/common/exceptions.py b/src/aosm/azext_aosm/common/exceptions.py new file mode 100644 index 00000000000..e9a472183ff --- /dev/null +++ b/src/aosm/azext_aosm/common/exceptions.py @@ -0,0 +1,29 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.cli.core.azclierror import UserFault + + +class InvalidFileTypeError(Exception): + """Raised when the file type is not supported by the parser""" + + +class MissingDependency(UserFault): + """Raised when the required dependency is missing""" + + +class MissingChartDependencyError(Exception): + """Raised when the chart dependency is missing""" + + +class SchemaGetOrGenerateError(Exception): + """Raised when the schema cannot be generated or retrieved""" + + +class DefaultValuesNotFoundError(UserFault): + """Raised when the default values file cannot be found""" + + +class TemplateValidationError(Exception): + """Raised when template validation fails""" diff --git a/src/aosm/azext_aosm/common/registry.py b/src/aosm/azext_aosm/common/registry.py new file mode 100644 index 00000000000..705f68addd4 --- /dev/null +++ b/src/aosm/azext_aosm/common/registry.py @@ -0,0 +1,469 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +import re +import shutil +import subprocess +from typing import List, Tuple, Dict, Union +from abc import abstractmethod +from knack.log import get_logger +from knack.util import CLIError +from azure.cli.core.azclierror import BadRequestError, ClientRequestError + +from azext_aosm.common.utils import ( + call_subprocess_raise_output, + clean_registry_name, +) + +logger = get_logger(__name__) +ACR_REGISTRY_NAME_PATTERN = r"^([a-zA-Z0-9]+\.azurecr\.io)" + + +# pylint: disable=too-few-public-methods +class ContainerRegistry: + """ + A class to represent a registry and handle all Registry operations. + """ + + def __init__(self, registry_name: str): + """ + Initialise the Registry object. + + :param registry_name: The name of the registry + """ + self.registry_name = registry_name + self.registry_namespaces: List[str] = [] + + def to_dict(self) -> Dict: + """Convert an instance to a dict.""" + output_dict = { + "type": REGISTRY_CLASS_TO_TYPE[type(self)], + "registry_name": self.registry_name, + } + return output_dict + + @classmethod + def from_dict(cls, registry_dict: Dict) -> "ContainerRegistry": + """Create an instance from a dict.""" + try: + registry_name = registry_dict["registry_name"] + registry_class = REGISTRY_TYPE_TO_CLASS[registry_dict["type"]] + return registry_class(registry_name) + except KeyError as error: + raise ValueError( + f"Registry is missing required field {error}.\n" + f"Registry is: {registry_dict}.\n" + "This is unexpected and most likely comes from manual editing " + "of the definition folder." + ) from error + + @abstractmethod + def find_image( + self, image: str, version: str + ) -> Union[Tuple["ContainerRegistry", str], Tuple[None, None]]: + """ + Find whether the given image exists in this registry. + + :param image: The image to find in the registry + (the image should be stripped of all namespace) + :param version: The version of the image (also known as image tag) + :return: The registry and namespace for the image + """ + raise NotImplementedError + + def add_namespace(self, namespace: str) -> None: + """ + Add a namespace to the registry. + + :param namespace: The namespace to add + """ + if namespace not in self.registry_namespaces: + self.registry_namespaces.append(namespace) + + def pull_image_to_local_registry(self, source_image: str) -> None: + """ + Pull image to local registry using docker pull. Requires docker. + + :param: source_image: source docker image name e.g. + uploadacr.azurecr.io/samples/nginx:stable + """ + try: + logger.info("Pulling source image %s", source_image) + pull_source_image_cmd = [ + str(shutil.which("docker")), + "pull", + source_image, + ] + call_subprocess_raise_output(pull_source_image_cmd) + except CLIError as error: + logger.debug(error, exc_info=True) + raise BadRequestError( + f"Failed to pull {source_image}. Check if this image exists in the" + f" source registry {self.registry_name} and that you have run docker" + "login on this registry." + ) from error + + +class UniversalRegistry(ContainerRegistry): + """ + A class to represent all Container Registries other than Azure Container Registry + and handle all Registry operations (primarily using docker CLI). + """ + + def find_image( + self, image: str, version: str + ) -> Union[Tuple[ContainerRegistry, str], Tuple[None, None]]: + """ + Find whether the given image exists in this registry. + + :param image: The image to find in the registry + (the image should be stripped of all namespace) + :param version: The version of the image (also known as image tag) + :return: The registry and namespace for the image + """ + + for namespace in self.registry_namespaces: + image_path = f"{self.registry_name}/{namespace}{image}:{version}" + + logger.debug("Checking if %s exists.", image_path) + + try: + manifest_inspect_cmd = [ + str(shutil.which("docker")), + "manifest", + "inspect", + image_path, + ] + + output = call_subprocess_raise_output(manifest_inspect_cmd) + + if output is not None: + return self, namespace + + except CLIError as error: + if "manifest unknown" in str(error): + continue + logger.warning( + ( + "Failed to contact source registry %s. " + "Make sure you run docker login on this registry " + "before running the aosm command." + ), + self.registry_name, + ) + logger.debug(error, exc_info=True) + return None, None + return None, None + + +class AzureContainerRegistry(ContainerRegistry): + """ + A class to represent an Azure Container Registry and handle all ACR operations. + """ + + def _login(self) -> None: + """ + Log in to the source registry using the Azure CLI. + Uses the CLI user's context to log in to the source registry. + """ + + message = f"Logging into source registry {self.registry_name}" + logger.info(message) + + try: + acr_source_login_cmd = [ + str(shutil.which("az")), + "acr", + "login", + "--name", + self.registry_name, + ] + call_subprocess_raise_output(acr_source_login_cmd) + except CLIError as error: + logger.debug(error, exc_info=True) + raise ClientRequestError( + f"Failed to log into registry {self.registry_name}" + ) from error + + def pull_image_to_local_registry(self, source_image: str) -> None: + """ + Pull image to local registry using docker pull. Requires docker. + + :param: source_image: source docker image name e.g. + uploadacr.azurecr.io/samples/nginx:stable + """ + + self._login() + try: + super().pull_image_to_local_registry(source_image) + except BadRequestError as error: + raise error + finally: + logger.info("Logging out of source registry %s", self.registry_name) + + # There is no az 'acr logout' command, so we use docker logout + docker_logout_cmd = [ + str(shutil.which("docker")), + "logout", + self.registry_name, + ] + call_subprocess_raise_output(docker_logout_cmd) + + def copy_image_to_target_acr( + self, + source_image: str, + image_name: str, + image_version: str, + target_acr: str, + ) -> None: + """ + Copy an image from this registry to a target ACR. + + :param source_image: The image to copy + e.g. uploadacr.azurecr.io/samples/nginx:1.0.0 + :param image_name: The name of the image + e.g. nginx + :param image_version: The version of the artifact + e.g. 1.0.0 + :param target_acr: The target ACR + """ + + try: + print("Copying artifact from source registry") + # In order to use az acr import cross subscription, we need to use a token + # to authenticate to the source registry. This is documented as the way to + # us az acr import cross-tenant, not cross-sub, but it also works + # cross-subscription, and meant we didn't have to make a breaking change to + # the format of input.json. Our usage here won't work cross-tenant since + # we're attempting to get the token (source) with the same context as that + # in which we are creating the ACR (i.e. the target tenant) + get_token_cmd = [str(shutil.which("az")), "account", "get-access-token"] + # Dont use call_subprocess_raise_output here as we don't want to log the + # output + called_process = subprocess.run( # noqa: S603 + get_token_cmd, + encoding="utf-8", + capture_output=True, + text=True, + check=True, + ) + access_token_json = json.loads(called_process.stdout) + access_token = access_token_json["accessToken"] + except subprocess.CalledProcessError as get_token_err: + # This error is thrown from the az account get-access-token command + # If it errored we can log the output as it doesn't contain the token + # TODO: we shouldn't be using "raise an issue" type language with users. + # We should find out what we should use and change this + logger.debug(get_token_err, exc_info=True) + raise ClientRequestError( # pylint: disable=raise-missing-from + "Failed to import image: could not get an access token from your" + " Azure account. Try logging in again with `az login` and then re-run" + " the command. If it fails again, please raise an issue." + " You can also try repeating the command using the --no-subscription-permissions" + " flag to pull the image to your local machine and then" + " push it to the Artifact Store using manifest credentials scoped" + " only to the store. This requires Docker to be installed" + " locally." + ) + + try: + acr_import_image_cmd = [ + str(shutil.which("az")), + "acr", + "import", + "--name", + target_acr, + "--source", + source_image, + "--image", + f"{image_name}:{image_version}", + "--password", + access_token, + ] + call_subprocess_raise_output(acr_import_image_cmd) + except CLIError as error: + logger.debug(error, exc_info=True) + if (" 401" in str(error)) or ("Unauthorized" in str(error)): + # As we shell out the the subprocess, I think checking for these strings + # is the best check we can do for permission failures. + raise BadRequestError( + "Failed to import image.\nThe problem may be one or more of:\n" + " - the image_source in your config file does not exist;\n" + " - the image doesn't exist;\n" + " - you do not have permissions to import images.\n" + f"You need to have Reader/AcrPull from {self.registry_name}, " + "and Contributor role + AcrPush role, or a custom " + "role that allows the importImage action and AcrPush over the " + "whole subscription in order to be able to import to the new " + "Artifact store. More information is available at " + "https://aka.ms/acr/authorization\n\n" + "If you do not have the latter then you can re-run the command using " + "the --no-subscription-permissions flag to pull the image to your " + "local machine and then push it to the Artifact Store using manifest " + "credentials scoped only to the store. This requires Docker to be " + "installed locally." + ) from error + + # Otherwise, the most likely failure is that the image already exists in the artifact + # store, so don't fail at this stage, log the error. + logger.warning( + ( + "Failed to copy %s from %s to %s. If this failure is because it already " + "exists in the target registry we can continue. If the failure was for " + "another reason, for example it does not exist in the source registry, " + "this is likely fatal. Attempting to continue.\n" + "%s" + ), + source_image, + self.registry_name, + target_acr, + error, + ) + + def find_image( + self, image: str, version: str + ) -> Union[Tuple["AzureContainerRegistry", str], Tuple[None, None]]: + """ + Find whether the given image exists in this registry. + + :param image: The image to find in the registry + (the image should be stripped of all namespace) + :param version: The version of the image (also known as image tag) + :return: The registry and namespace for the image + """ + for namespace in self.registry_namespaces: + image_with_namespace = f"{namespace}{image}:{version}" + logger.debug("Checking if %s exists.", image_with_namespace) + try: + acr_source_get_images_cmd = [ + str(shutil.which("az")), + "acr", + "repository", + "show", + "--name", + self.registry_name, + "--image", + image_with_namespace, + ] + + call_subprocess_raise_output(acr_source_get_images_cmd) + + return (self, namespace) + + except CLIError as error: + logger.debug( + ("Image %s, version %s not found in %s registry."), + image_with_namespace, + version, + self.registry_name, + ) + logger.debug(error, exc_info=True) + + return None, None + + +class ContainerRegistryHandler: + """ + A class to handle all the registries and their images. + """ + + def __init__(self, image_sources): + self.image_sources = image_sources + self.registry_list = self._create_registry_list() + + def _create_registry_list(self) -> List[ContainerRegistry]: + """ + Create a list of registry objects from the registries provided by the user. + + :return: A list of registry objects + """ + + registry_object_list: List[ContainerRegistry] = [] + + logger.debug("Creating registry list from the provided registries") + + for image_source in self.image_sources: + image_source = clean_registry_name(image_source) + + parts = image_source.split("/", 1) + registry_name = parts[0] + registry_namespace = parts[1] if len(parts) > 1 else None + + if registry_namespace: + # Make sure that the namespace ends with a slash + if not registry_namespace.endswith("/"): + registry_namespace += "/" + else: + registry_namespace = "" + + # Get the registry_object with matching registry_name from the list, + # or None if not found + registry_object = None + for registry in registry_object_list: + if registry.registry_name == registry_name: + registry_object = registry + break + + if registry_object: + # Object already exists, add namespace to it + registry_object.add_namespace(registry_namespace) + else: + # Object does not exist, create a new one + acr_match = re.match(ACR_REGISTRY_NAME_PATTERN, registry_name) + + if acr_match: + registry_object_list.append(AzureContainerRegistry(registry_name)) + else: + registry_object_list.append(UniversalRegistry(registry_name)) + + registry_object_list[-1].add_namespace(registry_namespace) + + return registry_object_list + + def find_registry_for_image( + self, image, version + ) -> Union[Tuple["ContainerRegistry", str], Tuple[None, None]]: + """ + Find the registry and namespace for a given image and version. + + :param image: The image to find the registry for + This should be stripped of all namespace (e.g nginx, not samples/nginx) + :param version: The version of the image + :return: The registry and namespace for the image + """ + # In the interest of speed, if we have just one registry and namespace, return it + if ( + len(self.registry_list) == 1 + and len(self.registry_list[0].registry_namespaces) == 1 + ): + return self.registry_list[0], self.registry_list[0].registry_namespaces[0] + + for registry in self.registry_list: + + image_registry, namespace = registry.find_image(image, version) + if (image_registry is not None) and (namespace is not None): + return image_registry, namespace + continue + + logger.warning( + ( + "Image: %s, version: %s was not found in any of the provided registries. " + "This image will not be part of your Network Function. " + "If you would like to include it, provide the registry containing this image " + "in the 'image_sources' list in the input file and run the build command again." + ), + image, + version, + ) + return None, None + + +# Mapping of registry type names to their classes. +REGISTRY_CLASS_TO_TYPE = { + UniversalRegistry: "UniversalRegistry", + AzureContainerRegistry: "AzureContainerRegistry", +} + +REGISTRY_TYPE_TO_CLASS = {value: key for key, value in REGISTRY_CLASS_TO_TYPE.items()} diff --git a/src/aosm/azext_aosm/generate_nfd/templates/cnfartifactmanifest.bicep.j2 b/src/aosm/azext_aosm/common/templates/cnf/cnfartifactmanifest.bicep.j2 similarity index 65% rename from src/aosm/azext_aosm/generate_nfd/templates/cnfartifactmanifest.bicep.j2 rename to src/aosm/azext_aosm/common/templates/cnf/cnfartifactmanifest.bicep.j2 index 0f0eeb99767..1849c2bf240 100644 --- a/src/aosm/azext_aosm/generate_nfd/templates/cnfartifactmanifest.bicep.j2 +++ b/src/aosm/azext_aosm/common/templates/cnf/cnfartifactmanifest.bicep.j2 @@ -9,13 +9,15 @@ param acrArtifactStoreName string @description('Name of the manifest to deploy for the ACR-backed Artifact Store') param acrManifestName string -// Created by the az aosm definition publish command before the template is deployed -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the cnfbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { name: publisherName - scope: resourceGroup() } -// Created by the az aosm definition publish command before the template is deployed +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +// If using publish command, this is created from deploying the cnfbase.bicep resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { parent: publisher name: acrArtifactStoreName @@ -27,11 +29,11 @@ resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/ location: location properties: { artifacts: [ - {%- for artifact in artifacts %} + {%- for artifact in acr_artifacts %} { - artifactName: '{{ artifact.name }}' + artifactName: '{{ artifact.artifact_name }}' artifactType: 'OCIArtifact' - artifactVersion: '{{ artifact.version }}' + artifactVersion: '{{ artifact.artifact_version }}' } {%- endfor %} ] diff --git a/src/aosm/azext_aosm/common/templates/cnf/cnfbase.bicep b/src/aosm/azext_aosm/common/templates/cnf/cnfbase.bicep new file mode 100644 index 00000000000..df45c2aa042 --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/cnf/cnfbase.bicep @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for a CNF +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of a Network Function Definition Group') +param nfDefinitionGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The NFD Group is the parent resource under which all NFD versions will be created. +resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' = { + parent: publisher + name: nfDefinitionGroup + location: location +} diff --git a/src/aosm/azext_aosm/generate_nfd/templates/cnfdefinition.bicep.j2 b/src/aosm/azext_aosm/common/templates/cnf/cnfdefinition.bicep.j2 similarity index 50% rename from src/aosm/azext_aosm/generate_nfd/templates/cnfdefinition.bicep.j2 rename to src/aosm/azext_aosm/common/templates/cnf/cnfdefinition.bicep.j2 index 4eeadfe6338..97e7853e7d7 100644 --- a/src/aosm/azext_aosm/generate_nfd/templates/cnfdefinition.bicep.j2 +++ b/src/aosm/azext_aosm/common/templates/cnf/cnfdefinition.bicep.j2 @@ -11,64 +11,68 @@ param nfDefinitionGroup string @description('The version of the NFDV you want to deploy, in format A.B.C') param nfDefinitionVersion string -// Created by the az aosm definition publish command before the template is deployed -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the cnfbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { name: publisherName - scope: resourceGroup() } -// Created by the az aosm definition publish command before the template is deployed +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +// If using publish command, this is created from deploying the cnfbase.bicep resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { parent: publisher name: acrArtifactStoreName } -// Created by the az aosm definition publish command before the template is deployed +// The NFD Group is the parent resource under which all NFD versions will be created. +// If using publish command, this is created from deploying the cnfbase.bicep resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { parent: publisher name: nfDefinitionGroup } +// This will deploy an NFDV in 'Preview' state. It should be changed to 'Active' once it is finalised. resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' = { parent: nfdg name: nfDefinitionVersion location: location properties: { - // versionState should be changed to 'Active' once it is finalized. - versionState: 'Preview' {#- Note that all paths in bicep must be specified using the forward slash #} {#- (/) character even if running on Windows. #} {#- See https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules#local-file #} - deployParameters: string(loadJsonContent('schemas/deploymentParameters.json')) + deployParameters: string(loadJsonContent('{{deploy_parameters_file}}')) networkFunctionType: 'ContainerizedNetworkFunction' networkFunctionTemplate: { nfviType: 'AzureArcKubernetes' networkFunctionApplications: [ - {%- for configuration in nf_application_configurations %} + {%- for configuration in acr_nf_applications %} { artifactType: 'HelmPackage' name: '{{ configuration.name }}' dependsOnProfile: { - installDependsOn: {{ configuration.dependsOnProfile }} + installDependsOn: {{ configuration.depends_on_profile.install_depends_on }} + uninstallDependsOn: {{ configuration.depends_on_profile.uninstall_depends_on }} + updateDependsOn: {{ configuration.depends_on_profile.update_depends_on }} } artifactProfile: { artifactStore: { id: acrArtifactStore.id } helmArtifactProfile: { - helmPackageName: '{{ configuration.chartName }}' - helmPackageVersionRange: '{{ configuration.chartVersion }}' - registryValuesPaths: {{ configuration.registryValuesPaths }} - imagePullSecretsValuesPaths: {{ configuration.imagePullSecretsValuesPaths }} + helmPackageName: '{{ configuration.artifact_profile.helm_artifact_profile.helm_package_name }}' + helmPackageVersionRange: '{{ configuration.artifact_profile.helm_artifact_profile.helm_package_version_range }}' + registryValuesPaths: {{ configuration.artifact_profile.helm_artifact_profile.registry_values_paths }} + imagePullSecretsValuesPaths: {{ configuration.artifact_profile.helm_artifact_profile.image_pull_secrets_values_paths }} } } deployParametersMappingRuleProfile: { applicationEnablement: 'Enabled' helmMappingRuleProfile: { - releaseNamespace: '{{ configuration.releaseName }}' - releaseName: '{{ configuration.releaseName }}' - helmPackageVersion: '{{ configuration.chartVersion }}' - values: string(loadJsonContent('configMappings/{{ configuration.valueMappingsFile }}')) + releaseNamespace: '{{ configuration.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.release_namespace }}' + releaseName: '{{ configuration.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.release_name }}' + helmPackageVersion: '{{ configuration.deploy_parameters_mapping_rule_profile.helm_mapping_rule_profile.helm_package_version }}' + values: string(loadJsonContent('{{configuration.name}}-mappings.json')) } } } diff --git a/src/aosm/azext_aosm/common/templates/cnf/cnfhelmtemplateerrors.txt.j2 b/src/aosm/azext_aosm/common/templates/cnf/cnfhelmtemplateerrors.txt.j2 new file mode 100644 index 00000000000..cb6aa75498a --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/cnf/cnfhelmtemplateerrors.txt.j2 @@ -0,0 +1,14 @@ +Errors were found in the following Helm charts (see below for details). Please fix them and try again. + +{% for chart_name in errors.keys() %} +- {{ chart_name }} +{% endfor %} + +{% for chart_name, error_message in errors.items() %} +---------------------------------------- +Error in Helm chart: {{ chart_name }} +---------------------------------------- + +{{ error_message }} + +{% endfor %} \ No newline at end of file diff --git a/src/aosm/azext_aosm/common/templates/nsd/nf_template.bicep.j2 b/src/aosm/azext_aosm/common/templates/nsd/nf_template.bicep.j2 new file mode 100644 index 00000000000..75edcad8eba --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/nsd/nf_template.bicep.j2 @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Highly Confidential Material +// +// The template that the NSD invokes to create the Network Function from a published NFDV. + +@secure() +param configObject object + +var resourceGroupId = resourceGroup().id + +var identityObject = (configObject.managedIdentityId == '') ? { + type: 'SystemAssigned' +} : { + type: 'UserAssigned' + userAssignedIdentities: { + '${configObject.managedIdentityId}': {} + } +} + +var nfdvSymbolicName = '${configObject.publisherName}/${configObject.nfdgName}/${configObject.nfdv}' + +resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { + name: nfdvSymbolicName + scope: resourceGroup(configObject.publisherResourceGroup) +} + +resource nfResource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in configObject.deployParameters: { + name: '${configObject.nfdgName}${i}' + location: configObject.location + identity: identityObject + properties: { + networkFunctionDefinitionVersionResourceReference: { + id: nfdv.id + idType: 'Open' + } + nfviType: '{{nfvi_type}}' + nfviId: (configObject.customLocationId == '') ? resourceGroupId : configObject.customLocationId + allowSoftwareUpdate: true + configurationType: 'Open' + deploymentValues: string(values) + {%- if is_cnf %} + roleOverrideValues: [ + {%- for nf_app_name in nf_application_names %} + '{"name":"{{nf_app_name}}","deployParametersMappingRuleProfile":{"helmMappingRuleProfile":{"options":{"installOptions":{"injectArtifactStoreDetails":"true"},"upgradeOptions":{"injectArtifactStoreDetails":"true"}}}}}' + {%- endfor %} + ] + {%- endif %} + } +}] diff --git a/src/aosm/azext_aosm/common/templates/nsd/nsdartifactmanifest.bicep.j2 b/src/aosm/azext_aosm/common/templates/nsd/nsdartifactmanifest.bicep.j2 new file mode 100644 index 00000000000..c0d0f02ef85 --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/nsd/nsdartifactmanifest.bicep.j2 @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an Artifact Manifest for a NSD +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of the Artifact Manifest to create') +param acrManifestName string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the nsdbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +// If using publish command, this is created from deploying the nsdbase.bicep +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +// Artifact manifest from ARMTemplate and NF RET artifacts +resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: acrArtifactStore + name: acrManifestName + location: location + properties: { + artifacts: [ + {%- for artifact in acr_artifacts %} + { + artifactName: '{{ artifact.artifact_name }}' + artifactType: '{{ artifact.artifact_type }}' + artifactVersion: '{{ artifact.artifact_version }}' + } + {%- endfor %} + ] + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/common/templates/nsd/nsdbase.bicep b/src/aosm/azext_aosm/common/templates/nsd/nsdbase.bicep new file mode 100644 index 00000000000..64b07ec80b4 --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/nsd/nsdbase.bicep @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for an NSD +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of an Network Service Design Group') +param nsDesignGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The NSD Group is the parent resource under which all NSD versions will be created. +resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' = { + parent: publisher + name: nsDesignGroup + location: location +} diff --git a/src/aosm/azext_aosm/generate_nsd/templates/nsd_template.bicep.j2 b/src/aosm/azext_aosm/common/templates/nsd/nsddefinition.bicep.j2 similarity index 76% rename from src/aosm/azext_aosm/generate_nsd/templates/nsd_template.bicep.j2 rename to src/aosm/azext_aosm/common/templates/nsd/nsddefinition.bicep.j2 index 57d787c4803..fd001b4409e 100644 --- a/src/aosm/azext_aosm/generate_nsd/templates/nsd_template.bicep.j2 +++ b/src/aosm/azext_aosm/common/templates/nsd/nsddefinition.bicep.j2 @@ -19,19 +19,21 @@ param nfviSiteName string = '{{nfvi_site_name}}' // The publisher resource is the top level AOSM resource under which all other designer resources // are created. +// If using publish command, this is created from deploying the nsdbase.bicep resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { name: publisherName scope: resourceGroup() } // The artifact store is the resource in which all the artifacts required to deploy the NF are stored. -// The artifact store is created by the az aosm CLI before this template is deployed. +// If using publish command, this is created from deploying the nsdbase.bicep resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { parent: publisher name: acrArtifactStoreName } -// Created up-front, the NSD Group is the parent resource under which all NSD versions will be created. +// The NSD Group is the parent resource under which all NSD versions will be created. +// If using publish command, this is created from deploying the nsdbase.bicep resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' existing = { parent: publisher name: nsDesignGroup @@ -42,45 +44,46 @@ resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups // The operator will create a config group values object that will satisfy this schema. resource cgSchema 'Microsoft.Hybridnetwork/publishers/configurationGroupSchemas@2023-09-01' = { parent: publisher - name: '{{cg_schema_name}}' + name: '{{cgs_name}}' location: location properties: { - schemaDefinition: string(loadJsonContent('schemas/{{cg_schema_name}}.json')) + schemaDefinition: string(loadJsonContent('{{cgs_file}}')) } } + // The NSD version +// This will deploy an NSDV in 'Preview' state. It should be changed to 'Active' once it is finalised. resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions@2023-09-01' = { parent: nsdGroup name: nsDesignVersion location: location properties: { description: '{{nsdv_description}}' - // The version state can be Preview, Active or Deprecated. - // Once in an Active state, the NSDV becomes immutable. - versionState: 'Preview' // The `configurationgroupsSchemaReferences` field contains references to the schemas required to // be filled out to configure this NSD. configurationGroupSchemaReferences: { - {{cg_schema_name}}: { + {{cgs_name}}: { id: cgSchema.id } } // This details the NFVIs that should be available in the Site object created by the operator. nfvisFromSite: { - nfvi1: { - name: nfviSiteName - type: 'AzureCore' + {%- for nfvi in nfvi_types %} + nfvi{{loop.index}}: { + name: '${nfviSiteName}{{loop.index}}' + type: '{{ nfvi}}' } + {%- endfor %} } // This field lists the templates that will be deployed by AOSM and the config mappings // to the values in the CG schemas. resourceElementTemplates: [ -{%- for index in range(nf_count) %} +{%- for ret in nf_rets %} { - name: '{{ResourceElementName[index]}}' + name: '{{ret.name}}' // The type of resource element can be ArmResourceDefinition, ConfigurationDefinition or NetworkFunctionDefinition. - type: 'NetworkFunctionDefinition' + type: '{{ret.resource_element_type}}' // The configuration object may be different for different types of resource element. configuration: { // This field points AOSM at the artifact in the artifact store. @@ -88,13 +91,13 @@ resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngrou artifactStoreReference: { id: acrArtifactStore.id } - artifactName: '{{armTemplateNames[index]}}' - artifactVersion: '{{armTemplateVersion}}' + artifactName: '{{ret.configuration.artifact_profile.artifact_name}}' + artifactVersion: '{{ret.configuration.artifact_profile.artifact_version}}' } - templateType: 'ArmTemplate' + templateType: '{{ret.configuration.template_type}}' // The parameter values map values from the CG schema, to values required by the template // deployed by this resource element. - parameterValues: string(loadJsonContent('configMappings/{{configMappingFiles[index]}}')) + parameterValues: string(loadJsonContent('{{ret.name}}-mappings.json')) } dependsOnProfile: { installDependsOn: [] diff --git a/src/aosm/azext_aosm/common/templates/vnf/vnfartifactmanifest.bicep.j2 b/src/aosm/azext_aosm/common/templates/vnf/vnfartifactmanifest.bicep.j2 new file mode 100644 index 00000000000..daf9072180e --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/vnf/vnfartifactmanifest.bicep.j2 @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an Artifact Manifest for a VNF +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +{%- if sa_artifacts %} +@description('Name of an existing Storage Account-backed Artifact Store, deployed under the publisher.') +param saArtifactStoreName string +{%- endif %} +@description('Name of the manifest to deploy for the ACR-backed Artifact Store') +param acrManifestName string +{%- if sa_artifacts %} +@description('Name of the manifest to deploy for the Storage Account-backed Artifact Store') +param saManifestName string +{%- endif %} + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the vnfbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName +} + +// The artifact store is the resource in which all the artifacts (except VHD images) required to deploy the NF are stored. +// If using publish command, this is created from deploying the vnfbase.bicep +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +{%- if sa_artifacts %} +// The storage account is the resource in which the VHD images are stored. +// If using publish command, this is created from deploying the vnfbase.bicep +resource saArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: saArtifactStoreName +} +{%- endif %} + +resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: acrArtifactStore + name: acrManifestName + location: location + properties: { + artifacts: [ + {%- for artifact in acr_artifacts %} + { + artifactName: '{{ artifact.artifact_name }}' + artifactType: 'ArmTemplate' + artifactVersion: '{{ artifact.artifact_version }}' + } + {%- endfor %} + ] + } +} + +{%- if sa_artifacts %} +resource saArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: saArtifactStore + name: saManifestName + location: location + properties: { + artifacts: [ + {%- for artifact in sa_artifacts %} + { + artifactName: '{{ artifact.artifact_name }}' + artifactType: 'VhdImageFile' + artifactVersion: '{{ artifact.artifact_version }}' + } + {%- endfor %} + ] + } +} +{%- endif %} + diff --git a/src/aosm/azext_aosm/common/templates/vnf/vnfbase.bicep b/src/aosm/azext_aosm/common/templates/vnf/vnfbase.bicep new file mode 100644 index 00000000000..91d224d7373 --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/vnf/vnfbase.bicep @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for a VNF +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of a Storage Account-backed Artifact Store, deployed under the publisher.') +param saArtifactStoreName string +@description('Name of a Network Function Definition Group') +param nfDefinitionGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts (except VHD images) required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The storage account is the resource in which the VHD images are stored. +resource saArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: saArtifactStoreName + location: location + properties: { + storeType: 'AzureStorageAccount' + } +} + +// The NFD Group is the parent resource under which all NFD versions will be created. +resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' = { + parent: publisher + name: nfDefinitionGroup + location: location +} diff --git a/src/aosm/azext_aosm/common/templates/vnf/vnfdefinition.bicep.j2 b/src/aosm/azext_aosm/common/templates/vnf/vnfdefinition.bicep.j2 new file mode 100644 index 00000000000..5a7e47c2b9d --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/vnf/vnfdefinition.bicep.j2 @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an NF definition for a VNF +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +{%- if sa_nf_applications %} +@description('Name of an existing Storage Account-backed Artifact Store, deployed under the publisher.') +param saArtifactStoreName string +{%- endif %} +@description('Name of an existing Network Function Definition Group') +param nfDefinitionGroup string +@description('The version of the NFDV you want to deploy, in format A.B.C') +param nfDefinitionVersion string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the vnfbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName +} + +// The artifact store is the resource in which all the artifacts (except VHD images) required to deploy the NF are stored. +// If using publish command, this is created from deploying the vnfbase.bicep +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +{%- if sa_nf_applications %} +// The storage account is the resource in which the VHD images are stored. +// If using publish command, this is created from deploying the vnfbase.bicep +resource saArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: saArtifactStoreName +} +{%- endif %} + +// The NFD Group is the parent resource under which all NFD versions will be created. +// If using publish command, this is created from deploying the vnfbase.bicep +resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { + parent: publisher + name: nfDefinitionGroup +} + +// This will deploy an NFDV in 'Preview' state. It should be changed to 'Active' once it is finalised. +resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' = { + parent: nfdg + name: nfDefinitionVersion + location: location + properties: { + {#- Note that all paths in bicep must be specified using the forward slash #} + {#- (/) character even if running on Windows. #} + {#- See https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules#local-file #} + deployParameters: string(loadJsonContent('{{deploy_parameters_file}}')) + networkFunctionType: 'VirtualNetworkFunction' + networkFunctionTemplate: { + nfviType: '{{ nfvi_type }}' + networkFunctionApplications: [ + {%- for configuration in sa_nf_applications %} + { + artifactType: 'VhdImageFile' + name: '{{ configuration.artifact_profile.vhd_artifact_profile.vhd_name }}' + dependsOnProfile: null + artifactProfile: { + vhdArtifactProfile: { + vhdName: '{{ configuration.artifact_profile.vhd_artifact_profile.vhd_name }}' + vhdVersion: '{{ configuration.artifact_profile.vhd_artifact_profile.vhd_version }}' + } + artifactStore: { + id: saArtifactStore.id + } + } + // mapping deploy param vals to vals required by this network function application object + deployParametersMappingRuleProfile: { + vhdImageMappingRuleProfile: { + userConfiguration: string(loadJsonContent('{{vhd_parameters_file}}')) + } + applicationEnablement: '{{ configuration.deploy_parameters_mapping_rule_profile.application_enablement.value }}' + } + } + {%- endfor %} + {%- for configuration in acr_nf_applications %} + { + artifactType: 'ArmTemplate' + name: '{{ configuration.name }}' + dependsOnProfile: null + artifactProfile: { + templateArtifactProfile: { + templateName: '{{ configuration.artifact_profile.template_artifact_profile.template_name }}' + templateVersion: '{{ configuration.artifact_profile.template_artifact_profile.template_version }}' + } + artifactStore: { + id: acrArtifactStore.id + } + } + deployParametersMappingRuleProfile: { + templateMappingRuleProfile: { + templateParameters: string(loadJsonContent('{{ configuration.name }}-{{template_parameters_file}}')) + } + applicationEnablement: '{{ configuration.deploy_parameters_mapping_rule_profile.application_enablement.value }}' + } + } + {%- endfor %} + {%- for configuration in nexus_image_nf_applications %} + { + artifactType: 'ImageFile' + name: '{{ configuration.name }}' + dependsOnProfile: null + artifactProfile: { + imageArtifactProfile: { + imageName: '{{ configuration.artifact_profile.image_artifact_profile.image_name }}' + imageVersion: '{{ configuration.artifact_profile.image_artifact_profile.image_version }}' + } + artifactStore: { + id: acrArtifactStore.id + } + } + deployParametersMappingRuleProfile: { + imageMappingRuleProfile: { + userConfiguration: string(loadJsonContent('{{ configuration.name }}-{{image_parameters_file}}')) + } + applicationEnablement: '{{ configuration.deploy_parameters_mapping_rule_profile.application_enablement.value }}' + } + } + {%- endfor %} + ] + } + } +} diff --git a/src/aosm/azext_aosm/common/templates/vnf/vnfnexusbase.bicep b/src/aosm/azext_aosm/common/templates/vnf/vnfnexusbase.bicep new file mode 100644 index 00000000000..be16ebaf457 --- /dev/null +++ b/src/aosm/azext_aosm/common/templates/vnf/vnfnexusbase.bicep @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for a Nexus VNF +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of a Network Function Definition Group') +param nfDefinitionGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The NFD Group is the parent resource under which all NFD versions will be created. +resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' = { + parent: publisher + name: nfDefinitionGroup + location: location +} diff --git a/src/aosm/azext_aosm/common/utils.py b/src/aosm/azext_aosm/common/utils.py new file mode 100644 index 00000000000..00cbb42feb5 --- /dev/null +++ b/src/aosm/azext_aosm/common/utils.py @@ -0,0 +1,292 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +import re +import os +import shutil +import subprocess +import tarfile +import tempfile +from typing import Tuple +from time import sleep +from pathlib import Path +from jinja2 import StrictUndefined, Template + +from knack.log import get_logger +from knack.util import CLIError +from azure.cli.core.azclierror import BadRequestError, ClientRequestError + +from azext_aosm.common.constants import NEXUS_IMAGE_REGEX +from azext_aosm.common.exceptions import InvalidFileTypeError, MissingDependency + +logger = get_logger(__name__) + + +def convert_bicep_to_arm(bicep_template_path: Path) -> dict: + """ + Convert a bicep template into an ARM template. + + :param bicep_template_path: The path to the bicep template to be converted + :return: Output dictionary representation of the ARM template JSON. + """ + with tempfile.TemporaryDirectory() as tmpdir: + bicep_filename = bicep_template_path.name + arm_template_name = bicep_filename.replace(".bicep", ".json") + arm_path = Path(tmpdir) / arm_template_name + logger.debug( + "Converting bicep template %s to ARM.", + bicep_template_path, + ) + + try: + subprocess.run( + [ + str(shutil.which("az")), + "bicep", + "build", + "--file", + bicep_template_path, + "--outfile", + str(arm_path), + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + 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()) + + return arm_json + + +def render_bicep_contents_from_j2(template_path: Path, params): + """Write the definition bicep file from given template.""" + with open(template_path, "r", encoding="UTF-8") as f: + template: Template = Template( + f.read(), + undefined=StrictUndefined, + ) + + bicep_contents: str = template.render(params) + return bicep_contents + + +def get_template_path(definition_type: str, template_name: str) -> Path: + """Get the path to a template.""" + return ( + Path(__file__).parent.parent + / "common" + / "templates" + / definition_type + / template_name + ) + + +def extract_tarfile(file_path: Path, target_dir: Path) -> Path: + """ + Extracts the tar file to a temporary directory. + Args: + file_path: Path to the tar file. + Returns: + Path to the temporary directory. + """ + file_extension = file_path.suffix + + if file_extension in (".gz", ".tgz"): + with tarfile.open(file_path, "r:gz") as tar: + tar.extractall(path=target_dir) + elif file_extension == ".tar": + with tarfile.open(file_path, "r:") as tar: + tar.extractall(path=target_dir) + else: + raise InvalidFileTypeError( + f"ERROR: The helm package, '{file_path}', is not" + "a .tgz, .tar or .tar.gz file." + ) + + return Path(target_dir, os.listdir(target_dir)[0]) + + +def snake_case_to_camel_case(text): + """Converts snake case to camel case.""" + components = text.split("_") + return components[0] + "".join(x[0].upper() + x[1:] for x in components[1:]) + + +def check_tool_installed(tool_name: str) -> None: + """ + Check whether a tool such as docker or helm is installed. + + :param tool_name: name of the tool to check, e.g. docker + """ + if shutil.which(tool_name) is None: + raise MissingDependency(f"You must install {tool_name} to use this command.") + + +def call_subprocess_raise_output(cmd: list) -> str: + """ + Call a subprocess and raise a CLIError with the output if it fails. + + :param cmd: command to run, in list format + :raise CLIError: if the subprocess fails + """ + log_cmd = cmd.copy() + if "--password" in log_cmd: + # Do not log out passwords. + log_cmd[log_cmd.index("--password") + 1] = "[REDACTED]" + + try: + called_process = subprocess.run( + cmd, encoding="utf-8", capture_output=True, text=True, check=True + ) + logger.debug( + "Output from %s: %s. Error: %s", + log_cmd, + called_process.stdout, + called_process.stderr, + ) + + return called_process.stdout + except subprocess.CalledProcessError as 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("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 + + +def clean_registry_name(registry_name: str) -> str: + """Remove https:// from the registry name.""" + return registry_name.replace("https://", "") + + +def push_image_from_local_registry_to_acr( + target_acr: str, + target_image: str, + target_username: str, + target_password: str, + local_docker_image: str, +) -> None: + """ + Push image to target registry using docker push. Requires docker. + + :param target_acr: name of the target Azure Container registry + e.g. targetacr.azurecr.io + :type target_acr: str + :param target_image: name of the target image (namespace/repository:tag) + e.g. namespace/nginx:1.0.0 + :type target_image: str + :param target_username: username for the target ACR + :type target_username: str + :param target_password: password for the target ACR + :type target_password: str + :param local_docker_image: name and tag of the source image on local registry + e.g. uploadacr.azurecr.io/samples/nginx:stable + :type local_docker_image: str + """ + + target = f"{target_acr}/{target_image}" + logger.debug("Target ACR: %s", target) + + # To push the image to the target registry, we need to tag the source image + logger.info("Tagging source image %s as %s", local_docker_image, target) + tag_image_cmd = [ + str(shutil.which("docker")), + "tag", + local_docker_image, + target, + ] + call_subprocess_raise_output(tag_image_cmd) + + login_to_artifact_store_registry(target_acr, target_username, target_password) + + try: + logger.info("Pushing target image %s using docker push", target) + push_target_image_cmd = [ + str(shutil.which("docker")), + "push", + target, + ] + call_subprocess_raise_output(push_target_image_cmd) + except CLIError as error: + logger.debug(error, exc_info=True) + raise ClientRequestError( + f"Failed to push {local_docker_image} to {target_acr}." + ) from error + finally: + docker_logout_cmd = [ + str(shutil.which("docker")), + "logout", + target_acr, + ] + call_subprocess_raise_output(docker_logout_cmd) + + +def login_to_artifact_store_registry( + registry: str, username: str, password: str +) -> None: + """ + Log in to the registry using az acr login. + + :param registry: The registry to log in to + :param username: The username to use for logging in + :param password: The password to use for logging in + """ + logger.info("Logging into artifact store registry %s", registry) + # ACR login seems to work intermittently, so we retry on failure + retries = 0 + while True: + try: + target_acr_login_cmd = [ + str(shutil.which("az")), + "acr", + "login", + "--name", + registry, + "--username", + username, + "--password", + password, + ] + call_subprocess_raise_output(target_acr_login_cmd) + logger.debug("Logged in to %s", registry) + break + except CLIError as error: + if retries < 20: + logger.info("Retrying ACR login. Retries so far: %s", retries) + retries += 1 + sleep(3) + continue + logger.debug(error, exc_info=True) + + raise BadRequestError( + f"Failed to login to {registry} as {username}." + ) from error + + +def split_image_path(image) -> Tuple[str, str, str]: + """Split the image path into source acr registry, name and version.""" + (source_acr_registry, name_and_version) = image.split("/", 2) + (name, version) = name_and_version.split(":", 2) + return (source_acr_registry, name, version) + + +def is_valid_nexus_image_version(string): + """Check if image version is valid. + + This is based on validation in pez repo. + It requires the image version to be major.minor.patch, + but does not enforce full semver validation. + """ + return re.match(NEXUS_IMAGE_REGEX, string) is not None diff --git a/src/aosm/azext_aosm/generate_nsd/__init__.py b/src/aosm/azext_aosm/configuration_models/__init__.py similarity index 100% rename from src/aosm/azext_aosm/generate_nsd/__init__.py rename to src/aosm/azext_aosm/configuration_models/__init__.py diff --git a/src/aosm/azext_aosm/configuration_models/common_input.py b/src/aosm/azext_aosm/configuration_models/common_input.py new file mode 100644 index 00000000000..b01175ac384 --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/common_input.py @@ -0,0 +1,46 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +from dataclasses import dataclass, field + +from azure.cli.core.azclierror import ValidationError + + +@dataclass +class ArmTemplatePropertiesConfig: + """ARM template configuration.""" + + artifact_name: str = field( + default="", + metadata={"comment": "Name of the artifact. Used as internal reference only."}, + ) + version: str = field( + default="", metadata={"comment": "Version of the artifact in 1.1.1 format (three integers separated by dots)."} + ) + file_path: str = field( + default="", + metadata={ + "comment": ( + "File path (absolute or relative to this configuration file) of the artifact you wish to upload from " + "your local disk.\n" + "Use Linux slash (/) file separator even if running on Windows." + ) + }, + ) + + def validate(self): + """Validate the configuration.""" + if not self.artifact_name: + raise ValidationError("Artifact name must be set") + if not self.version: + raise ValidationError("Artifact version must be set") + if "." not in self.version or "-" in self.version: + raise ValidationError( + "Config validation error. ARM template artifact version should be in" + " format 1.1.1" + ) + if not self.file_path: + raise ValidationError("Artifact file path must be set") diff --git a/src/aosm/azext_aosm/configuration_models/common_parameters_config.py b/src/aosm/azext_aosm/configuration_models/common_parameters_config.py new file mode 100644 index 00000000000..5c65e291345 --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/common_parameters_config.py @@ -0,0 +1,58 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import annotations + +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. +@dataclass(frozen=True) +class BaseCommonParametersConfig(ABC): + """Base common parameters configuration.""" + + location: str + publisherName: str + publisherResourceGroupName: str + acrArtifactStoreName: str + acrManifestName: str + + +@dataclass(frozen=True) +class NFDCommonParametersConfig(BaseCommonParametersConfig): + """Common parameters configuration for NFs.""" + + nfDefinitionGroup: str + nfDefinitionVersion: str + + +@dataclass(frozen=True) +class CoreVNFCommonParametersConfig(NFDCommonParametersConfig): + """Common parameters configuration for VNFs.""" + + saArtifactStoreName: str + saManifestName: str + + +@dataclass(frozen=True) +class NexusVNFCommonParametersConfig(NFDCommonParametersConfig): + """Common parameters configuration for VNFs.""" + + +@dataclass(frozen=True) +class CNFCommonParametersConfig(NFDCommonParametersConfig): + """Common parameters configuration for VNFs.""" + + +@dataclass(frozen=True) +class NSDCommonParametersConfig(BaseCommonParametersConfig): + """Common parameters configuration for NSDs.""" + + nsDesignGroup: str + nsDesignVersion: str + nfviSiteName: str diff --git a/src/aosm/azext_aosm/configuration_models/onboarding_base_input_config.py b/src/aosm/azext_aosm/configuration_models/onboarding_base_input_config.py new file mode 100644 index 00000000000..3e5562314be --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/onboarding_base_input_config.py @@ -0,0 +1,61 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import annotations + +from abc import ABC +from dataclasses import dataclass, field + +from azure.cli.core.azclierror import ValidationError + + +@dataclass +class OnboardingBaseInputConfig(ABC): + """Base input configuration for onboarding commands.""" + + location: str = field( + default="", + metadata={ + "comment": "Azure location to use when creating resources e.g uksouth" + }, + ) + publisher_name: str = field( + default="", + metadata={ + "comment": ( + "Name of the Publisher resource you want your definition published to.\n" + "Will be created if it does not exist." + ) + }, + ) + publisher_resource_group_name: str = field( + default="", + metadata={ + "comment": ( + "Resource group for the Publisher resource.\n" + "Will be created if it does not exist." + ) + }, + ) + acr_artifact_store_name: str = field( + default="", + metadata={ + "comment": ( + "Name of the ACR Artifact Store resource.\n" + "Will be created if it does not exist." + ) + }, + ) + + 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("Artifact store name must be set") diff --git a/src/aosm/azext_aosm/configuration_models/onboarding_cnf_input_config.py b/src/aosm/azext_aosm/configuration_models/onboarding_cnf_input_config.py new file mode 100644 index 00000000000..1f76548b22d --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/onboarding_cnf_input_config.py @@ -0,0 +1,99 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from azure.cli.core.azclierror import ValidationError + +from azext_aosm.configuration_models.onboarding_nfd_base_input_config import ( + OnboardingNFDBaseInputConfig, +) + + +@dataclass +class HelmPackageConfig: + """Helm package configuration.""" + + name: str = field(default="", metadata={"comment": "The name of the Helm package."}) + path_to_chart: str = field( + default="", + metadata={ + "comment": ( + "The file path to the helm chart on the local disk, relative to the directory from which the " + "command is run.\n" + "Accepts .tgz, .tar or .tar.gz, or an unpacked directory. Use Linux slash (/) file separator " + "even if running on Windows." + ) + }, + ) + default_values: str | None = field( + default="", + metadata={ + "comment": ( + "The file path (absolute or relative to this configuration file) of YAML values file on the " + "local disk which will be used instead of the values.yaml file present in the helm chart.\n" + "Accepts .yaml or .yml. Use Linux slash (/) file separator even if running on Windows." + ) + }, + ) + # # NOTE: there is a story to reimplement this, so not deleting + # depends_on: list = field( + # default_factory=lambda: [], + # metadata={ + # "comment": ( + # "Names of the Helm packages this package depends on.\n" + # "Leave as an empty array if there are no dependencies." + # ) + # }, + # ) + + def validate(self): + """Validate the helm package configuration.""" + if not self.name: + raise ValidationError("nf_name must be set for your helm package") + if not self.path_to_chart: + raise ValidationError("path_to_chart must be set for your helm package") + + +@dataclass +class OnboardingCNFInputConfig(OnboardingNFDBaseInputConfig): + """Input configuration for onboarding CNFs.""" + + image_sources: list = field( + default_factory=list, + metadata={ + "comment": ( + "List of registries from which to pull the image(s).\n" + 'For example ["sourceacr.azurecr.io/test", "myacr2.azurecr.io", "ghcr.io/path"].\n' + "For non Azure Container Registries, ensure you have run a docker login command before running build.\n" + ) + }, + ) + + helm_packages: List[HelmPackageConfig] = field( + default_factory=lambda: [HelmPackageConfig()], + metadata={"comment": "List of Helm packages to be included in the CNF."}, + ) + + def validate(self): + """Validate the CNFconfiguration.""" + super().validate() + if not self.image_sources: + raise ValidationError("At least one image source must be included.") + if not self.helm_packages: + raise ValidationError("At least one Helm package must be included.") + for helm_package in self.helm_packages: + helm_package.validate() + + def __post_init__(self): + helm_list = [] + for helm_package in self.helm_packages: + if isinstance(helm_package, dict): + helm_list.append(HelmPackageConfig(**helm_package)) + else: + helm_list.append(helm_package) + self.helm_packages = helm_list diff --git a/src/aosm/azext_aosm/configuration_models/onboarding_nfd_base_input_config.py b/src/aosm/azext_aosm/configuration_models/onboarding_nfd_base_input_config.py new file mode 100644 index 00000000000..059efec470b --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/onboarding_nfd_base_input_config.py @@ -0,0 +1,55 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from dataclasses import dataclass, field + +from azure.cli.core.azclierror import ValidationError + +from azext_aosm.configuration_models.onboarding_base_input_config import ( + OnboardingBaseInputConfig, +) + + +@dataclass +class OnboardingNFDBaseInputConfig(OnboardingBaseInputConfig): + """Common input configuration for onboarding NFDs.""" + + nf_name: str = field( + default="", metadata={"comment": "Name of the network function."} + ) + version: str = field( + default="", + metadata={ + "comment": "Version of the network function definition in 1.1.1 format (three integers separated by dots)." + }, + ) + expose_all_parameters: bool = field( + default=False, + metadata={ + "comment": ( + "If set to true, all NFD configuration parameters are made available to the designer, including " + "optional parameters and those with defaults.\nIf not set or set to false, only required parameters " + "without defaults will be exposed." + ) + }, + ) + + @property + def acr_manifest_name(self) -> str: + """Return the ACR manifest name from the NFD name and version.""" + sanitized_nf_name = self.nf_name.lower().replace("_", "-") + return f"{sanitized_nf_name}-acr-manifest-{self.version.replace('.', '-')}" + + 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") + if "-" in self.version or "." not in self.version: + raise ValidationError( + "Config validation error. Version must be in format 1.1.1 (three integers separated by dots)." + ) diff --git a/src/aosm/azext_aosm/configuration_models/onboarding_nsd_input_config.py b/src/aosm/azext_aosm/configuration_models/onboarding_nsd_input_config.py new file mode 100644 index 00000000000..93efa44dd6e --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/onboarding_nsd_input_config.py @@ -0,0 +1,235 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import annotations + +from dataclasses import dataclass, field + +from azure.cli.core.azclierror import ValidationError + +from azext_aosm.configuration_models.common_input import ArmTemplatePropertiesConfig +from azext_aosm.configuration_models.onboarding_base_input_config import ( + OnboardingBaseInputConfig, +) + + +@dataclass +class NetworkFunctionPropertiesConfig: + """Network function object for NSDs.""" + + publisher: str = field( + default="", + metadata={"comment": "The name of the existing publisher for the NSD."}, + ) + publisher_resource_group: str = field( + default="", + metadata={"comment": "The resource group that the publisher is hosted in."}, + ) + name: str = field( + default="", + metadata={ + "comment": ( + "The name of the existing Network Function Definition Group" + " to deploy using this NSD.\n" + "This will be the same as the NF name if you published your NFDV using the CLI." + ) + }, + ) + version: str = field( + default="", + metadata={ + "comment": ( + "The version of the existing Network Function Definition to base this NSD on.\n" + "This NSD will be able to deploy any NFDV with deployment parameters" + " compatible with this version." + ) + }, + ) + publisher_offering_location: str = field( + default="", metadata={"comment": "The region that the NFDV is published to."} + ) + type: str = field( + default="", + metadata={ + "comment": "Type of Network Function. Valid values are 'cnf' or 'vnf'." + }, + ) + # multiple_instances: str = field( + # default="", + # metadata={ + # "comment": ( + # "Set to true or false. Whether the NSD should allow arbitrary numbers of this type of NF. " + # "If false only a single instance will be allowed. " + # "Only supported on VNFs, must be set to false on CNFs." + # ) + # }, + # ) + + def validate(self): + """Validate the configuration.""" + if not self.publisher: + raise ValidationError("publisher must be set for your network function") + if not self.publisher_resource_group: + raise ValidationError( + "publisher_resource_group must be set for your network function" + ) + if not self.name: + raise ValidationError("name must be set for your network function") + if not self.version: + raise ValidationError("version must be set for your network function") + if not self.publisher_offering_location: + raise ValidationError( + "publisher_offering_location must be set for your network function" + ) + + if not self.type: + raise ValidationError("type must be set for your network function") + if self.type.lower() not in ["cnf", "vnf"]: + raise ValidationError("type must either be cnf or vnf") + + # if not self.multiple_instances: + # raise ValidationError( + # "multiple_instances must be set for your network function" + # ) + # if self.multiple_instances.lower() not in ["false", "true"]: + # raise ValidationError("multiple_instances must be either true or false") + + +@dataclass +class NetworkFunctionConfig: + """Network function object for NSDs.""" + + resource_element_type: str = field( + default="NF", + metadata={"comment": "Type of Resource Element. Either NF or ArmTemplate"}, + ) + properties: NetworkFunctionPropertiesConfig | dict = field( + default_factory=NetworkFunctionPropertiesConfig, + ) + + def validate(self): + """Validate the configuration.""" + # TODO: fix logic here as no RET returns invalid config file error not this + if not self.resource_element_type: + raise ValidationError("You must specify the type of Resource Element.") + # TODO: fix logic here as no properties doesnt error + if not self.properties: + raise ValidationError( + "You must specify the properties of the Resource Element." + ) + self.properties.validate() + + def __post_init__(self): + if self.properties and isinstance(self.properties, dict): + self.properties = NetworkFunctionPropertiesConfig(**self.properties) + + +@dataclass +class ArmTemplateConfig: + """Configuration for RET.""" + + resource_element_type: str = field( + default="ArmTemplate", + metadata={"comment": "Type of Resource Element. Either NF or ArmTemplate"}, + ) + properties: ArmTemplatePropertiesConfig | dict = field( + default_factory=ArmTemplatePropertiesConfig, + metadata={"comment": "Properties of the Resource Element."}, + ) + + def validate(self): + """Validate the configuration.""" + if not self.resource_element_type: + raise ValidationError(("You must specify the type of Resource Element.")) + if not self.properties: + raise ValidationError( + ("You must specify the properties of the Resource Element.") + ) + self.properties.validate() + + def __post_init__(self): + if self.properties and isinstance(self.properties, dict): + self.properties = ArmTemplatePropertiesConfig(**self.properties) + + +@dataclass +class OnboardingNSDInputConfig(OnboardingBaseInputConfig): + """Input configuration for onboarding NSDs.""" + + nsd_name: str = field( + default="", + metadata={ + "comment": ( + "Network Service Design (NSD) name. " + "This is the collection of Network Service Design Versions. Will be created if it does not exist." + ) + }, + ) + nsd_version: str = field( + default="", + metadata={ + "comment": "Version of the NSD to be created. This should be in the format A.B.C" + }, + ) + nsdv_description: str | None = field( + default="", + metadata={ + "comment": "Optional. Description of the Network Service Design Version (NSDV)." + }, + ) + + # # TODO: Add detailed comment for this + resource_element_templates: "list[NetworkFunctionConfig | ArmTemplateConfig]" = ( + field( + default_factory=lambda: [NetworkFunctionConfig(), ArmTemplateConfig()], + metadata={ + "comment": ( + "List of Resource Element Templates (RETs).\n" + "There must be at least one NF RET.\n" + "ArmTemplate RETs are optional. Delete if not required." + ) + }, + ) + ) + + @property + def acr_manifest_name(self) -> str: + """Return the ACR manifest name from the NSD name and version.""" + sanitized_nsd_name = self.nsd_name.lower().replace("_", "-") + return f"{sanitized_nsd_name}-nsd-manifest-{self.nsd_version.replace('.', '-')}" + + def validate(self): + """Validate the configuration.""" + super().validate() + if not self.resource_element_templates: + raise ValidationError( + ("At least one Resource Element Template must be included.") + ) + + if not self.nsd_name: + raise ValidationError("nsd_name must be set") + if not self.nsd_version: + raise ValidationError("nsd_version must be set") + + # Validate each RET + for configuration in self.resource_element_templates: + configuration.validate() + + def __post_init__(self): + RET_list = [] + for resource_element in self.resource_element_templates: + if resource_element and isinstance(resource_element, dict): + if resource_element["resource_element_type"] == "ArmTemplate": + RET_list.append(ArmTemplateConfig(**resource_element)) + elif resource_element["resource_element_type"] == "NF": + RET_list.append(NetworkFunctionConfig(**resource_element)) + else: + # TODO: propagate this error up, currently it just raises invalid config file from base handler + raise ValidationError( + ("You must specify the type of Resource Element.") + ) + else: + RET_list.append(resource_element) + self.resource_element_templates = RET_list diff --git a/src/aosm/azext_aosm/configuration_models/onboarding_vnf_input_config.py b/src/aosm/azext_aosm/configuration_models/onboarding_vnf_input_config.py new file mode 100644 index 00000000000..295affe969f --- /dev/null +++ b/src/aosm/azext_aosm/configuration_models/onboarding_vnf_input_config.py @@ -0,0 +1,220 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import annotations +from dataclasses import dataclass, field +from typing import List + +from azure.cli.core.azclierror import ValidationError + +from azext_aosm.configuration_models.common_input import ArmTemplatePropertiesConfig +from azext_aosm.configuration_models.onboarding_nfd_base_input_config import ( + OnboardingNFDBaseInputConfig, +) +from azext_aosm.common.utils import split_image_path, is_valid_nexus_image_version + + +@dataclass +class VhdImageConfig: + """Configuration for a VHD image.""" + + artifact_name: str = field( + default="", + metadata={ + "comment": "Optional. Name of the artifact. Name will be generated if not supplied." + }, + ) + version: str = field( + default="", + metadata={ + "comment": "Version of the artifact in A-B-C format. Note the '-' (dash) not '.' (dot)." + }, + ) + file_path: str = field( + default="", + metadata={ + "comment": ( + "Supply either file_path or blob_sas_url, not both.\n" + "File path (absolute or relative to this configuration file) of the artifact you wish to upload from " + "your local disk.\n" + "Leave as empty string if not required. Use Linux slash (/) file separator even if running on Windows." + ) + }, + ) + blob_sas_url: str = field( + default="", + metadata={ + "comment": ( + "Supply either file_path or blob_sas_url, not both.\nSAS URL of the blob artifact you wish to copy to " + "your Artifact Store.\n" + "Leave as empty string if not required. Use Linux slash (/) file separator even if running on Windows." + ) + }, + ) + image_disk_size_GB: str | None = field( + default="", + metadata={ + "comment": ( + "Optional. Specifies the size of empty data disks in gigabytes.\n" + "This value cannot be larger than 1023 GB. Delete if not required." + ) + }, + ) + image_hyper_v_generation: str | None = field( + default="", + metadata={ + "comment": ( + "Optional. Specifies the HyperVGenerationType of the VirtualMachine created from the image.\n" + "Valid values are V1 and V2. V1 is the default if not specified. Delete if not required." + ) + }, + ) + image_api_version: str | None = field( + default="", + metadata={ + "comment": ( + "Optional. The ARM API version used to create the Microsoft.Compute/images resource.\n" + "Delete if not required." + ) + }, + ) + + def validate(self): + """Validate the configuration.""" + if not self.version: + raise ValidationError("Artifact version must be set") + if "-" not in self.version or "." in self.version: + raise ValidationError( + "Config validation error. VHD image artifact version should be in" + " format A-B-C" + ) + if self.blob_sas_url and self.file_path: + raise ValidationError( + "Only one of file_path or blob_sas_url may be set for vhd." + ) + if not (self.blob_sas_url or self.file_path): + raise ValidationError( + "One of file_path or sas_blob_url must be set for vhd." + ) + + +@dataclass +class OnboardingCoreVNFInputConfig(OnboardingNFDBaseInputConfig): + """Input configuration for onboarding VNFs.""" + + blob_artifact_store_name: str = field( + default="", + metadata={ + "comment": ( + "Optional. Name of the storage account Artifact Store resource. \n" + "Will be created if it does not exist (with a default name if none is supplied)." + ) + }, + ) + + arm_templates: List[ArmTemplatePropertiesConfig] = field( + default_factory=lambda: [ArmTemplatePropertiesConfig()], + metadata={ + "comment": "ARM template configuration. The ARM templates given here would deploy a VM if run. They will " + "be used to generate the VNF." + }, + ) + + vhd: VhdImageConfig = field( + default_factory=VhdImageConfig, + metadata={"comment": "VHD image configuration."}, + ) + + def __post_init__(self): + arm_list = [] + for arm_template in self.arm_templates: + if arm_template and isinstance(arm_template, dict): + arm_list.append(ArmTemplatePropertiesConfig(**arm_template)) + else: + arm_list.append(arm_template) + self.arm_templates = arm_list + + if self.vhd and isinstance(self.vhd, dict): + self.vhd = VhdImageConfig(**self.vhd) + + sanitized_nf_name = self.nf_name.lower().replace("_", "-") + if not self.blob_artifact_store_name: + self.blob_artifact_store_name = sanitized_nf_name + "-sa" + + @property + def sa_manifest_name(self) -> str: + """Return the Storage account manifest name from the NFD name and version.""" + sanitized_nf_name = self.nf_name.lower().replace("_", "-") + return f"{sanitized_nf_name}-sa-manifest-{self.version.replace('.', '-')}" + + def validate(self): + """Validate the configuration.""" + super().validate() + if not self.arm_templates: + raise ValidationError("arm_template must be set") + if not self.vhd: + raise ValidationError("vhd must be set") + if not self.arm_templates: + raise ValidationError("You must include at least one arm template") + for arm_template in self.arm_templates: + arm_template.validate() + self.vhd.validate() + + +@dataclass +class OnboardingNexusVNFInputConfig(OnboardingNFDBaseInputConfig): + """Input configuration for onboarding VNFs.""" + + arm_templates: List[ArmTemplatePropertiesConfig] = field( + default_factory=lambda: [ArmTemplatePropertiesConfig()], + metadata={ + "comment": ( + "ARM template configuration. The ARM templates given here would deploy a VM if run." + "They will be used to generate the VNF." + ) + }, + ) + + images: List[str] = field( + default_factory=lambda: [], + metadata={ + "comment": ( + "List of images to be pulled from the acr registry.\n" + "You must provide the source acr registry, the image name and the version.\n" + "For example: 'sourceacr.azurecr.io/imagename:imageversion'." + ) + }, + ) + + def __post_init__(self): + arm_list = [] + for arm_template in self.arm_templates: + if arm_template and isinstance(arm_template, dict): + arm_list.append(ArmTemplatePropertiesConfig(**arm_template)) + else: + arm_list.append(arm_template) + self.arm_templates = arm_list + + @property + def sa_manifest_name(self) -> str: + """Return the Storage account manifest name from the NFD name and version.""" + sanitized_nf_name = self.nf_name.lower().replace("_", "-") + return f"{sanitized_nf_name}-sa-manifest-{self.version.replace('.', '-')}" + + def validate(self): + """Validate the configuration.""" + super().validate() + if not self.arm_templates: + raise ValidationError("arm_template must be set") + if not self.images: + raise ValidationError("You must include at least one image") + for image in self.images: + (_, _, version) = split_image_path(image) + if not is_valid_nexus_image_version(version): + raise ValidationError(f"{image} has invalid version '{version}'.\n" + "Allowed format is major.minor.patch") + if not self.arm_templates: + raise ValidationError("You must include at least one arm template") + for arm_template in self.arm_templates: + arm_template.validate() diff --git a/src/aosm/azext_aosm/custom.py b/src/aosm/azext_aosm/custom.py index 008b2210a21..17d89aef878 100644 --- a/src/aosm/azext_aosm/custom.py +++ b/src/aosm/azext_aosm/custom.py @@ -2,524 +2,136 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +from __future__ import annotations -import json -import os -import shutil -from dataclasses import asdict from pathlib import Path from typing import Optional -from azure.cli.core.azclierror import ( - CLIInternalError, - InvalidArgumentValueError, - UnclassifiedUserFault, -) +from azure.cli.core.azclierror import UnrecognizedArgumentError from azure.cli.core.commands import AzCliCommand -from azure.core import exceptions as azure_exceptions -from knack.log import get_logger -from azext_aosm._client_factory import cf_features, cf_resources -from azext_aosm._configuration import ( - CNFConfiguration, - Configuration, - NFConfiguration, - NSConfiguration, - VNFConfiguration, - get_configuration, -) -from azext_aosm.delete.delete import ResourceDeleter -from azext_aosm.deploy.deploy_with_arm import DeployerViaArm -from azext_aosm.generate_nfd.cnf_nfd_generator import CnfNfdGenerator -from azext_aosm.generate_nfd.nfd_generator_base import NFDGenerator -from azext_aosm.generate_nfd.vnf_nfd_generator import VnfNfdGenerator -from azext_aosm.generate_nsd.nsd_generator import NSDGenerator -from azext_aosm.util.constants import ( - AOSM_FEATURE_NAMESPACE, - AOSM_REQUIRED_FEATURES, - CNF, - NSD, - VNF, - DeployableResourceTypes, - SkipSteps, -) -from azext_aosm.util.management_clients import ApiClients -from azext_aosm.vendored_sdks import HybridNetworkManagementClient +from azext_aosm.cli_handlers.onboarding_cnf_handler import OnboardingCNFCLIHandler +from azext_aosm.cli_handlers.onboarding_vnf_handler import OnboardingVNFCLIHandler +from azext_aosm.cli_handlers.onboarding_core_vnf_handler import OnboardingCoreVNFCLIHandler +from azext_aosm.cli_handlers.onboarding_nexus_vnf_handler import OnboardingNexusVNFCLIHandler +from azext_aosm.cli_handlers.onboarding_nsd_handler import OnboardingNSDCLIHandler +from azext_aosm.common.command_context import CommandContext +from azext_aosm.common.constants import ALL_PARAMETERS_FILE_NAME, CNF, VNF, VNF_NEXUS -logger = get_logger(__name__) - -def build_definition( - definition_type: str, - config_file: str, - order_params: bool = False, - interactive: bool = False, - force: bool = False, -): - """ - Build a definition. - - :param definition_type: VNF or CNF - :param config_file: path to the file - :param order_params: VNF definition_type only - ignored for CNF. Order - deploymentParameters schema and configMappings to have the parameters without - default values at the top. - :param interactive: Whether to prompt for input when creating deploy parameters - mapping files - :param force: force the build even if the design has already been built - """ - - # Read the config from the given file - config = _get_config_from_file( - config_file=config_file, configuration_type=definition_type - ) - assert isinstance(config, NFConfiguration) - - # Generate the NFD and the artifact manifest. - _generate_nfd( - definition_type=definition_type, - config=config, - order_params=order_params, - interactive=interactive, - force=force, - ) - - -def generate_definition_config(definition_type: str, output_file: str = "input.json"): - """ - Generate an example config file for building a definition. - - :param definition_type: CNF, VNF - :param output_file: path to output config file, defaults to "input.json" - """ - config: Configuration +def onboard_nfd_generate_config(definition_type: str, output_file: str | None): + """Generate config file for onboarding NFs.""" + # Declare types explicitly + handler: OnboardingCNFCLIHandler | OnboardingVNFCLIHandler | OnboardingNexusVNFCLIHandler if definition_type == CNF: - config = CNFConfiguration.helptext() + handler = OnboardingCNFCLIHandler() elif definition_type == VNF: - config = VNFConfiguration.helptext() + handler = OnboardingCoreVNFCLIHandler() + elif definition_type == VNF_NEXUS: + handler = OnboardingNexusVNFCLIHandler() else: - raise ValueError("definition_type must be CNF or VNF") - - _generate_config(configuration=config, output_file=output_file) - - -def _get_config_from_file(config_file: str, configuration_type: str) -> Configuration: - """ - Read input config file JSON and turn it into a Configuration object. - - :param config_file: path to the file - :param configuration_type: VNF, CNF or NSD - :returns: The Configuration object - """ + raise UnrecognizedArgumentError( + "Invalid definition type, valid values are 'cnf', 'vnf' or 'vnfnexus'") + handler.generate_config(output_file) - if not os.path.exists(config_file): - raise InvalidArgumentValueError( - f"Config file {config_file} not found. Please specify a valid config file" - " path." - ) - config = get_configuration(configuration_type, config_file) - return config - - -def _generate_nfd( - definition_type: str, - config: NFConfiguration, - order_params: bool, - interactive: bool, - force: bool = False, +def onboard_nfd_build( + definition_type: str, config_file: Path, skip: Optional[str] = None ): - """Generate a Network Function Definition for the given type and config.""" - nfd_generator: NFDGenerator - if definition_type == VNF: - assert isinstance(config, VNFConfiguration) - nfd_generator = VnfNfdGenerator(config, order_params, interactive) - elif definition_type == CNF: - assert isinstance(config, CNFConfiguration) - nfd_generator = CnfNfdGenerator(config, interactive) + """Build the NF definition.""" + # Declare types explicitly + handler: OnboardingCNFCLIHandler | OnboardingVNFCLIHandler | OnboardingNexusVNFCLIHandler + if definition_type == CNF: + handler = OnboardingCNFCLIHandler(config_file_path=Path(config_file), skip=skip) + elif definition_type == VNF: + handler = OnboardingCoreVNFCLIHandler(config_file_path=Path(config_file)) + elif definition_type == VNF_NEXUS: + handler = OnboardingNexusVNFCLIHandler(config_file_path=Path(config_file)) else: - raise CLIInternalError( - "Generate NFD called for unrecognised definition_type. Only VNF and CNF" - " have been implemented." - ) - if nfd_generator.nfd_bicep_path: - if not force: - carry_on = input( - f"The {nfd_generator.nfd_bicep_path.parent} directory already exists -" - " delete it and continue? (y/n)" - ) - if carry_on != "y": - raise UnclassifiedUserFault("User aborted!") + raise UnrecognizedArgumentError( + "Invalid definition type, valid values are 'cnf', 'vnf' or 'vnfnexus'") + handler.build() - shutil.rmtree(nfd_generator.nfd_bicep_path.parent) - nfd_generator.generate_nfd() - -def _check_features_enabled(cmd: AzCliCommand): - """ - Check that the required Azure features are enabled on the subscription. - - :param cmd: The AzCLICommand object for the original command that was run, we use - this to retrieve the CLI context in order to get the features client for access - to the features API. - """ - features_client = cf_features(cmd.cli_ctx) - # Check that the required features are enabled on the subscription - for feature in AOSM_REQUIRED_FEATURES: - try: - feature_result = features_client.features.get( - resource_provider_namespace=AOSM_FEATURE_NAMESPACE, - feature_name=feature, - ) - if ( - not feature_result - or not feature_result.properties.state == "Registered" - ): - # We don't want to log the name of the feature to the user as it is - # a hidden feature. We do want to log it to the debug log though. - logger.debug( - "Feature %s is not registered on the subscription.", feature - ) - raise CLIInternalError( - "Your Azure subscription has not been fully onboarded to AOSM. " - "Please see the AOSM onboarding documentation for more information." - ) - except azure_exceptions.ResourceNotFoundError as rerr: - # If the feature is not found, it is not registered, but also something has - # gone wrong with the CLI code and onboarding instructions. - logger.debug( - "Feature not found error - Azure doesn't recognise the feature %s." - "This indicates a coding error or error with the AOSM onboarding " - "instructions.", - feature, - ) - logger.debug(rerr) - raise CLIInternalError( - "CLI encountered an error checking that your " - "subscription has been onboarded to AOSM. Please raise an issue against" - " the CLI." - ) from rerr - - -def publish_definition( +def onboard_nfd_publish( cmd: AzCliCommand, - client: HybridNetworkManagementClient, - definition_type, - config_file, - definition_file: Optional[str] = None, - parameters_json_file: Optional[str] = None, - manifest_file: Optional[str] = None, - manifest_params_file: Optional[str] = None, - skip: Optional[SkipSteps] = None, + definition_type: str, + build_output_folder: Path, no_subscription_permissions: bool = False, ): - """ - Publish a generated definition. - - :param cmd: The AzCLICommand object for the command that was run, we use this to - find the CLI context (from which, for example, subscription id and - credentials can be found, and other clients can be generated.) - :param client: The AOSM client. This is created in _client_factory.py and passed - in by commands.py - we could alternatively just use cf_aosm as - we use cf_resources, but other extensions seem to pass a client - around like this. - :type client: HybridNetworkManagementClient - :param definition_type: VNF or CNF - :param config_file: Path to the config file for the NFDV - :param definition_file: Optional path to a bicep template to deploy, in case the - user wants to edit the built NFDV template. If omitted, the default built NFDV - template will be used. - :param parameters_json_file: Optional path to a parameters file for the bicep file, - in case the user wants to edit the built NFDV template. If omitted, parameters - from config will be turned into parameters for the bicep file - :param manifest_file: Optional path to an override bicep template to deploy - manifests - :param manifest_params_file: Optional path to an override bicep parameters file for - manifest parameters - :param skip: options to skip, either publish bicep or upload artifacts - :param no_subscription_permissions: - CNF definition_type publish only - ignored for VNF. Causes the image - artifact copy from a source ACR to be done via docker pull and push, - rather than `az acr import`. This is slower but does not require - Contributor (or importImage action) and AcrPush permissions on the publisher - subscription. It requires Docker to be installed. - """ - # Check that the required features are enabled on the subscription - _check_features_enabled(cmd) - - print("Publishing definition.") - api_clients = ApiClients( - aosm_client=client, - resource_client=cf_resources(cmd.cli_ctx), - ) - - config = _get_config_from_file( - config_file=config_file, configuration_type=definition_type - ) - - deployer = DeployerViaArm( - api_clients, - resource_type=definition_type, - config=config, - bicep_path=definition_file, - parameters_json_file=parameters_json_file, - manifest_bicep_path=manifest_file, - manifest_params_file=manifest_params_file, - skip=skip, + """Publish the NF definition.""" + command_context = CommandContext( cli_ctx=cmd.cli_ctx, - use_manifest_permissions=no_subscription_permissions, - ) - deployer.deploy_nfd_from_bicep() - - -def delete_published_definition( - cmd: AzCliCommand, - client: HybridNetworkManagementClient, - definition_type, - config_file, - clean=False, - force=False, -): - """ - Delete a published definition. - - :param cmd: The AzCLICommand object for the command that was run, we use this to - find the CLI context (from which, for example, subscription id and - credentials can be found, and other clients can be generated.) - :param client: The AOSM client. This is created in _client_factory.py and passed - in by commands.py - we could alternatively just use cf_aosm as - we use cf_resources, but other extensions seem to pass a client - around like this. - :param definition_type: CNF or VNF - :param config_file: Path to the config file - :param clean: if True, will delete the NFDG, artifact stores and publisher too. - Defaults to False. Only works if no resources have those as a parent. Use - with care. - :param force: if True, will not prompt for confirmation before deleting the resources. - """ - # Check that the required features are enabled on the subscription - _check_features_enabled(cmd) - - config = _get_config_from_file( - config_file=config_file, configuration_type=definition_type - ) - - api_clients = ApiClients( - aosm_client=client, resource_client=cf_resources(cmd.cli_ctx) + cli_options={ + "no_subscription_permissions": no_subscription_permissions, + "definition_folder": Path(build_output_folder), + }, ) - - delly = ResourceDeleter(api_clients, config, cmd.cli_ctx) - if definition_type == VNF: - delly.delete_nfd(clean=clean, force=force) - elif definition_type == CNF: - delly.delete_nfd(clean=clean, force=force) + # Declare types explicitly + handler: OnboardingCNFCLIHandler | OnboardingVNFCLIHandler + if definition_type == CNF: + handler = OnboardingCNFCLIHandler( + all_deploy_params_file_path=Path(build_output_folder, ALL_PARAMETERS_FILE_NAME)) + elif definition_type == VNF: + handler = OnboardingCoreVNFCLIHandler( + all_deploy_params_file_path=Path(build_output_folder, ALL_PARAMETERS_FILE_NAME)) + elif definition_type == VNF_NEXUS: + handler = OnboardingNexusVNFCLIHandler( + all_deploy_params_file_path=Path(build_output_folder, ALL_PARAMETERS_FILE_NAME)) else: - raise ValueError( - "Definition type must be either 'vnf' or 'cnf'. Definition type" - f" {definition_type} is not recognised." - ) - - -def generate_design_config(output_file: str = "input.json"): - """ - Generate an example config file for building a NSD. - - :param output_file: path to output config file, defaults to "input.json" - :type output_file: str, optional - """ - _generate_config(NSConfiguration.helptext(), output_file) - - -def _generate_config(configuration: Configuration, output_file: str = "input.json"): - """ - Generic generate config function for NFDs and NSDs. - - :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" - """ - # 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(configuration) - config.pop("config_file") + raise UnrecognizedArgumentError( + "Invalid definition type, valid values are 'cnf', 'vnf' or 'vnfnexus'") + handler.publish(command_context=command_context) - config_as_dict = json.dumps(config, indent=4) - if Path(output_file).exists(): - carry_on = input( - f"The file {output_file} already exists - do you want to overwrite it?" - " (y/n)" - ) - if carry_on != "y": - raise UnclassifiedUserFault("User aborted!") +# def onboard_nfd_delete(cmd: AzCliCommand, definition_type: str, config_file: str): +# """Delete the NF definition.""" +# command_context = CommandContext(cmd.cli_ctx) +# if definition_type == "cnf": +# handler = OnboardingCNFCLIHandler(config_file) +# handler.delete(command_context=command_context) +# elif definition_type == "vnf": +# handler = OnboardingVNFCLIHandler(config_file) +# handler.delete(command_context=command_context) +# else: +# raise UnrecognizedArgumentError("Invalid definition type") - with open(output_file, "w", encoding="utf-8") as f: - f.write(config_as_dict) - 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 - ) +def onboard_nsd_generate_config(output_file: str | None): + """Generate config file for onboarding NSD.""" + handler = OnboardingNSDCLIHandler() + handler.generate_config(output_file) -def build_design( - cmd: AzCliCommand, - client: HybridNetworkManagementClient, - config_file: str, - force: bool = False, -): - """ - Build a Network Service Design. - - :param cmd: The AzCLICommand object for the command that was run, we use this to - find the CLI context (from which, for example, subscription id and - credentials can be found, and other clients can be generated.) - :param client: The AOSM client. This is created in _client_factory.py and passed - in by commands.py - we could alternatively just use cf_aosm as - we use cf_resources, but other extensions seem to pass a client - around like this. - :type client: HybridNetworkManagementClient - :param config_file: path to the file - :param force: force the build, even if the design has already been built - """ - - api_clients = ApiClients( - aosm_client=client, resource_client=cf_resources(cmd.cli_ctx) - ) - # Read the config from the given file - config = _get_config_from_file(config_file=config_file, configuration_type=NSD) - assert isinstance(config, NSConfiguration) - config.validate() - - # Generate the NSD and the artifact manifest. - # This function should not be taking deploy parameters - _generate_nsd( - config=config, - api_clients=api_clients, - force=force, - ) +def onboard_nsd_build(config_file: Path, cmd: AzCliCommand): + """Build the NSD definition.""" + command_context = CommandContext(cli_ctx=cmd.cli_ctx) + handler = OnboardingNSDCLIHandler(config_file_path=Path(config_file), + aosm_client=command_context.aosm_client) + handler.build() -def delete_published_design( +def onboard_nsd_publish( cmd: AzCliCommand, - client: HybridNetworkManagementClient, - config_file, - clean=False, - force=False, -): - """ - Delete a published NSD. - - :param cmd: The AzCLICommand object for the command that was run, we use this to - find the CLI context (from which, for example, subscription id and - credentials can be found, and other clients can be generated.) - :param client: The AOSM client. This is created in _client_factory.py and passed - in by commands.py - we could alternatively just use cf_aosm as - we use cf_resources, but other extensions seem to pass a client - around like this. - :param config_file: Path to the config file - :param clean: if True, will delete the NSD, artifact stores and publisher too. - Defaults to False. Only works if no resources have those as a parent. - Use with care. - :param clean: if True, will delete the NSD on top of the other resources. - :param force: if True, will not prompt for confirmation before deleting the resources. - """ - # Check that the required features are enabled on the subscription - _check_features_enabled(cmd) - - config = _get_config_from_file(config_file=config_file, configuration_type=NSD) - - api_clients = ApiClients( - aosm_client=client, resource_client=cf_resources(cmd.cli_ctx) - ) - - destroyer = ResourceDeleter(api_clients, config, cmd.cli_ctx) - destroyer.delete_nsd(clean=clean, force=force) - - -def publish_design( - cmd: AzCliCommand, - client: HybridNetworkManagementClient, - config_file, - design_file: Optional[str] = None, - parameters_json_file: Optional[str] = None, - manifest_file: Optional[str] = None, - manifest_params_file: Optional[str] = None, - skip: Optional[SkipSteps] = None, + build_output_folder: Path, + no_subscription_permissions: bool = False, ): - """ - Publish a generated design. - - :param cmd: The AzCLICommand object for the command that was run, we use this to - find the CLI context (from which, for example, subscription id and - credentials can be found, and other clients can be generated.) - :param client: The AOSM client. This is created in _client_factory.py and passed - in by commands.py - we could alternatively just use cf_aosm as - we use cf_resources, but other extensions seem to pass a client - around like this. - :type client: HybridNetworkManagementClient - :param config_file: Path to the config file for the NSDV - :param design_file: Optional path to an override bicep template to deploy the NSDV. - :param parameters_json_file: Optional path to a parameters file for the bicep file, - in case the user wants to edit the built NSDV template. If - omitted, parameters from config will be turned into parameters - for the bicep file - :param manifest_file: Optional path to an override bicep template to deploy - manifests - :param manifest_params_file: Optional path to an override bicep parameters - file for manifest parameters - :param skip: options to skip, either publish bicep or upload artifacts - """ - # Check that the required features are enabled on the subscription - _check_features_enabled(cmd) - - print("Publishing design.") - api_clients = ApiClients( - aosm_client=client, resource_client=cf_resources(cmd.cli_ctx) - ) - - config = _get_config_from_file(config_file=config_file, configuration_type=NSD) - assert isinstance(config, NSConfiguration) - config.validate() - - deployer = DeployerViaArm( - api_clients, - resource_type=DeployableResourceTypes.NSD, - config=config, - bicep_path=design_file, - parameters_json_file=parameters_json_file, - manifest_bicep_path=manifest_file, - manifest_params_file=manifest_params_file, - skip=skip, + """Publish the NF definition.""" + command_context = CommandContext( cli_ctx=cmd.cli_ctx, + cli_options={ + "no_subscription_permissions": no_subscription_permissions, + "definition_folder": Path(build_output_folder), + }, ) + handler = OnboardingNSDCLIHandler( + all_deploy_params_file_path=Path(build_output_folder, ALL_PARAMETERS_FILE_NAME) + ) + handler.publish(command_context=command_context) - deployer.deploy_nsd_from_bicep() - - -def _generate_nsd( - config: NSConfiguration, api_clients: ApiClients, force: bool = False -): - """Generate a Network Service Design for the given config.""" - if config: - nsd_generator = NSDGenerator(config=config, api_clients=api_clients) - else: - raise CLIInternalError("Generate NSD called without a config file") - - if os.path.exists(config.output_directory_for_build): - if not force: - carry_on = input( - f"The folder {config.output_directory_for_build} already exists - delete it" - " and continue? (y/n)" - ) - if carry_on != "y": - raise UnclassifiedUserFault("User aborted! ") - - shutil.rmtree(config.output_directory_for_build) - nsd_generator.generate_nsd() +# def onboard_nsd_delete(cmd: AzCliCommand, config_file: str): +# """Delete the NSD definition.""" +# command_context = CommandContext(cmd.cli_ctx) +# handler = OnboardingNSDCLIHandler(config_file) +# handler.delete(command_context=command_context) diff --git a/src/aosm/azext_aosm/util/__init__.py b/src/aosm/azext_aosm/definition_folder/__init__.py similarity index 100% rename from src/aosm/azext_aosm/util/__init__.py rename to src/aosm/azext_aosm/definition_folder/__init__.py diff --git a/src/aosm/azext_aosm/definition_folder/builder/__init__.py b/src/aosm/azext_aosm/definition_folder/builder/__init__.py new file mode 100644 index 00000000000..9ccaff6c1b8 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- diff --git a/src/aosm/azext_aosm/definition_folder/builder/artifact_builder.py b/src/aosm/azext_aosm/definition_folder/builder/artifact_builder.py new file mode 100644 index 00000000000..38b71813751 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/artifact_builder.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from pathlib import Path +from typing import List + +from knack.log import get_logger + +from azext_aosm.common.artifact import BaseArtifact + +from .base_builder import BaseDefinitionElementBuilder + +logger = get_logger(__name__) + + +class ArtifactDefinitionElementBuilder(BaseDefinitionElementBuilder): + """Artifact builder""" + + artifacts: List[BaseArtifact] + + def __init__( + self, + path: Path, + artifacts: List[BaseArtifact], + only_delete_on_clean: bool = False, + ): + super().__init__(path, only_delete_on_clean) + self.artifacts = artifacts + + def write(self): + """Write the definition element to disk.""" + self.path.mkdir(exist_ok=True) + artifacts_list = [] + for artifact in self.artifacts: + artifact_dict = artifact.to_dict() + logger.debug( + "Writing artifact %s as: %s", artifact.artifact_name, artifact_dict + ) + artifacts_list.append(artifact_dict) + (self.path / "artifacts.json").write_text(json.dumps(artifacts_list, indent=4)) + self._write_supporting_files() diff --git a/src/aosm/azext_aosm/definition_folder/builder/base_builder.py b/src/aosm/azext_aosm/definition_folder/builder/base_builder.py new file mode 100644 index 00000000000..49041573ac5 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/base_builder.py @@ -0,0 +1,36 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from abc import ABC, abstractmethod +from pathlib import Path +from typing import List + +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder + + +class BaseDefinitionElementBuilder(ABC): + """Base element definition builder.""" + + path: Path + supporting_files: List[LocalFileBuilder] + only_delete_on_clean: bool + + def __init__(self, path: Path, only_delete_on_clean: bool = False): + self.path = path + self.supporting_files = [] + self.only_delete_on_clean = only_delete_on_clean + + def add_supporting_file(self, supporting_file: LocalFileBuilder): + """Add a supporting file to the element.""" + self.supporting_files.append(supporting_file) + + def _write_supporting_files(self): + """Write supporting files to disk.""" + for supporting_file in self.supporting_files: + supporting_file.write() + + @abstractmethod + def write(self): + return NotImplementedError diff --git a/src/aosm/azext_aosm/definition_folder/builder/bicep_builder.py b/src/aosm/azext_aosm/definition_folder/builder/bicep_builder.py new file mode 100644 index 00000000000..19b125a6414 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/bicep_builder.py @@ -0,0 +1,29 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from pathlib import Path + +from azext_aosm.definition_folder.builder.base_builder import ( + BaseDefinitionElementBuilder, +) + + +class BicepDefinitionElementBuilder(BaseDefinitionElementBuilder): + """Bicep definition element builder.""" + + bicep_content: str + + def __init__( + self, path: Path, bicep_content: str, only_delete_on_clean: bool = False + ): + super().__init__(path, only_delete_on_clean) + self.bicep_content = bicep_content + + def write(self): + """Write the definition element to disk.""" + self.path.mkdir(exist_ok=True) + (self.path / "deploy.bicep").write_text(self.bicep_content) + + self._write_supporting_files() diff --git a/src/aosm/azext_aosm/definition_folder/builder/definition_folder_builder.py b/src/aosm/azext_aosm/definition_folder/builder/definition_folder_builder.py new file mode 100644 index 00000000000..f2e1b3182b3 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/definition_folder_builder.py @@ -0,0 +1,68 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import shutil +from pathlib import Path +from typing import List + +from azure.cli.core.azclierror import UnclassifiedUserFault + +from azext_aosm.definition_folder.builder.base_builder import ( + BaseDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) +from azext_aosm.definition_folder.builder.json_builder import ( + JSONDefinitionElementBuilder, +) + + +class DefinitionFolderBuilder: + """Builds and writes out a definition folder for an NFD or NSD.""" + + path: Path + elements: List[BaseDefinitionElementBuilder] + + def __init__(self, path: Path): + self.path = path + self.elements = [] + + def add_element(self, element: BaseDefinitionElementBuilder): + """Add an element to the definition folder.""" + self.elements.append(element) + + def write(self): + """Write the definition folder.""" + self._check_for_overwrite() + if self.path.exists(): + shutil.rmtree(self.path) + self.path.mkdir() + for element in self.elements: + element.write() + index_json = [] + for element in self.elements: + if not isinstance(element, JSONDefinitionElementBuilder): + index_json.append( + { + "name": element.path.name, + "type": "bicep" + if isinstance(element, BicepDefinitionElementBuilder) + else "artifact", + "only_delete_on_clean": element.only_delete_on_clean, + } + ) + (self.path / "index.json").write_text(json.dumps(index_json, indent=4)) + # TODO: Write some readme file + + def _check_for_overwrite(self): + if self.path.exists(): + carry_on = input( + f"The output folder {self.path.name} already exists in this location - do you want to overwrite it?" + " (y/n)" + ) + if carry_on != "y": + raise UnclassifiedUserFault("User aborted!") diff --git a/src/aosm/azext_aosm/definition_folder/builder/json_builder.py b/src/aosm/azext_aosm/definition_folder/builder/json_builder.py new file mode 100644 index 00000000000..2ab0fa24151 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/json_builder.py @@ -0,0 +1,27 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from pathlib import Path + +from azext_aosm.common.constants import ALL_PARAMETERS_FILE_NAME +from azext_aosm.definition_folder.builder.base_builder import ( + BaseDefinitionElementBuilder, +) + + +class JSONDefinitionElementBuilder(BaseDefinitionElementBuilder): + """JSON definition element builder.""" + + json_content: str + + def __init__( + self, path: Path, json_content: str, only_delete_on_clean: bool = False + ): + super().__init__(path, only_delete_on_clean) + self.json_content = json_content + + def write(self): + """Write the definition element to disk.""" + self.path.mkdir(exist_ok=True) + (self.path / ALL_PARAMETERS_FILE_NAME).write_text(self.json_content) diff --git a/src/aosm/azext_aosm/definition_folder/builder/local_file_builder.py b/src/aosm/azext_aosm/definition_folder/builder/local_file_builder.py new file mode 100644 index 00000000000..bb9c0cad736 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/builder/local_file_builder.py @@ -0,0 +1,23 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from pathlib import Path + + +# pylint: disable=too-few-public-methods +class LocalFileBuilder: + """Writes locally generated files to disk.""" + + path: Path + file_content: str + + def __init__(self, path: Path, file_content: str): + """Initialize a new instance of the LocalFileBuilder class.""" + self.path = path + self.file_content = file_content + + def write(self): + """Write the file to disk.""" + self.path.write_text(self.file_content) diff --git a/src/aosm/azext_aosm/definition_folder/reader/__init__.py b/src/aosm/azext_aosm/definition_folder/reader/__init__.py new file mode 100644 index 00000000000..9ccaff6c1b8 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/reader/__init__.py @@ -0,0 +1,5 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ----------------------------------------------------------------------------- diff --git a/src/aosm/azext_aosm/definition_folder/reader/artifact_definition.py b/src/aosm/azext_aosm/definition_folder/reader/artifact_definition.py new file mode 100644 index 00000000000..2b86cb50e8a --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/reader/artifact_definition.py @@ -0,0 +1,67 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from pathlib import Path +from typing import cast, Type + +from knack.log import get_logger + +from azext_aosm.common.artifact import ARTIFACT_TYPE_TO_CLASS, BaseArtifact +from azext_aosm.common.command_context import CommandContext +from azext_aosm.configuration_models.common_parameters_config import ( + BaseCommonParametersConfig, +) +from azext_aosm.definition_folder.reader.base_definition import BaseDefinitionElement + +logger = get_logger(__name__) + + +class ArtifactDefinitionElement(BaseDefinitionElement): + """Definition for Artifact""" # TODO: Is this actually an artifact manifest? + + def __init__(self, path: Path, only_delete_on_clean: bool): + super().__init__(path, only_delete_on_clean) + logger.debug("ArtifactDefinitionElement path: %s", path) + artifact_list = json.loads((path / "artifacts.json").read_text()) + self.artifacts = [ + self.create_artifact_object(artifact) for artifact in artifact_list + ] + + @staticmethod + def create_artifact_object(artifact: dict) -> BaseArtifact: + """ + 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. + """ + if "type" not in artifact or artifact["type"] not in ARTIFACT_TYPE_TO_CLASS: + raise ValueError( + f"Artifact type is missing or invalid for artifact {artifact}" + ) + + # Give mypy a hint for the artifact type + artifact_class = cast( + Type[BaseArtifact], ARTIFACT_TYPE_TO_CLASS[artifact["type"]] + ) + return artifact_class.from_dict(artifact) + + def deploy( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Deploy the element.""" + for artifact in self.artifacts: + logger.info( + "Deploying artifact %s of type %s", + artifact.artifact_name, + type(artifact), + ) + artifact.upload(config=config, command_context=command_context) + + def delete( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Delete the element.""" + # TODO: Implement? + raise NotImplementedError diff --git a/src/aosm/azext_aosm/definition_folder/reader/base_definition.py b/src/aosm/azext_aosm/definition_folder/reader/base_definition.py new file mode 100644 index 00000000000..c6c49de6f10 --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/reader/base_definition.py @@ -0,0 +1,34 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from abc import ABC, abstractmethod +from pathlib import Path + +from azext_aosm.common.command_context import CommandContext +from azext_aosm.configuration_models.common_parameters_config import ( + BaseCommonParametersConfig, +) + + +class BaseDefinitionElement(ABC): + """Base element definition.""" + + def __init__(self, path: Path, only_delete_on_clean: bool): + self.path = path + self.only_delete_on_clean = only_delete_on_clean + + @abstractmethod + def deploy( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Deploy the element.""" + return NotImplementedError + + @abstractmethod + def delete( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Delete the element.""" + return NotImplementedError diff --git a/src/aosm/azext_aosm/definition_folder/reader/bicep_definition.py b/src/aosm/azext_aosm/definition_folder/reader/bicep_definition.py new file mode 100644 index 00000000000..f3078d2595f --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/reader/bicep_definition.py @@ -0,0 +1,230 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import time +from dataclasses import asdict +from typing import Any, Dict + +from azure.cli.core import AzCli +from azure.cli.core.azclierror import AzCLIError +from azure.cli.core.commands import LongRunningOperation +from azure.core import exceptions as azure_exceptions +from azure.mgmt.resource import ResourceManagementClient +from azure.mgmt.resource.resources.models import DeploymentExtended +from knack.log import get_logger +from azext_aosm.common.command_context import CommandContext +from azext_aosm.common.utils import convert_bicep_to_arm +from azext_aosm.configuration_models.common_parameters_config import \ + BaseCommonParametersConfig, CoreVNFCommonParametersConfig +from azext_aosm.definition_folder.reader.base_definition import \ + BaseDefinitionElement +from azext_aosm.common.constants import ManifestsExist + +logger = get_logger(__name__) + + +class BicepDefinitionElement(BaseDefinitionElement): + """Bicep definition""" + + @staticmethod + def _validate_and_deploy_arm_template( + cli_ctx: AzCli, + template: Any, + parameters: Dict[Any, Any], + resource_group: str, + resource_client: ResourceManagementClient, + ) -> Any: + """ + Validate and deploy an individual ARM template. + + This ARM template will be created in the resource group passed in. + + :param template: The JSON contents of the template to deploy + :param parameters: The JSON contents of the parameters file + :param resource_group: The name of the resource group that has been deployed + + :return: Output dictionary from the bicep template. + :raise RuntimeError if validation or deploy fails + """ + # Add a timestamp to the deployment name to ensure it is unique + current_time = int(time.time()) + deployment_name = f"AOSM_CLI_deployment_{current_time}" + + # Validation is automatically re-attempted in live runs, but not in test + # playback, causing them to fail. This explicitly re-attempts validation to + # ensure the tests pass + validation_res = None + for validation_attempt in range(2): + try: + validation = resource_client.deployments.begin_validate( + resource_group_name=resource_group, + deployment_name=deployment_name, + parameters={ + "properties": { + "mode": "Incremental", + "template": template, + "parameters": parameters, + } + }, + ) + validation_res = LongRunningOperation( + cli_ctx, "Validating ARM template..." + )(validation) + break + except Exception: # pylint: disable=broad-except + if validation_attempt == 1: + raise + + if not validation_res or validation_res.error: + raise RuntimeError(f"Validation of template {template} failed.") + + # Validation succeeded so proceed with deployment + poller = resource_client.deployments.begin_create_or_update( + resource_group_name=resource_group, + deployment_name=deployment_name, + parameters={ + "properties": { + "mode": "Incremental", + "template": template, + "parameters": parameters, + } + }, + ) + + # Wait for the deployment to complete and get the outputs + deployment: DeploymentExtended = LongRunningOperation( + cli_ctx, "Deploying ARM template" + )(poller) + + if deployment.properties is None: + raise RuntimeError("The deployment has no properties.\nAborting") + + if deployment.properties.provisioning_state != "Succeeded": + raise RuntimeError( + "Deploy of template to resource group" + f" {resource_group} proceeded but the provisioning" + f" state returned is {deployment.properties.provisioning_state}." + "\nAborting" + ) + + return deployment.properties.outputs + + @staticmethod + def _artifact_manifests_exist( + config: BaseCommonParametersConfig, command_context: CommandContext + ) -> ManifestsExist: + """ + + Returns True if all required manifests exist, False if none do, and raises an + AzCLIError if some but not all exist. + + Current code only allows one manifest for ACR, and one manifest for SA (if applicable), + so that's all we check for. + """ + try: + command_context.aosm_client.artifact_manifests.get( + resource_group_name=config.publisherResourceGroupName, + publisher_name=config.publisherName, + artifact_store_name=config.acrArtifactStoreName, + artifact_manifest_name=config.acrManifestName, + ) + acr_manifest_exists = True + except azure_exceptions.ResourceNotFoundError: + acr_manifest_exists = False + # TODO: test config type change works + if isinstance(config, CoreVNFCommonParametersConfig): + try: + command_context.aosm_client.artifact_manifests.get( + resource_group_name=config.publisherResourceGroupName, + publisher_name=config.publisherName, + artifact_store_name=config.saArtifactStoreName, + artifact_manifest_name=config.saManifestName, + ) + sa_manifest_exists = True + except azure_exceptions.ResourceNotFoundError: + sa_manifest_exists = False + + if acr_manifest_exists != sa_manifest_exists: + return ManifestsExist.SOME + + if acr_manifest_exists: + return ManifestsExist.ALL + + return ManifestsExist.NONE + + def deploy( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Deploy the element.""" + # TODO: Deploying base takes about 4 minutes, even if everything is already deployed. + # We should have a check to see if it's already deployed and skip it if so. + # The following can be used to speed up testing by skipping base deploy: TODO: remove this + # if self.path.name == "base": + # print("Temporarily skip base for debugging") + # return + + # artifact manifests return an error if it already exists, so they need special handling. + # Currently, _only_ manifests are special, but if we need to add any more custom code, + # breaking this into a separate class (like we do for artifacts) is probably the right + # thing to do. + if self.path.name == "artifactManifest": + manifests_exist = self._artifact_manifests_exist( + config=config, command_context=command_context + ) + if manifests_exist == ManifestsExist.ALL: # pylint: disable=no-else-return + # The manifest(s) already exist so nothing else to do for this template + logger.info("Artifact manifest(s) already exist; skipping deployment.") + return + elif manifests_exist == ManifestsExist.SOME: + # We don't know why we're in this state, and the safest thing to do is to delete + # the NFDV/NSDV and start again, but we shouldn't do this ourselves. + raise AzCLIError( + "Unexpected state: A subset of artifact manifest exists; expected all or " + "none so cannot proceed.\n" + "Please delete the NFDV or NSDV (as appropriate) using the " + "`az aosm nfd delete` or `az aosm nsd delete` command." + ) + else: + assert manifests_exist == ManifestsExist.NONE + # If none of the manifests exist, we can just go ahead and deploy the template + # as normal. + + logger.info( + "Converting bicep to ARM for '%s' template. This can take a few seconds.", + self.path.name, + ) + arm_json = convert_bicep_to_arm(self.path / "deploy.bicep") + logger.info("Deploying ARM template for %s", self.path.name) + + # TODO: handle creating the resource group if it doesn't exist + + # Create the deploy parameters with only the parameters needed by this template + parameters_in_template = arm_json["parameters"] + parameters = { + k: {"value": v} + for (k, v) in asdict(config).items() + if k in parameters_in_template + } + logger.debug("All parameters provided by user: %s", config) + logger.debug( + "Parameters required by %s in built ARM template:%s ", + self.path.name, + parameters_in_template, + ) + logger.debug("Filtered parameters: %s", parameters) + + self._validate_and_deploy_arm_template( + cli_ctx=command_context.cli_ctx, + template=arm_json, + parameters=parameters, + resource_group=config.publisherResourceGroupName, + resource_client=command_context.resources_client, + ) + + def delete( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Delete the element.""" + # TODO: Implement. + raise NotImplementedError diff --git a/src/aosm/azext_aosm/definition_folder/reader/definition_folder.py b/src/aosm/azext_aosm/definition_folder/reader/definition_folder.py new file mode 100644 index 00000000000..feee63f4b4b --- /dev/null +++ b/src/aosm/azext_aosm/definition_folder/reader/definition_folder.py @@ -0,0 +1,111 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +from pathlib import Path +from typing import Any, Dict, List + +from knack.log import get_logger +from azure.mgmt.resource.resources.models import ResourceGroup +from azext_aosm.common.command_context import CommandContext +from azext_aosm.configuration_models.common_parameters_config import ( + BaseCommonParametersConfig, +) +from azext_aosm.definition_folder.reader.artifact_definition import ( + ArtifactDefinitionElement, +) +from azext_aosm.definition_folder.reader.base_definition import BaseDefinitionElement +from azext_aosm.definition_folder.reader.bicep_definition import BicepDefinitionElement + +logger = get_logger(__name__) + + +class DefinitionFolder: + """Represents a definition folder for an NFD or NSD.""" + + def __init__(self, path: Path): + self.path = path + try: + index = self._parse_index_file((path / "index.json").read_text()) + except Exception as e: + raise ValueError(f"Error parsing index file - {e}") + self.elements: List[BaseDefinitionElement] = [] + for element in index: + if element["type"] == "bicep": + self.elements.append( + BicepDefinitionElement( + element["path"], element["only_delete_on_clean"] + ) + ) + elif element["type"] == "artifact": + self.elements.append( + ArtifactDefinitionElement( + element["path"], element["only_delete_on_clean"] + ) + ) + + def _parse_index_file(self, file_content: str) -> List[Dict[str, Any]]: + """Read the index file. Return a list of dicts containing path, type, only_delete_on_clean""" + json_content = json.loads(file_content) + parsed_elements = [] + for element in json_content: + if "name" not in element: + raise ValueError("Index file element is missing name") + if "type" not in element: + raise ValueError( + f"Index file element {element['name']} is missing type" + ) + if "only_delete_on_clean" not in element: + element["only_delete_on_clean"] = False + elif not isinstance(element["only_delete_on_clean"], bool): + raise ValueError( + f"Index file element {element['name']} only_delete_on_clean should be a boolean" + ) + parsed_elements.append( + { + "path": self.path / element["name"], + "type": element["type"], + "only_delete_on_clean": element["only_delete_on_clean"], + } + ) + return parsed_elements + + def _create_or_confirm_existence_of_resource_group(self, config, command_context): + """Ensure resource group exists before deploying of elements begins. + + Using ResourceManagementClient: + - Check for existence of resource group specified in allDeployParameters.json. + - Create resource group if doesn't exist. + """ + resources_client = command_context.resources_client + if not resources_client.resource_groups.check_existence(config.publisherResourceGroupName): + rg_params = ResourceGroup(location=config.location) + resources_client.resource_groups.create_or_update( + resource_group_name=config.publisherResourceGroupName, + parameters=rg_params + ) + + def deploy( + self, config: BaseCommonParametersConfig, command_context: CommandContext + ): + """Deploy the resources defined in the folder.""" + self._create_or_confirm_existence_of_resource_group(config, command_context) + for element in self.elements: + logger.info( + "Deploying definition element %s of type %s", + element.path, + type(element), + ) + element.deploy(config=config, command_context=command_context) + + def delete( + self, + config: BaseCommonParametersConfig, + command_context: CommandContext, + clean: bool = False, + ): + """Delete the definition folder.""" + for element in reversed(self.elements): + if clean or not element.only_delete_on_clean: + element.delete(config=config, command_context=command_context) diff --git a/src/aosm/azext_aosm/delete/delete.py b/src/aosm/azext_aosm/delete/delete.py deleted file mode 100644 index 561ce51e04e..00000000000 --- a/src/aosm/azext_aosm/delete/delete.py +++ /dev/null @@ -1,345 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains class for deploying generated definitions using the Python SDK.""" -from time import sleep -from typing import Optional - -from azure.cli.core.commands import LongRunningOperation -from azure.core.exceptions import ResourceExistsError -from azext_aosm._configuration import ( - Configuration, - NFConfiguration, - NSConfiguration, - VNFConfiguration, -) -from azext_aosm.util.management_clients import ApiClients -from azext_aosm.util.utils import input_ack -from knack.log import get_logger - -logger = get_logger(__name__) - - -class ResourceDeleter: - def __init__( - self, - api_clients: ApiClients, - config: Configuration, - cli_ctx: Optional[object] = None, - ) -> None: - """ - Initializes a new instance of the Deployer class. - - :param aosm_client: The client to use for managing AOSM resources. - :type aosm_client: HybridNetworkManagementClient - :param resource_client: The client to use for managing Azure resources. - :type resource_client: ResourceManagementClient - """ - logger.debug("Create ARM/Bicep Deployer") - self.api_clients = api_clients - self.config = config - self.cli_ctx = cli_ctx - - def delete_nfd(self, clean: bool = False, force: bool = False) -> None: - """ - Delete the NFDV and manifests. If they don't exist it still reports them as deleted. - - :param clean: Delete the NFDG, artifact stores and publisher too. Defaults to False. - Use with care. - """ - assert isinstance(self.config, NFConfiguration) - - if not force: - if clean: - print( - "Are you sure you want to delete all resources associated with NFD" - f" {self.config.nf_name} including the artifact stores and publisher" - f" {self.config.publisher_name}?" - ) - logger.warning( - "This command will fail if other NFD versions exist in the NFD group." - ) - logger.warning( - "Only do this if you are SURE you are not sharing the publisher and" - " artifact stores with other NFDs" - ) - print("There is no undo. Type the publisher name to confirm.") - if not input_ack(self.config.publisher_name.lower(), "Confirm delete:"): - print("Not proceeding with delete") - return - else: - print( - "Are you sure you want to delete the NFD Version" - f" {self.config.version} and associated manifests from group" - f" {self.config.nfdg_name} and publisher {self.config.publisher_name}?" - ) - print("There is no undo. Type 'delete' to confirm") - if not input_ack("delete", "Confirm delete:"): - print("Not proceeding with delete") - return - - self.delete_nfdv() - - if isinstance(self.config, VNFConfiguration): - self.delete_artifact_manifest("sa") - self.delete_artifact_manifest("acr") - - if clean: - logger.info("Delete called for all resources.") - self.delete_nfdg() - self.delete_artifact_store("acr") - if isinstance(self.config, VNFConfiguration): - self.delete_artifact_store("sa") - self.delete_publisher() - - def delete_nsd(self, clean: bool = False, force: bool = False) -> None: - """ - Delete the NSDV and manifests. - - If they don't exist it still reports them as deleted. - """ - assert isinstance(self.config, NSConfiguration) - - if not force: - print( - "Are you sure you want to delete the NSD Version" - f" {self.config.nsd_version}, the associated manifests" - f" {self.config.acr_manifest_names} and configuration group schema" - f" {self.config.cg_schema_name}?" - ) - if clean: - print( - f"Because of the --clean flag, the NSD {self.config.nsd_name} will also be deleted." - ) - print("There is no undo. Type 'delete' to confirm") - if not input_ack("delete", "Confirm delete:"): - print("Not proceeding with delete") - return - - self.delete_nsdv() - self.delete_artifact_manifest("acr") - self.delete_config_group_schema() - if clean: - self.delete_nsdg() - - def delete_nfdv(self): - assert isinstance(self.config, NFConfiguration) - message = ( - f"Delete NFDV {self.config.version} from group {self.config.nfdg_name} and" - f" publisher {self.config.publisher_name}" - ) - logger.debug(message) - print(message) - try: - poller = self.api_clients.aosm_client.network_function_definition_versions.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - network_function_definition_group_name=self.config.nfdg_name, - network_function_definition_version_name=self.config.version, - ) - LongRunningOperation(self.cli_ctx, "Deleting NFDV...")(poller) - logger.info("Deleted NFDV.") - except Exception: - logger.error( - "Failed to delete NFDV %s from group %s", - self.config.version, - self.config.nfdg_name, - ) - raise - - def delete_nsdv(self): - assert isinstance(self.config, NSConfiguration) - message = ( - f"Delete NSDV {self.config.nsd_version} from group" - f" {self.config.nsd_name} and publisher {self.config.publisher_name}" - ) - logger.debug(message) - print(message) - try: - poller = self.api_clients.aosm_client.network_service_design_versions.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - network_service_design_group_name=self.config.nsd_name, - network_service_design_version_name=self.config.nsd_version, - ) - LongRunningOperation(self.cli_ctx, "Deleting NSDV...")(poller) - logger.info("Deleted NSDV.") - except Exception: - logger.error( - "Failed to delete NSDV %s from group %s", - self.config.nsd_version, - self.config.nsd_name, - ) - raise - - def delete_artifact_manifest(self, store_type: str) -> None: - """ - _summary_ - - :param store_type: "sa" or "acr" - :raises CLIInternalError: If called with any other store type :raises - Exception if delete throws an exception - """ - if store_type == "sa": - assert isinstance(self.config, VNFConfiguration) - store_name = self.config.blob_artifact_store_name - manifest_names = [self.config.sa_manifest_name] - elif store_type == "acr": - store_name = self.config.acr_artifact_store_name - manifest_names = self.config.acr_manifest_names - else: - from azure.cli.core.azclierror import CLIInternalError - - raise CLIInternalError( - "Delete artifact manifest called for invalid store type. Valid types" - " are sa and acr." - ) - - for manifest_name in manifest_names: - message = f"Delete Artifact manifest {manifest_name} from artifact store {store_name}" - logger.debug(message) - print(message) - try: - poller = self.api_clients.aosm_client.artifact_manifests.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - artifact_store_name=store_name, - artifact_manifest_name=manifest_name, - ) - LongRunningOperation(self.cli_ctx, "Deleting Artifact manifest...")( - poller - ) # noqa: E501 - logger.info("Deleted Artifact Manifest") - except Exception: - logger.error( - "Failed to delete Artifact manifest %s from artifact store %s", - manifest_name, - store_name, - ) - raise - - def delete_nsdg(self) -> None: - """Delete the NSD.""" - assert isinstance(self.config, NSConfiguration) - message = f"Delete NSD {self.config.nsd_name}" - logger.debug(message) - print(message) - try: - poller = ( - self.api_clients.aosm_client.network_service_design_groups.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - network_service_design_group_name=self.config.nsd_name, - ) - ) - LongRunningOperation(self.cli_ctx, "Deleting NSD...")(poller) - logger.info("Deleted NSD") - except Exception: - logger.error("Failed to delete NSD.") - raise - - def delete_nfdg(self) -> None: - """Delete the NFDG.""" - assert isinstance(self.config, NFConfiguration) - message = f"Delete NFD Group {self.config.nfdg_name}" - logger.debug(message) - print(message) - try: - poller = self.api_clients.aosm_client.network_function_definition_groups.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - network_function_definition_group_name=self.config.nfdg_name, - ) - LongRunningOperation(self.cli_ctx, "Deleting NFD Group...")(poller) - logger.info("Deleted NFD Group") - except Exception: - logger.error("Failed to delete NFDG.") - raise - - def delete_artifact_store(self, store_type: str) -> None: - """Delete an artifact store - :param store_type: "sa" or "acr" - :raises CLIInternalError: If called with any other store type - :raises Exception if delete throws an exception.""" - if store_type == "sa": - assert isinstance(self.config, VNFConfiguration) - store_name = self.config.blob_artifact_store_name - elif store_type == "acr": - store_name = self.config.acr_artifact_store_name - else: - from azure.cli.core.azclierror import CLIInternalError - - raise CLIInternalError( - "Delete artifact store called for invalid store type. Valid types are" - " sa and acr." - ) - message = f"Delete Artifact store {store_name}" - logger.debug(message) - print(message) - try: - poller = self.api_clients.aosm_client.artifact_stores.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - artifact_store_name=store_name, - ) - LongRunningOperation(self.cli_ctx, "Deleting Artifact store...")(poller) - logger.info("Deleted Artifact Store") - except Exception: - logger.error("Failed to delete Artifact store %s", store_name) - raise - - def delete_publisher(self) -> None: - """ - Delete the publisher. - - Warning - dangerous - """ - message = f"Delete Publisher {self.config.publisher_name}" - logger.debug(message) - print(message) - # Occasionally nested resources that have just been deleted (e.g. artifact store) will - # still appear to exist, raising ResourceExistsError. We handle this by retrying up to - # 6 times, with a 30 second wait between each. - for attempt in range(6): - try: - poller = self.api_clients.aosm_client.publishers.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - ) - LongRunningOperation(self.cli_ctx, "Deleting Publisher...")(poller) - logger.info("Deleted Publisher") - break - except ResourceExistsError: - if attempt == 5: - logger.error("Failed to delete publisher") - raise - logger.debug( - "ResourceExistsError: This may be nested resource is not finished deleting. Wait and retry." - ) - sleep(30) - except Exception: - logger.error("Failed to delete publisher") - raise - - def delete_config_group_schema(self) -> None: - """Delete the Configuration Group Schema.""" - assert isinstance(self.config, NSConfiguration) - message = f"Delete Configuration Group Schema {self.config.cg_schema_name}" - logger.debug(message) - print(message) - try: - poller = ( - self.api_clients.aosm_client.configuration_group_schemas.begin_delete( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - configuration_group_schema_name=self.config.cg_schema_name, - ) - ) - LongRunningOperation( - self.cli_ctx, "Deleting Configuration Group Schema..." - )(poller) - logger.info("Deleted Configuration Group Schema") - except Exception: - logger.error("Failed to delete the Configuration Group Schema") - raise diff --git a/src/aosm/azext_aosm/deploy/artifact.py b/src/aosm/azext_aosm/deploy/artifact.py deleted file mode 100644 index ce8f2daa58f..00000000000 --- a/src/aosm/azext_aosm/deploy/artifact.py +++ /dev/null @@ -1,645 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -# pylint: disable=unidiomatic-typecheck -"""A module to handle interacting with artifacts.""" -import json -import math -import shutil -import subprocess -from dataclasses import dataclass -from typing import Any, Optional, Union - -from knack.log import get_logger -from knack.util import CLIError -from oras.client import OrasClient - -from azext_aosm._configuration import ( - ArtifactConfig, - CNFImageConfig, - HelmPackageConfig, - VhdArtifactConfig, -) -from azext_aosm.vendored_sdks.azure_storagev2.blob.v2022_11_02 import ( - BlobClient, BlobType) - -logger = get_logger(__name__) - - -@dataclass -class Artifact: - """Artifact class.""" - - artifact_name: str - artifact_type: str - artifact_version: str - artifact_client: Union[BlobClient, OrasClient] - manifest_credentials: Any - - def upload( - self, - artifact_config: Union[ArtifactConfig, HelmPackageConfig], - use_manifest_permissions: bool = False, - ) -> None: - """ - Upload artifact. - - :param artifact_config: configuration for the artifact being uploaded - """ - if isinstance(self.artifact_client, OrasClient): - if isinstance(artifact_config, HelmPackageConfig): - self._upload_helm_to_acr(artifact_config, use_manifest_permissions) - elif isinstance(artifact_config, ArtifactConfig): - self._upload_arm_to_acr(artifact_config) - elif isinstance(artifact_config, CNFImageConfig): - self._upload_or_copy_image_to_acr( - artifact_config, use_manifest_permissions - ) - else: - raise ValueError(f"Unsupported artifact type: {type(artifact_config)}.") - else: - assert isinstance(artifact_config, ArtifactConfig) - self._upload_to_storage_account(artifact_config) - - def _upload_arm_to_acr(self, artifact_config: ArtifactConfig) -> None: - """ - Upload ARM artifact to ACR. - - :param artifact_config: configuration for the artifact being uploaded - """ - assert isinstance(self.artifact_client, OrasClient) - - if artifact_config.file_path: - if not self.artifact_client.remote.hostname: - raise ValueError( - "Cannot upload ARM template as OrasClient has no remote hostname." - " Please check your ACR config." - ) - target = ( - f"{self.artifact_client.remote.hostname.replace('https://', '')}" - f"/{self.artifact_name}:{self.artifact_version}" - ) - logger.debug("Uploading %s to %s", artifact_config.file_path, target) - self.artifact_client.push(files=[artifact_config.file_path], target=target) - else: - raise NotImplementedError( - "Copying artifacts is not implemented for ACR artifacts stores." - ) - - @staticmethod - def _call_subprocess_raise_output(cmd: list) -> None: - """ - Call a subprocess and raise a CLIError with the output if it fails. - - :param cmd: command to run, in list format - :raise CLIError: if the subprocess fails - """ - log_cmd = cmd.copy() - if "--password" in log_cmd: - # Do not log out passwords. - log_cmd[log_cmd.index("--password") + 1] = "[REDACTED]" - - try: - called_process = subprocess.run( - cmd, encoding="utf-8", capture_output=True, text=True, check=True - ) - logger.debug( - "Output from %s: %s. Error: %s", - log_cmd, - called_process.stdout, - 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"Output: {error.stdout}\n" - f"Error output: {error.stderr}\n" - f"Return code: {error.returncode}" - ) - logger.debug("All the output %s", all_output) - - # Raise the error without the original exception, which may contain secrets. - raise CLIError(all_output) from None - - def _upload_helm_to_acr( - self, artifact_config: HelmPackageConfig, use_manifest_permissions: bool - ) -> None: - """ - Upload artifact to ACR. This does and az acr login and then a helm push. - - Requires helm to be installed. - - :param artifact_config: configuration for the artifact being uploaded - :param use_manifest_permissions: whether to use the manifest credentials for the - upload. If False, the CLI user credentials will be used, which does not - require Docker to be installed. If True, the manifest creds will be used, - which requires Docker. - """ - self._check_tool_installed("helm") - assert isinstance(self.artifact_client, OrasClient) - chart_path = artifact_config.path_to_chart - if not self.artifact_client.remote.hostname: - raise ValueError( - "Cannot upload artifact. Oras client has no remote hostname." - ) - registry = self._get_acr() - target_registry = f"oci://{registry}" - registry_name = registry.replace(".azurecr.io", "") - - username = self.manifest_credentials["username"] - password = self.manifest_credentials["acr_token"] - - if not use_manifest_permissions: - # Note that this uses the user running the CLI's AZ login credentials, not - # the manifest credentials retrieved from the ACR. This allows users with - # enough permissions to avoid having to install docker. It logs in to the - # registry by retrieving an access token, which allows use of this command - # in environments without docker. - # It is governed by the no-subscription-permissions CLI argument which - # default to False. - logger.debug("Using CLI user credentials to log into %s", registry_name) - acr_login_with_token_cmd = [ - str(shutil.which("az")), - "acr", - "login", - "--name", - registry_name, - "--expose-token", - "--output", - "tsv", - "--query", - "accessToken", - ] - username = "00000000-0000-0000-0000-000000000000" - try: - password = subprocess.check_output( - acr_login_with_token_cmd, encoding="utf-8", text=True - ).strip() - except subprocess.CalledProcessError as error: - unauthorized = ( - error.stderr - and (" 401" in error.stderr or "unauthorized" in error.stderr) - ) or ( - error.stdout - and (" 401" in error.stdout or "unauthorized" in error.stdout) - ) - - if unauthorized: - # As we shell out the the subprocess, I think checking for these - # strings is the best check we can do for permission failures. - raise CLIError( - " Failed to login to Artifact Store ACR.\n" - " It looks like you do not have permissions. You need to have" - " the AcrPush role over the" - " whole subscription in order to be able to upload to the new" - " Artifact store.\n\nIf you do not have them then you can" - " re-run the command using the --no-subscription-permissions" - " flag to use manifest credentials scoped" - " only to the store. This requires Docker to be installed" - " locally." - ) from error - else: - # This seems to prevent occasional helm login failures - self._check_tool_installed("docker") - acr_login_cmd = [ - str(shutil.which("az")), - "acr", - "login", - "--name", - registry_name, - "--username", - username, - "--password", - password, - ] - self._call_subprocess_raise_output(acr_login_cmd) - try: - logger.debug("Uploading %s to %s", chart_path, target_registry) - helm_login_cmd = [ - str(shutil.which("helm")), - "registry", - "login", - registry, - "--username", - username, - "--password", - password, - ] - self._call_subprocess_raise_output(helm_login_cmd) - - # helm push "$chart_path" "$target_registry" - push_command = [ - str(shutil.which("helm")), - "push", - chart_path, - target_registry, - ] - self._call_subprocess_raise_output(push_command) - finally: - helm_logout_cmd = [ - str(shutil.which("helm")), - "registry", - "logout", - registry, - ] - self._call_subprocess_raise_output(helm_logout_cmd) - - @staticmethod - def _convert_to_readable_size(size_in_bytes: Optional[int]) -> str: - """Converts a size in bytes to a human readable size.""" - if size_in_bytes is None: - return "Unknown bytes" - size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") - index = int(math.floor(math.log(size_in_bytes, 1024))) - power = math.pow(1024, index) - readable_size = round(size_in_bytes / power, 2) - return f"{readable_size} {size_name[index]}" - - def _vhd_upload_progress_callback( - self, current_bytes: int, total_bytes: Optional[int] - ) -> None: - """Callback function for VHD upload progress.""" - current_readable = self._convert_to_readable_size(current_bytes) - total_readable = self._convert_to_readable_size(total_bytes) - message = f"Uploaded {current_readable} of {total_readable} bytes" - logger.info(message) - print(message) - - def _upload_to_storage_account(self, artifact_config: ArtifactConfig) -> None: - """ - Upload artifact to storage account. - - :param artifact_config: configuration for the artifact being uploaded - """ - assert isinstance(self.artifact_client, BlobClient) - assert isinstance(artifact_config, ArtifactConfig) - - # If the file path is given, upload the artifact, else, copy it from an existing blob. - if artifact_config.file_path: - logger.info("Upload to blob store") - with open(artifact_config.file_path, "rb") as artifact: - self.artifact_client.upload_blob( - data=artifact, - overwrite=True, - blob_type=BlobType.PAGEBLOB, - progress_hook=self._vhd_upload_progress_callback, - ) - logger.info( - "Successfully uploaded %s to %s", - artifact_config.file_path, - self.artifact_client.account_name, - ) - else: - # Config Validation will raise error if not true - assert isinstance(artifact_config, VhdArtifactConfig) - assert artifact_config.blob_sas_url - logger.info("Copy from SAS URL to blob store") - source_blob = BlobClient.from_blob_url(artifact_config.blob_sas_url) - - if source_blob.exists(): - logger.debug(source_blob.url) - self.artifact_client.start_copy_from_url(source_blob.url) - logger.info( - "Successfully copied %s from %s to %s", - source_blob.blob_name, - source_blob.account_name, - self.artifact_client.account_name, - ) - else: - raise RuntimeError( - f"{source_blob.blob_name} does not exist in" - f" {source_blob.account_name}." - ) - - def _get_acr(self) -> str: - """ - Get the name of the ACR. - - :return: The name of the ACR - """ - assert hasattr(self.artifact_client, "remote") - if not self.artifact_client.remote.hostname: - raise ValueError( - "Cannot upload artifact. Oras client has no remote hostname." - ) - return self._clean_name(self.artifact_client.remote.hostname) - - def _get_acr_target_image( - self, - include_hostname: bool = True, - ) -> str: - """Format the acr url, artifact name and version into a target image string.""" - if include_hostname: - return f"{self._get_acr()}/{self.artifact_name}:{self.artifact_version}" - - return f"{self.artifact_name}:{self.artifact_version}" - - @staticmethod - def _check_tool_installed(tool_name: str) -> None: - """ - Check whether a tool such as docker or helm is installed. - - :param tool_name: name of the tool to check, e.g. docker - """ - if shutil.which(tool_name) is None: - raise CLIError(f"You must install {tool_name} to use this command.") - - def _upload_or_copy_image_to_acr( - self, artifact_config: CNFImageConfig, use_manifest_permissions: bool - ) -> None: - # Check whether the source registry has a namespace in the repository path - source_registry_namespace: str = "" - if artifact_config.source_registry_namespace: - source_registry_namespace = f"{artifact_config.source_registry_namespace}/" - - if artifact_config.source_local_docker_image: - # The user has provided a local docker image to use as the source - # for the images in the artifact manifest - self._check_tool_installed("docker") - print( - f"Using local docker image as source for image artifact upload for image artifact: {self.artifact_name}" - ) - self._push_image_from_local_registry( - local_docker_image=artifact_config.source_local_docker_image, - target_username=self.manifest_credentials["username"], - target_password=self.manifest_credentials["acr_token"], - ) - elif use_manifest_permissions: - self._check_tool_installed("docker") - print( - f"Using docker pull and push to copy image artifact: {self.artifact_name}" - ) - image_name = ( - f"{self._clean_name(artifact_config.source_registry)}/" - f"{source_registry_namespace}{self.artifact_name}" - f":{self.artifact_version}" - ) - self._pull_image_to_local_registry( - source_registry_login_server=self._clean_name( - artifact_config.source_registry - ), - source_image=image_name, - ) - self._push_image_from_local_registry( - local_docker_image=image_name, - target_username=self.manifest_credentials["username"], - target_password=self.manifest_credentials["acr_token"], - ) - else: - print(f"Using az acr import to copy image artifact: {self.artifact_name}") - self._copy_image( - source_registry_login_server=artifact_config.source_registry, - source_image=( - f"{source_registry_namespace}{self.artifact_name}" - f":{self.artifact_version}" - ), - ) - - def _push_image_from_local_registry( - self, - local_docker_image: str, - target_username: str, - target_password: str, - ): - """ - Push image to target registry using docker push. Requires docker. - - :param local_docker_image: name and tag of the source image on local registry - e.g. uploadacr.azurecr.io/samples/nginx:stable - :type local_docker_image: str - :param target_username: The username to use for the az acr login attempt - :type target_username: str - :param target_password: The password to use for the az acr login attempt - :type target_password: str - """ - assert hasattr(self.artifact_client, "remote") - target_acr = self._get_acr() - try: - target = self._get_acr_target_image() - print("Tagging source image") - - tag_image_cmd = [ - str(shutil.which("docker")), - "tag", - local_docker_image, - target, - ] - self._call_subprocess_raise_output(tag_image_cmd) - message = ( - "Logging into artifact store registry " - f"{self.artifact_client.remote.hostname}" - ) - - print(message) - logger.info(message) - acr_target_login_cmd = [ - str(shutil.which("az")), - "acr", - "login", - "--name", - target_acr, - "--username", - target_username, - "--password", - target_password, - ] - self._call_subprocess_raise_output(acr_target_login_cmd) - - print("Pushing target image using docker push") - push_target_image_cmd = [ - str(shutil.which("docker")), - "push", - target, - ] - self._call_subprocess_raise_output(push_target_image_cmd) - except CLIError as error: - logger.error( - ("Failed to tag and push %s to %s."), - local_docker_image, - target_acr, - ) - logger.debug(error, exc_info=True) - raise error - finally: - docker_logout_cmd = [ - str(shutil.which("docker")), - "logout", - target_acr, - ] - self._call_subprocess_raise_output(docker_logout_cmd) - - def _pull_image_to_local_registry( - self, - source_registry_login_server: str, - source_image: str, - ) -> None: - """ - Pull image to local registry using docker pull. Requires docker. - - Uses the CLI user's context to log in to the source registry. - - :param: source_registry_login_server: e.g. uploadacr.azurecr.io - :param: source_image: source docker image name e.g. - uploadacr.azurecr.io/samples/nginx:stable - """ - try: - # Login to the source registry with the CLI user credentials. This requires - # docker to be installed. - message = f"Logging into source registry {source_registry_login_server}" - print(message) - logger.info(message) - acr_source_login_cmd = [ - str(shutil.which("az")), - "acr", - "login", - "--name", - source_registry_login_server, - ] - self._call_subprocess_raise_output(acr_source_login_cmd) - message = f"Pulling source image {source_image}" - print(message) - logger.info(message) - pull_source_image_cmd = [ - str(shutil.which("docker")), - "pull", - source_image, - ] - self._call_subprocess_raise_output(pull_source_image_cmd) - except CLIError as error: - logger.error( - ( - "Failed to pull %s. Check if this image exists in the" - " source registry %s." - ), - source_image, - source_registry_login_server, - ) - logger.debug(error, exc_info=True) - raise error - finally: - docker_logout_cmd = [ - str(shutil.which("docker")), - "logout", - source_registry_login_server, - ] - self._call_subprocess_raise_output(docker_logout_cmd) - - @staticmethod - def _clean_name(registry_name: str) -> str: - """Remove https:// from the registry name.""" - return registry_name.replace("https://", "") - - def _copy_image( - self, - source_registry_login_server: str, - source_image: str, - ): - """ - Copy image from one ACR to another. - - Use az acr import to do the import image. Previously we used the python - sdk ContainerRegistryManagementClient.registries.begin_import_image - but this requires the source resource group name, which is more faff - at configuration time. - - Neither az acr import or begin_import_image support using the username - and acr_token retrieved from the manifest credentials, so this uses the - CLI users context to access both the source registry and the target - Artifact Store registry, which requires either Contributor role or a - custom role that allows the importImage action over the whole subscription. - - :param source_registry: source registry login server e.g. https://uploadacr.azurecr.io - :param source_image: source image including namespace and tags e.g. - samples/nginx:stable - """ - target_acr = self._get_acr() - try: - print("Copying artifact from source registry") - # In order to use az acr import cross subscription, we need to use a token - # to authenticate to the source registry. This is documented as the way to - # us az acr import cross-tenant, not cross-sub, but it also works - # cross-subscription, and meant we didn't have to make a breaking change to - # the format of input.json. Our usage here won't work cross-tenant since - # we're attempting to get the token (source) with the same context as that - # in which we are creating the ACR (i.e. the target tenant) - get_token_cmd = [str(shutil.which("az")), "account", "get-access-token"] - # Dont use _call_subprocess_raise_output here as we don't want to log the - # output - called_process = subprocess.run( # noqa: S603 - get_token_cmd, - encoding="utf-8", - capture_output=True, - text=True, - check=True, - ) - access_token_json = json.loads(called_process.stdout) - access_token = access_token_json["accessToken"] - except subprocess.CalledProcessError as get_token_err: - # This error is thrown from the az account get-access-token command - # If it errored we can log the output as it doesn't contain the token - logger.debug(get_token_err, exc_info=True) - raise CLIError( # pylint: disable=raise-missing-from - "Failed to import image: could not get an access token from your" - " Azure account. Try logging in again with `az login` and then re-run" - " the command. If it fails again, please raise an issue and try" - " repeating the command using the --no-subscription-permissions" - " flag to pull the image to your local machine and then" - " push it to the Artifact Store using manifest credentials scoped" - " only to the store. This requires Docker to be installed" - " locally." - ) - - try: - source = f"{self._clean_name(source_registry_login_server)}/{source_image}" - acr_import_image_cmd = [ - str(shutil.which("az")), - "acr", - "import", - "--name", - target_acr, - "--source", - source, - "--image", - self._get_acr_target_image(include_hostname=False), - "--password", - access_token, - ] - self._call_subprocess_raise_output(acr_import_image_cmd) - except CLIError as error: - logger.debug(error, exc_info=True) - if (" 401" in str(error)) or ("Unauthorized" in str(error)): - # As we shell out the the subprocess, I think checking for these strings - # is the best check we can do for permission failures. - raise CLIError( - " Failed to import image.\nIt looks like either the source_registry" - " in your config file does not exist or the image doesn't exist or" - " you do not have" - " permissions to import images. You need to have Reader/AcrPull" - f" from {source_registry_login_server}, and Contributor role +" - " AcrPush role, or a custom" - " role that allows the importImage action and AcrPush over the" - " whole subscription in order to be able to import to the new" - " Artifact store.\n\nIf you do not have the latter then you" - " can re-run the command using the --no-subscription-permissions" - " flag to pull the image to your local machine and then" - " push it to the Artifact Store using manifest credentials scoped" - " only to the store. This requires Docker to be installed" - " locally." - ) from error - - # The most likely failure is that the image already exists in the artifact - # store, so don't fail at this stage, log the error. - logger.error( - ( - "Failed to import %s to %s. Check if this image exists in the" - " source registry or is already present in the target registry.\n" - "%s" - ), - source_image, - target_acr, - error, - ) diff --git a/src/aosm/azext_aosm/deploy/artifact_manifest.py b/src/aosm/azext_aosm/deploy/artifact_manifest.py deleted file mode 100644 index ba0dc51a70c..00000000000 --- a/src/aosm/azext_aosm/deploy/artifact_manifest.py +++ /dev/null @@ -1,169 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""A module to handle interacting with artifact manifests.""" -from functools import cached_property, lru_cache -from typing import Any, List, Union - -from azure.cli.core.azclierror import AzCLIError -from knack.log import get_logger -from oras.client import OrasClient - -from azext_aosm._configuration import Configuration -from azext_aosm.deploy.artifact import Artifact -from azext_aosm.util.management_clients import ApiClients -from azext_aosm.vendored_sdks.models import ( - ArtifactManifest, - ArtifactType, - CredentialType, - ManifestArtifactFormat, -) -from azext_aosm.vendored_sdks.azure_storagev2.blob.v2022_11_02 import BlobClient - -logger = get_logger(__name__) - - -class ArtifactManifestOperator: - """ArtifactManifest class.""" - - # pylint: disable=too-few-public-methods - def __init__( - self, - config: Configuration, - api_clients: ApiClients, - store_name: str, - manifest_name: str, - ) -> None: - """Init.""" - self.manifest_name = manifest_name - self.api_clients = api_clients - self.config = config - self.store_name = store_name - self.artifacts = self._get_artifact_list() - - @cached_property - def _manifest_credentials(self) -> Any: - """Gets the details for uploading the artifacts in the manifest.""" - - return self.api_clients.aosm_client.artifact_manifests.list_credential( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - artifact_store_name=self.store_name, - artifact_manifest_name=self.manifest_name, - ).as_dict() - - @lru_cache(maxsize=32) # noqa: B019 - def _oras_client(self, acr_url: str) -> OrasClient: - """ - Returns an OrasClient object for uploading to the artifact store ACR. - - :param arc_url: URL of the ACR backing the artifact manifest - """ - client = OrasClient(hostname=acr_url) - client.login( - username=self._manifest_credentials["username"], - password=self._manifest_credentials["acr_token"], - ) - return client - - def _get_artifact_list(self) -> List[Artifact]: - """Get the list of Artifacts in the Artifact Manifest.""" - artifacts = [] - - manifest: ArtifactManifest = ( - self.api_clients.aosm_client.artifact_manifests.get( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - artifact_store_name=self.store_name, - artifact_manifest_name=self.manifest_name, - ) - ) - - # Instatiate an Artifact object for each artifact in the manifest. - if manifest.properties.artifacts: - for artifact in manifest.properties.artifacts: - if not ( - artifact.artifact_name - and artifact.artifact_type - and artifact.artifact_version - ): - raise AzCLIError( - "Cannot upload artifact. Artifact returned from " - "manifest query is missing required information." - f"{artifact}" - ) - - artifacts.append( - Artifact( - artifact_name=artifact.artifact_name, - artifact_type=artifact.artifact_type, - artifact_version=artifact.artifact_version, - artifact_client=self._get_artifact_client(artifact), - manifest_credentials=self._manifest_credentials, - ) - ) - - return artifacts - - def _get_artifact_client( - self, artifact: ManifestArtifactFormat - ) -> Union[BlobClient, OrasClient]: - """ - Get the artifact client required for uploading the artifact. - - :param artifact - a ManifestArtifactFormat with the artifact info. - """ - # Appease mypy - an error will be raised before this if these are blank - assert artifact.artifact_name - assert artifact.artifact_type - assert artifact.artifact_version - if ( - self._manifest_credentials["credential_type"] - == CredentialType.AZURE_STORAGE_ACCOUNT_TOKEN - ): - # Check we have the required artifact types for this credential. Indicates - # a coding error if we hit this but worth checking. - if not ( - artifact.artifact_type - in (ArtifactType.IMAGE_FILE, ArtifactType.VHD_IMAGE_FILE) - ): - raise AzCLIError( - f"Cannot upload artifact {artifact.artifact_name}." - " Artifact manifest credentials of type " - f"{CredentialType.AZURE_STORAGE_ACCOUNT_TOKEN} are not expected " - f"for Artifacts of type {artifact.artifact_type}" - ) - - container_basename = artifact.artifact_name.replace("-", "") - container_name = f"{container_basename}-{artifact.artifact_version}" - - # For AOSM to work VHD blobs must have the suffix .vhd - if artifact.artifact_name.endswith("-vhd"): - blob_name = f"{artifact.artifact_name[:-4].replace('-', '')}-{artifact.artifact_version}.vhd" - else: - blob_name = container_name - - logger.debug("container name: %s, blob name: %s", container_name, blob_name) - - blob_url = self._get_blob_url(container_name, blob_name) - return BlobClient.from_blob_url(blob_url) - return self._oras_client(self._manifest_credentials["acr_server_url"]) - - def _get_blob_url(self, container_name: str, blob_name: str) -> str: - """ - Get the URL for the blob to be uploaded to the storage account artifact store. - - :param container_name: name of the container - :param blob_name: the name that the blob will get uploaded with - """ - for container_credential in self._manifest_credentials["container_credentials"]: - if container_credential["container_name"] == container_name: - sas_uri = str(container_credential["container_sas_uri"]) - sas_uri_prefix, sas_uri_token = sas_uri.split("?", maxsplit=1) - - blob_url = f"{sas_uri_prefix}/{blob_name}?{sas_uri_token}" - logger.debug("Blob URL: %s", blob_url) - - return blob_url - raise KeyError(f"Manifest does not include a credential for {container_name}.") diff --git a/src/aosm/azext_aosm/deploy/deploy_with_arm.py b/src/aosm/azext_aosm/deploy/deploy_with_arm.py deleted file mode 100644 index 73da03a965a..00000000000 --- a/src/aosm/azext_aosm/deploy/deploy_with_arm.py +++ /dev/null @@ -1,731 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains class for deploying generated definitions using ARM.""" -import json -import os -import shutil -import subprocess # noqa -import tempfile -import time -from typing import Any, Dict, Optional - -from azure.cli.core.azclierror import ValidationError -from azure.cli.core.commands import LongRunningOperation -from azure.mgmt.resource.resources.models import DeploymentExtended -from knack.log import get_logger -from knack.util import CLIError - -from azext_aosm._configuration import ( - ArtifactConfig, - CNFConfiguration, - Configuration, - NFConfiguration, - NFDRETConfiguration, - NSConfiguration, - VNFConfiguration, -) -from azext_aosm.deploy.artifact import Artifact -from azext_aosm.deploy.artifact_manifest import ArtifactManifestOperator -from azext_aosm.deploy.pre_deploy import PreDeployerViaSDK -from azext_aosm.util.constants import ( - ARTIFACT_UPLOAD, - BICEP_PUBLISH, - CNF, - CNF_DEFINITION_BICEP_TEMPLATE_FILENAME, - CNF_MANIFEST_BICEP_TEMPLATE_FILENAME, - IMAGE_UPLOAD, - NSD, - NSD_ARTIFACT_MANIFEST_BICEP_FILENAME, - NSD_BICEP_FILENAME, - VNF, - VNF_DEFINITION_BICEP_TEMPLATE_FILENAME, - VNF_MANIFEST_BICEP_TEMPLATE_FILENAME, - DeployableResourceTypes, - SkipSteps, -) -from azext_aosm.util.management_clients import ApiClients - -logger = get_logger(__name__) - - -class DeployerViaArm: # pylint: disable=too-many-instance-attributes - """ - A class to deploy Artifact Manifests, NFDs and NSDs from bicep templates using ARM. - - Uses the SDK to pre-deploy less complex resources and then ARM to deploy the bicep - templates. - """ - - def __init__( - self, - api_clients: ApiClients, - resource_type: DeployableResourceTypes, - config: Configuration, - bicep_path: Optional[str] = None, - parameters_json_file: Optional[str] = None, - manifest_bicep_path: Optional[str] = None, - manifest_params_file: Optional[str] = None, - skip: Optional[SkipSteps] = None, - cli_ctx: Optional[object] = None, - use_manifest_permissions: bool = False, - ): - """ - :param api_clients: ApiClients object for AOSM and ResourceManagement - :param config: The configuration for this NF - :param bicep_path: The path to the bicep template of the nfdv - :param parameters_json_file: path to an override file of set parameters for the nfdv - :param manifest_bicep_path: The path to the bicep template of the manifest - :param manifest_params_file: path to an override file of set parameters for - the manifest - :param skip: options to skip, either publish bicep or upload artifacts - :param cli_ctx: The CLI context. Used with CNFs and all LongRunningOperations - :param use_manifest_permissions: - CNF definition_type publish only - ignored for VNF or NSD. Causes the image - artifact copy from a source ACR to be done via docker pull and push, - rather than `az acr import`. This is slower but does not require - Contributor (or importImage action) permissions on the publisher - subscription. Also uses manifest permissions for helm chart upload. - Requires Docker to be installed locally. - """ - self.api_clients = api_clients - self.resource_type = resource_type - self.config = config - self.bicep_path = bicep_path - self.parameters_json_file = parameters_json_file - self.manifest_bicep_path = manifest_bicep_path - self.manifest_params_file = manifest_params_file - self.skip = skip - self.cli_ctx = cli_ctx - self.pre_deployer = PreDeployerViaSDK( - self.api_clients, self.config, self.cli_ctx - ) - self.use_manifest_permissions = use_manifest_permissions - - def deploy_nfd_from_bicep(self) -> None: - """ - Deploy the bicep template defining the NFD. - - Also ensure that all required predeploy resources are deployed. - """ - assert isinstance(self.config, NFConfiguration) - if self.skip == BICEP_PUBLISH: - print("Skipping bicep manifest publish") - else: - # 1) Deploy Artifact manifest bicep - # Create or check required resources - deploy_manifest_template = not self.nfd_predeploy() - if deploy_manifest_template: - self.deploy_manifest_template() - else: - print( - f"Artifact manifests exist for NFD {self.config.nf_name} " - f"version {self.config.version}" - ) - - if self.skip == ARTIFACT_UPLOAD: - print("Skipping artifact upload") - else: - # 2) Upload artifacts - must be done before nfd deployment - if self.resource_type == VNF: - self._vnfd_artifact_upload() - if self.resource_type == CNF: - self._cnfd_artifact_upload() - - if self.skip == BICEP_PUBLISH: - print("Skipping bicep nfd publish") - print("Done") - return - - # 3) Deploy NFD bicep - if not self.bicep_path: - # User has not passed in a bicep template, so we are deploying the default - # one produced from building the NFDV using this CLI - if self.resource_type == VNF: - file_name = VNF_DEFINITION_BICEP_TEMPLATE_FILENAME - if self.resource_type == CNF: - file_name = CNF_DEFINITION_BICEP_TEMPLATE_FILENAME - bicep_path = os.path.join(self.config.output_directory_for_build, file_name) - message = ( - f"Deploy bicep template for NFD {self.config.nf_name} version" - f" {self.config.version} into" - f" {self.config.publisher_resource_group_name} under publisher" - f" {self.config.publisher_name}" - ) - print(message) - logger.info(message) - logger.debug( - "Parameters used for NF definition bicep deployment: %s", - self.parameters, - ) - self.deploy_bicep_template(bicep_path, self.parameters) - print(f"Deployed NFD {self.config.nf_name} version {self.config.version}.") - - def _vnfd_artifact_upload(self) -> None: - """Uploads the VHD and ARM template artifacts.""" - assert isinstance(self.config, VNFConfiguration) - storage_account_manifest = ArtifactManifestOperator( - self.config, - self.api_clients, - self.config.blob_artifact_store_name, - self.config.sa_manifest_name, - ) - acr_manifest = ArtifactManifestOperator( - self.config, - self.api_clients, - self.config.acr_artifact_store_name, - self.config.acr_manifest_names[0], - ) - - 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(vhd_config) - - print("Uploading ARM template artifact") - arm_template_artifact.upload(arm_template_config) - - def _cnfd_artifact_upload(self) -> None: - """Uploads the Helm chart and any additional images.""" - assert isinstance(self.config, CNFConfiguration) - acr_properties = self.api_clients.aosm_client.artifact_stores.get( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - artifact_store_name=self.config.acr_artifact_store_name, - ) - if not acr_properties.properties.storage_resource_id: - raise CLIError( - f"Artifact store {self.config.acr_artifact_store_name} " - "has no storage resource id linked" - ) - - # The artifacts from the manifest which has been deployed by bicep - acr_manifest = ArtifactManifestOperator( - self.config, - self.api_clients, - self.config.acr_artifact_store_name, - self.config.acr_manifest_names[0], - ) - - # Create a new dictionary of artifacts from the manifest, keyed by artifact name - artifact_dictionary = {} - - for artifact in acr_manifest.artifacts: - artifact_dictionary[artifact.artifact_name] = artifact - - for helm_package in self.config.helm_packages: - # Go through the helm packages in the config that the user has provided - helm_package_name = helm_package.name # type: ignore - - if helm_package_name not in artifact_dictionary: - # Helm package in the config file but not in the artifact manifest - raise CLIError( - f"Artifact {helm_package_name} not found in the artifact manifest" - ) - # Get the artifact object that came from the manifest - manifest_artifact = artifact_dictionary[helm_package_name] - - print(f"Uploading Helm package: {helm_package_name}") - - # The artifact object will use the correct client (ORAS) to upload the - # artifact - manifest_artifact.upload(helm_package, self.use_manifest_permissions) # type: ignore - - print(f"Finished uploading Helm package: {helm_package_name}") - - # Remove this helm package artifact from the dictionary. - artifact_dictionary.pop(helm_package_name) - - # All the remaining artifacts are not in the helm_packages list. We assume that - # they are images that need to be copied from another ACR or uploaded from a - # local image. - if self.skip == IMAGE_UPLOAD: - print("Skipping upload of images") - return - - # This is the first time we have easy access to the number of images to upload - # so we validate the config file here. - if ( - len(artifact_dictionary.values()) > 1 - and self.config.images.source_local_docker_image # type: ignore - ): - raise ValidationError( - "Multiple image artifacts found to upload and a local docker image" - " was specified in the config file. source_local_docker_image is only " - "supported if there is a single image artifact to upload." - ) - for artifact in artifact_dictionary.values(): - assert isinstance(artifact, Artifact) - artifact.upload(self.config.images, self.use_manifest_permissions) # type: ignore - - def nfd_predeploy(self) -> bool: - """ - All the predeploy steps for a NFD. Create publisher, artifact stores and NFDG. - - Return True if artifact manifest already exists, False otherwise - """ - logger.debug("Ensure all required resources exist") - self.pre_deployer.ensure_config_resource_group_exists() - self.pre_deployer.ensure_config_publisher_exists() - self.pre_deployer.ensure_acr_artifact_store_exists() - if self.resource_type == VNF: - self.pre_deployer.ensure_sa_artifact_store_exists() - self.pre_deployer.ensure_config_nfdg_exists() - return self.pre_deployer.do_config_artifact_manifests_exist() - - @property - def parameters(self) -> Dict[str, Any]: - if self.parameters_json_file: - message = f"Use parameters from file {self.parameters_json_file}" - logger.info(message) - print(message) - with open(self.parameters_json_file, "r", encoding="utf-8") as f: - parameters_json = json.loads(f.read()) - parameters = parameters_json["parameters"] - else: - # User has not passed in parameters file, so we use the parameters - # required from config for the default bicep template produced from - # building the NFDV using this CLI - logger.debug("Create parameters for default template.") - parameters = self.construct_parameters() - - return parameters - - def construct_parameters(self) -> Dict[str, Any]: - """ - Create the parmeters dictionary for vnfdefinitions.bicep. - - VNF specific. - """ - 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}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "saArtifactStoreName": {"value": self.config.blob_artifact_store_name}, - "nfName": {"value": self.config.nf_name}, - "nfDefinitionGroup": {"value": self.config.nfdg_name}, - "nfDefinitionVersion": {"value": self.config.version}, - "vhdVersion": {"value": self.config.vhd.version}, - "armTemplateVersion": {"value": self.config.arm_template.version}, - } - if self.resource_type == CNF: - assert isinstance(self.config, CNFConfiguration) - return { - "location": {"value": self.config.location}, - "publisherName": {"value": self.config.publisher_name}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "nfDefinitionGroup": {"value": self.config.nfdg_name}, - "nfDefinitionVersion": {"value": self.config.version}, - } - if self.resource_type == NSD: - assert isinstance(self.config, NSConfiguration) - return { - "location": {"value": self.config.location}, - "publisherName": {"value": self.config.publisher_name}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "nsDesignGroup": {"value": self.config.nsd_name}, - "nsDesignVersion": {"value": self.config.nsd_version}, - "nfviSiteName": {"value": self.config.nfvi_site_name}, - } - raise TypeError( - "Unexpected config type. Expected [VNFConfiguration|CNFConfiguration|NSConfiguration]," - f" received {type(self.config)}" - ) - - 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}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "saArtifactStoreName": {"value": self.config.blob_artifact_store_name}, - "acrManifestName": {"value": self.config.acr_manifest_names[0]}, - "saManifestName": {"value": self.config.sa_manifest_name}, - "nfName": {"value": self.config.nf_name}, - "vhdVersion": {"value": self.config.vhd.version}, - "armTemplateVersion": {"value": self.config.arm_template.version}, - } - if self.resource_type == CNF: - assert isinstance(self.config, CNFConfiguration) - return { - "location": {"value": self.config.location}, - "publisherName": {"value": self.config.publisher_name}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "acrManifestName": {"value": self.config.acr_manifest_names[0]}, - } - if self.resource_type == NSD: - assert isinstance(self.config, NSConfiguration) - - 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. - return { - "location": {"value": self.config.location}, - "publisherName": {"value": self.config.publisher_name}, - "acrArtifactStoreName": {"value": self.config.acr_artifact_store_name}, - "acrManifestNames": {"value": self.config.acr_manifest_names}, - "armTemplateNames": {"value": arm_template_names}, - "armTemplateVersion": {"value": self.config.nsd_version}, - } - raise ValueError("Unknown configuration type") - - def deploy_nsd_from_bicep(self) -> None: - """ - Deploy the bicep template defining the VNFD. - - Also ensure that all required predeploy resources are deployed. - """ - assert isinstance(self.config, NSConfiguration) - if not self.skip == BICEP_PUBLISH: - # 1) Deploy Artifact manifest bicep - if not self.bicep_path: - # User has not passed in a bicep template, so we are deploying the default - # one produced from building the NSDV using this CLI - bicep_path = os.path.join( - self.config.output_directory_for_build, - NSD_BICEP_FILENAME, - ) - - logger.debug(self.parameters) - - # Create or check required resources - deploy_manifest_template = not self.nsd_predeploy() - - if deploy_manifest_template: - self.deploy_manifest_template() - else: - logger.debug( - "Artifact manifests %s already exist", - self.config.acr_manifest_names, - ) - print("Artifact manifests already exist") - - if self.skip == ARTIFACT_UPLOAD: - print("Skipping artifact upload") - else: - # 2) Upload artifacts - must be done before nsd deployment - 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, - self.config.acr_artifact_store_name, - manifest, - ) - - # Convert the NF bicep to ARM - arm_template_artifact_json = self.convert_bicep_to_arm( - os.path.join( - self.config.output_directory_for_build, nf.nf_bicep_filename - ) - ) - - arm_template_artifact = acr_manifest.artifacts[0] - - # appease mypy - assert ( - nf.arm_template.file_path - ), "Config missing ARM template file path" - with open(nf.arm_template.file_path, "w", encoding="utf-8") as file: - file.write(json.dumps(arm_template_artifact_json, indent=4)) - - print(f"Uploading ARM template artifact: {nf.arm_template.file_path}") - arm_template_artifact.upload(nf.arm_template) - - if self.skip == BICEP_PUBLISH: - print("Skipping bicep nsd publish") - print("Done") - return - - # 3) Deploy NSD bicep - if not self.bicep_path: - # User has not passed in a bicep template, so we are deploying the default - # one produced from building the NSDV using this CLI - bicep_path = os.path.join( - self.config.output_directory_for_build, - NSD_BICEP_FILENAME, - ) - message = ( - f"Deploy bicep template for NSDV {self.config.nsd_version} " - f"into {self.config.publisher_resource_group_name} under publisher " - f"{self.config.publisher_name}" - ) - print(message) - logger.info(message) - self.deploy_bicep_template(bicep_path, self.parameters) - print( - f"Deployed NSD {self.config.nsd_name} " - f"version {self.config.nsd_version}." - ) - - def deploy_manifest_template(self) -> None: - """Deploy the bicep template defining the manifest.""" - print("Deploy bicep template for Artifact manifests") - logger.debug("Deploy manifest bicep") - - if not self.manifest_bicep_path: - file_name: str = "" - if self.resource_type == NSD: - file_name = NSD_ARTIFACT_MANIFEST_BICEP_FILENAME - if self.resource_type == VNF: - file_name = VNF_MANIFEST_BICEP_TEMPLATE_FILENAME - if self.resource_type == CNF: - file_name = CNF_MANIFEST_BICEP_TEMPLATE_FILENAME - - manifest_bicep_path = os.path.join( - str(self.config.output_directory_for_build), - file_name, - ) - if not self.manifest_params_file: - manifest_params = self.construct_manifest_parameters() - else: - logger.info("Use provided manifest parameters") - with open(self.manifest_params_file, "r", encoding="utf-8") as f: - manifest_json = json.loads(f.read()) - manifest_params = manifest_json["parameters"] - self.deploy_bicep_template(manifest_bicep_path, manifest_params) - - def nsd_predeploy(self) -> bool: - """ - All the predeploy steps for a NSD. Check if the RG, publisher, ACR, NSD and - artifact manifest exist. - - Return True if artifact manifest already exists, False otherwise - """ - logger.debug("Ensure all required resources exist") - self.pre_deployer.ensure_config_resource_group_exists() - self.pre_deployer.ensure_config_publisher_exists() - self.pre_deployer.ensure_acr_artifact_store_exists() - self.pre_deployer.ensure_config_nsd_exists() - return self.pre_deployer.do_config_artifact_manifests_exist() - - def deploy_bicep_template( - self, bicep_template_path: str, parameters: Dict[Any, Any] - ) -> Any: - """ - Deploy a bicep template. - - :param bicep_template_path: Path to the bicep template - :param parameters: Parameters for the bicep template - :return Any output that the template produces - """ - logger.info("Deploy %s", bicep_template_path) - logger.debug("Parameters: %s", parameters) - arm_template_json = self.convert_bicep_to_arm(bicep_template_path) - - return self.validate_and_deploy_arm_template( - arm_template_json, parameters, self.config.publisher_resource_group_name - ) - - def resource_exists(self, resource_name: str) -> bool: - """ - Determine if a resource with the given name exists. - - :param resource_name: The name of the resource to check. - """ - logger.debug("Check if %s exists", resource_name) - resources = self.api_clients.resource_client.resources.list_by_resource_group( - resource_group_name=self.config.publisher_resource_group_name - ) - - resource_exists = False - - for resource in resources: - if resource.name == resource_name: - resource_exists = True - break - - return resource_exists - - def validate_and_deploy_arm_template( - self, template: Any, parameters: Dict[Any, Any], resource_group: str - ) -> Any: - """ - Validate and deploy an individual ARM template. - - This ARM template will be created in the resource group passed in. - - :param template: The JSON contents of the template to deploy - :param parameters: The JSON contents of the parameters file - :param resource_group: The name of the resource group that has been deployed - - :return: Output dictionary from the bicep template. - :raise RuntimeError if validation or deploy fails - """ - # Get current time from the time module and remove all digits after the decimal - # point - current_time = str(time.time()).split(".", maxsplit=1)[0] - - # Add a timestamp to the deployment name to ensure it is unique - deployment_name = f"AOSM_CLI_deployment_{current_time}" - - # Validation is automatically re-attempted in live runs, but not in test - # playback, causing them to fail. This explicitly re-attempts validation to - # ensure the tests pass - validation_res = None - for validation_attempt in range(2): - try: - validation = ( - self.api_clients.resource_client.deployments.begin_validate( - resource_group_name=resource_group, - deployment_name=deployment_name, - parameters={ - "properties": { - "mode": "Incremental", - "template": template, - "parameters": parameters, - } - }, - ) - ) - validation_res = LongRunningOperation( - self.cli_ctx, "Validating ARM template..." - )(validation) - break - except Exception: # pylint: disable=broad-except - if validation_attempt == 1: - raise - - if not validation_res: - # Don't expect to hit this but it appeases mypy - raise RuntimeError(f"Validation of template {template} failed.") - - logger.debug("Validation Result %s", validation_res) - if validation_res.error: - # Validation failed so don't even try to deploy - logger.error( - ( - "Template for resource group %s has failed validation. The message" - " was: %s. See logs for additional details." - ), - resource_group, - validation_res.error.message, - ) - logger.debug( - ( - "Template for resource group %s failed validation." - " Full error details: %s" - ), - resource_group, - validation_res.error, - ) - raise RuntimeError("Azure template validation failed.") - - # Validation succeeded so proceed with deployment - logger.debug("Successfully validated resources for %s", resource_group) - - poller = self.api_clients.resource_client.deployments.begin_create_or_update( - resource_group_name=resource_group, - deployment_name=deployment_name, - parameters={ - "properties": { - "mode": "Incremental", - "template": template, - "parameters": parameters, - } - }, - ) - logger.debug(poller) - - # Wait for the deployment to complete and get the outputs - deployment: DeploymentExtended = LongRunningOperation( - self.cli_ctx, "Deploying ARM template" - )(poller) - logger.debug("Finished deploying") - - if deployment.properties is not None: - depl_props = deployment.properties - else: - raise RuntimeError("The deployment has no properties.\nAborting") - logger.debug("Deployed: %s %s %s", deployment.name, deployment.id, depl_props) - - if depl_props.provisioning_state != "Succeeded": - logger.debug("Failed to provision: %s", depl_props) - raise RuntimeError( - "Deploy of template to resource group" - f" {resource_group} proceeded but the provisioning" - f" state returned is {depl_props.provisioning_state}." - "\nAborting" - ) - logger.debug( - "Provisioning state of deployment %s : %s", - resource_group, - depl_props.provisioning_state, - ) - - return depl_props.outputs - - @staticmethod - def convert_bicep_to_arm(bicep_template_path: str) -> Any: - """ - Convert a bicep template into an ARM template. - - :param bicep_template_path: The path to the bicep template to be converted - :return: Output dictionary from the bicep template. - """ - logger.debug("Converting %s to ARM template", bicep_template_path) - - with tempfile.TemporaryDirectory() as tmpdir: - bicep_filename = os.path.basename(bicep_template_path) - arm_template_name = bicep_filename.replace(".bicep", ".json") - - try: - bicep_output = subprocess.run( # noqa - [ - str(shutil.which("az")), - "bicep", - "build", - "--file", - bicep_template_path, - "--outfile", - os.path.join(tmpdir, arm_template_name), - ], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - logger.debug("az bicep output: %s", str(bicep_output)) - except subprocess.CalledProcessError as err: - logger.error( - ( - "ARM template compilation failed! See logs for full " - "output. The failing command was %s" - ), - err.cmd, - ) - logger.debug("bicep build stdout: %s", err.stdout) - logger.debug("bicep build stderr: %s", err.stderr) - raise - - with open( - os.path.join(tmpdir, arm_template_name), "r", encoding="utf-8" - ) as template_file: - arm_json = json.loads(template_file.read()) - - return arm_json diff --git a/src/aosm/azext_aosm/deploy/pre_deploy.py b/src/aosm/azext_aosm/deploy/pre_deploy.py deleted file mode 100644 index b34a4929cc9..00000000000 --- a/src/aosm/azext_aosm/deploy/pre_deploy.py +++ /dev/null @@ -1,444 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains class for deploying resources required by NFDs/NSDs via the SDK.""" - -from typing import Optional - -from azure.cli.core.azclierror import AzCLIError -from azure.cli.core.commands import LongRunningOperation -from azure.core import exceptions as azure_exceptions -from azure.mgmt.resource.resources.models import ResourceGroup -from knack.log import get_logger - -from azext_aosm._configuration import ( - Configuration, - VNFConfiguration, -) -from azext_aosm.util.management_clients import ApiClients -from azext_aosm.vendored_sdks.models import ( - ArtifactStore, - ArtifactStorePropertiesFormat, - ArtifactStoreType, - NetworkFunctionDefinitionGroup, - NetworkServiceDesignGroup, - ProvisioningState, - Publisher, - PublisherPropertiesFormat, - ManagedServiceIdentity -) - -logger = get_logger(__name__) - - -class PreDeployerViaSDK: - """ - A class for checking or publishing resources required by NFDs/NSDs. - - Uses the SDK to deploy rather than ARM, as the objects it deploys are not complex. - """ - - def __init__( - self, - api_clients: ApiClients, - config: Configuration, - cli_ctx: Optional[object] = None, - ) -> None: - """ - Initializes a new instance of the Deployer class. - - :param api_clients: ApiClients object for AOSM and ResourceManagement - :param config: The configuration for this NF - :param cli_ctx: The CLI context. Used with all LongRunningOperation calls. - """ - - self.api_clients = api_clients - self.config = config - self.cli_ctx = cli_ctx - - def ensure_resource_group_exists(self, resource_group_name: str) -> None: - """ - Checks whether a particular resource group exists on the subscription, and - attempts to create it if not. - - :param resource_group_name: The name of the resource group - """ - if not self.api_clients.resource_client.resource_groups.check_existence( - resource_group_name - ): - logger.info("RG %s not found. Create it.", resource_group_name) - print(f"Creating resource group {resource_group_name}.") - rg_params: ResourceGroup = ResourceGroup(location=self.config.location) - self.api_clients.resource_client.resource_groups.create_or_update( - resource_group_name, rg_params - ) - else: - print(f"Resource group {resource_group_name} exists.") - self.api_clients.resource_client.resource_groups.get(resource_group_name) - - def ensure_config_resource_group_exists(self) -> None: - """ - Ensures that the resource group exists. - - Finds the parameters from self.config - """ - self.ensure_resource_group_exists(self.config.publisher_resource_group_name) - - def ensure_publisher_exists( - self, resource_group_name: str, publisher_name: str, location: str - ) -> None: - """ - Ensures that the publisher exists in the resource group. - - :param resource_group_name: The name of the resource group. - :type resource_group_name: str - :param publisher_name: The name of the publisher. - :type publisher_name: str - :param location: The location of the publisher. - :type location: str - """ - - try: - publisher = self.api_clients.aosm_client.publishers.get( - resource_group_name, publisher_name - ) - print( - f"Publisher {publisher.name} exists in resource group" - f" {resource_group_name}" - ) - except azure_exceptions.ResourceNotFoundError: - # Create the publisher with default SAMI and private scope - logger.info("Creating publisher %s if it does not exist", publisher_name) - print( - f"Creating publisher {publisher_name} in resource group" - f" {resource_group_name}" - ) - publisher_properties = PublisherPropertiesFormat(scope="Private") - publisher_sami = ManagedServiceIdentity(type="SystemAssigned") - poller = self.api_clients.aosm_client.publishers.begin_create_or_update( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - parameters=Publisher(location=location, properties=publisher_properties, identity=publisher_sami), - ) - LongRunningOperation(self.cli_ctx, "Creating publisher...")(poller) - - def ensure_config_publisher_exists(self) -> None: - """ - Ensures that the publisher exists in the resource group. - - Finds the parameters from self.config - """ - self.ensure_publisher_exists( - resource_group_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - location=self.config.location, - ) - - def ensure_artifact_store_exists( - self, - resource_group_name: str, - publisher_name: str, - artifact_store_name: str, - artifact_store_type: ArtifactStoreType, - location: str, - ) -> None: - """ - Ensures that the artifact store exists in the resource group. - - :param resource_group_name: The name of the resource group. - :type resource_group_name: str - :param publisher_name: The name of the publisher. - :type publisher_name: str - :param artifact_store_name: The name of the artifact store. - :type artifact_store_name: str - :param artifact_store_type: The type of the artifact store. - :type artifact_store_type: ArtifactStoreType - :param location: The location of the artifact store. - :type location: str - """ - logger.info( - "Creating artifact store %s if it does not exist", - artifact_store_name, - ) - try: - self.api_clients.aosm_client.artifact_stores.get( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - artifact_store_name=artifact_store_name, - ) - print( - f"Artifact store {artifact_store_name} exists in resource group" - f" {resource_group_name}" - ) - except azure_exceptions.ResourceNotFoundError as ex: - print( - f"Create Artifact Store {artifact_store_name} of type" - f" {artifact_store_type}" - ) - artifact_store_properties = ArtifactStorePropertiesFormat(store_type=artifact_store_type) - poller = ( - self.api_clients.aosm_client.artifact_stores.begin_create_or_update( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - artifact_store_name=artifact_store_name, - parameters=ArtifactStore( - location=location, - properties=artifact_store_properties, - ), - ) - ) - # LongRunningOperation waits for provisioning state Succeeded before - # carrying on - artifactStore: ArtifactStore = LongRunningOperation( - self.cli_ctx, "Creating Artifact Store..." - )(poller) - - if artifactStore.properties.provisioning_state != ProvisioningState.SUCCEEDED: - logger.debug("Failed to provision artifact store: %s", artifactStore.name) - raise RuntimeError( - "Creation of artifact store proceeded, but the provisioning" - f" state returned is {artifactStore.properties.provisioning_state}. " - "\nAborting" - ) from ex - logger.debug( - "Provisioning state of %s: %s", - artifact_store_name, - artifactStore.properties.provisioning_state, - ) - - def ensure_acr_artifact_store_exists(self) -> None: - """ - Ensures that the ACR Artifact store exists. - - Finds the parameters from self.config - """ - self.ensure_artifact_store_exists( - self.config.publisher_resource_group_name, - self.config.publisher_name, - self.config.acr_artifact_store_name, - ArtifactStoreType.AZURE_CONTAINER_REGISTRY, # type: ignore - self.config.location, - ) - - def ensure_sa_artifact_store_exists(self) -> None: - """ - Ensures that the Storage Account Artifact store for VNF exists. - - Finds the parameters from self.config - """ - if not isinstance(self.config, VNFConfiguration): - # This is a coding error but worth checking. - raise AzCLIError( - "Cannot check that the storage account artifact store exists as " - "the configuration file doesn't map to VNFConfiguration" - ) - - self.ensure_artifact_store_exists( - self.config.publisher_resource_group_name, - self.config.publisher_name, - self.config.blob_artifact_store_name, - ArtifactStoreType.AZURE_STORAGE_ACCOUNT, # type: ignore - self.config.location, - ) - - def ensure_nfdg_exists( - self, - resource_group_name: str, - publisher_name: str, - nfdg_name: str, - location: str, - ): - """ - Ensures that the network function definition group exists in the resource group. - - :param resource_group_name: The name of the resource group. - :type resource_group_name: str - :param publisher_name: The name of the publisher. - :type publisher_name: str - :param nfdg_name: The name of the network function definition group. - :type nfdg_name: str - :param location: The location of the network function definition group. - :type location: str - """ - - logger.info( - "Creating network function definition group %s if it does not exist", - nfdg_name, - ) - - try: - self.api_clients.aosm_client.network_function_definition_groups.get( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - network_function_definition_group_name=nfdg_name, - ) - print( - f"Network function definition group {nfdg_name} exists in resource" - f" group {resource_group_name}" - ) - except azure_exceptions.ResourceNotFoundError as ex: - print(f"Create Network Function Definition Group {nfdg_name}") - poller = self.api_clients.aosm_client.network_function_definition_groups.begin_create_or_update( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - network_function_definition_group_name=nfdg_name, - parameters=NetworkFunctionDefinitionGroup(location=location), - ) - - # Asking for result waits for provisioning state Succeeded before carrying - # on - nfdg: NetworkFunctionDefinitionGroup = LongRunningOperation( - self.cli_ctx, "Creating Network Function Definition Group..." - )(poller) - - if nfdg.properties.provisioning_state != ProvisioningState.SUCCEEDED: - logger.debug( - "Failed to provision Network Function Definition Group: %s", - nfdg.name, - ) - raise RuntimeError( - "Creation of Network Function Definition Group proceeded, but the" - f" provisioning state returned is {nfdg.properties.provisioning_state}." - " \nAborting" - ) from ex - logger.debug( - "Provisioning state of %s: %s", nfdg_name, nfdg.properties.provisioning_state - ) - - def ensure_config_nfdg_exists( - self, - ): - """ - Ensures that the Network Function Definition Group exists. - - Finds the parameters from self.config - """ - self.ensure_nfdg_exists( - self.config.publisher_resource_group_name, - self.config.publisher_name, - self.config.nfdg_name, - self.config.location, - ) - - def ensure_config_nsd_exists( - self, - ): - """ - Ensures that the Network Service Design exists. - - Finds the parameters from self.config - """ - self.ensure_nsd_exists( - self.config.publisher_resource_group_name, - self.config.publisher_name, - self.config.nsd_name, - self.config.location, - ) - - def does_artifact_manifest_exist( - self, rg_name: str, publisher_name: str, store_name: str, manifest_name: str - ) -> bool: - try: - self.api_clients.aosm_client.artifact_manifests.get( - resource_group_name=rg_name, - publisher_name=publisher_name, - artifact_store_name=store_name, - artifact_manifest_name=manifest_name, - ) - logger.debug("Artifact manifest %s exists", manifest_name) - return True - except azure_exceptions.ResourceNotFoundError: - logger.debug("Artifact manifest %s does not exist", manifest_name) - return False - - def do_config_artifact_manifests_exist( - self, - ) -> bool: - """Returns True if all required manifests exist, False otherwise.""" - all_acr_mannys_exist = True - any_acr_mannys_exist: bool = not self.config.acr_manifest_names - - for manifest in self.config.acr_manifest_names: - acr_manny_exists: bool = self.does_artifact_manifest_exist( - rg_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - store_name=self.config.acr_artifact_store_name, - manifest_name=manifest, - ) - all_acr_mannys_exist &= acr_manny_exists - any_acr_mannys_exist |= acr_manny_exists - - if isinstance(self.config, VNFConfiguration): - sa_manny_exists: bool = self.does_artifact_manifest_exist( - rg_name=self.config.publisher_resource_group_name, - publisher_name=self.config.publisher_name, - store_name=self.config.blob_artifact_store_name, - manifest_name=self.config.sa_manifest_name, - ) - if all_acr_mannys_exist and sa_manny_exists: - return True - if any_acr_mannys_exist or sa_manny_exists: - raise AzCLIError( - "Only a subset of artifact manifest exists. Cannot proceed. Please delete" - " the NFDV or NSDV as appropriate using the `az aosm nfd delete` or " - "`az aosm nsd delete` command." - ) - return False - - return all_acr_mannys_exist - - def ensure_nsd_exists( - self, - resource_group_name: str, - publisher_name: str, - nsd_name: str, - location: str, - ): - """ - Ensures that the network service design group exists in the resource group. - - :param resource_group_name: The name of the resource group. - :type resource_group_name: str - :param publisher_name: The name of the publisher. - :type publisher_name: str - :param nsd_name: The name of the network service design group. - :type nsd_name: str - :param location: The location of the network service design group. - :type location: str - """ - print( - f"Creating Network Service Design {nsd_name} if it does not exist", - ) - logger.info( - "Creating Network Service Design %s if it does not exist", - nsd_name, - ) - poller = self.api_clients.aosm_client.network_service_design_groups.begin_create_or_update( - resource_group_name=resource_group_name, - publisher_name=publisher_name, - network_service_design_group_name=nsd_name, - parameters=NetworkServiceDesignGroup(location=location), - ) - LongRunningOperation(self.cli_ctx, "Creating Network Service Design...")(poller) - - def resource_exists_by_name(self, rg_name: str, resource_name: str) -> bool: - """ - Determine if a resource with the given name exists. No checking is done as - to the type. - - :param resource_name: The name of the resource to check. - """ - logger.debug("Check if %s exists", resource_name) - resources = self.api_clients.resource_client.resources.list_by_resource_group( - resource_group_name=rg_name - ) - - resource_exists = False - - for resource in resources: - if resource.name == resource_name: - resource_exists = True - break - - return resource_exists diff --git a/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py b/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py deleted file mode 100644 index 9543ccff776..00000000000 --- a/src/aosm/azext_aosm/generate_nfd/cnf_nfd_generator.py +++ /dev/null @@ -1,850 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains a class for generating CNF NFDs and associated resources.""" -import json -import re -import shutil -import tarfile -import tempfile -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple - -import yaml -from azure.cli.core.azclierror import FileOperationError, InvalidTemplateError -from jinja2 import StrictUndefined, Template -from knack.log import get_logger - -from azext_aosm._configuration import CNFConfiguration, HelmPackageConfig -from azext_aosm.generate_nfd.nfd_generator_base import NFDGenerator -from azext_aosm.util.constants import ( - CNF_DEFINITION_BICEP_TEMPLATE_FILENAME, - CNF_DEFINITION_JINJA2_SOURCE_TEMPLATE_FILENAME, - CNF_MANIFEST_BICEP_TEMPLATE_FILENAME, - CNF_MANIFEST_JINJA2_SOURCE_TEMPLATE_FILENAME, - CNF_VALUES_SCHEMA_FILENAME, - CONFIG_MAPPINGS_DIR_NAME, - DEPLOYMENT_PARAMETER_MAPPING_REGEX, - DEPLOYMENT_PARAMETERS_FILENAME, - GENERATED_VALUES_MAPPINGS_DIR_NAME, - IMAGE_NAME_AND_VERSION_REGEX, - IMAGE_PATH_REGEX, - IMAGE_PULL_SECRETS_START_STRING, - IMAGE_START_STRING, - SCHEMA_PREFIX, - SCHEMAS_DIR_NAME, -) -from azext_aosm.util.utils import input_ack - -logger = get_logger(__name__) - - -@dataclass -class Artifact: - """Information about an artifact.""" - - name: str - version: str - - -@dataclass -class NFApplicationConfiguration: # pylint: disable=too-many-instance-attributes - name: str - chartName: str - chartVersion: str - releaseName: str - dependsOnProfile: List[str] - registryValuesPaths: List[str] - imagePullSecretsValuesPaths: List[str] - valueMappingsFile: str - - def __post_init__(self): - """Format the fields based on the NFDV validation rules.""" - self._format_name() - self._format_release_name() - - def _format_name(self): - """ - Format the name field. - - The name should start with a alphabetic character, have alphanumeric characters - or '-' in-between and end with alphanumerc character, and be less than 64 - characters long. See NfdVersionValidationHelper.cs in pez codebase - """ - # Replace any non (alphanumeric or '-') characters with '-' - self.name = re.sub("[^0-9a-zA-Z-]+", "-", self.name) - # Strip leading or trailing - - self.name = self.name.strip("-") - self.name = self.name[:64] - - if not self.name: - raise InvalidTemplateError( - "The name field of the NF application configuration for helm package " - f"{self.chartName} is empty after removing invalid characters. " - "Valid characters are alphanumeric and '-'. Please fix this in the name" - " field for the helm package in your input config file." - ) - - def _format_release_name(self): - """ - Format release name. - - It must consist of lower case alphanumeric characters, '-' or '.', and must - start and end with an alphanumeric character See - AzureArcKubernetesRuleBuilderExtensions.cs and - AzureArcKubernetesNfValidationMessage.cs in pez codebase - """ - self.releaseName = self.releaseName.lower() - # Replace any non (alphanumeric or '-' or '.') characters with '-' - self.releaseName = re.sub("[^0-9a-z-.]+", "-", self.releaseName) - # Strip leading - or . - self.releaseName = self.releaseName.strip("-") - self.releaseName = self.releaseName.strip(".") - if not self.releaseName: - raise InvalidTemplateError( - "The releaseName field of the NF application configuration for helm " - f"chart {self.chartName} is empty after formatting and removing invalid" - "characters. Valid characters are alphanumeric, -.' and '-' and the " - "releaseName must start and end with an alphanumeric character. The " - "value of this field is taken from Chart.yaml within the helm package. " - "Please fix up the helm package. Before removing invalid characters" - f", the releaseName was {self.chartName}." - ) - - -@dataclass -class ImageInfo: - parameter: List[str] - name: str - version: str - - -class CnfNfdGenerator(NFDGenerator): # pylint: disable=too-many-instance-attributes - """ - CNF NFD Generator. - - This takes a config file, and outputs: - - A bicep file for the NFDV - - Parameters files that are used by the NFDV bicep file, these are the - deployParameters and the mapping profiles of those deploy parameters - - A bicep file for the Artifact manifests - """ - - def __init__(self, config: CNFConfiguration, interactive: bool = False): - """ - Create a new CNF NFD Generator. - - Interactive parameter is only used if the user wants to generate the values - mapping file from the values.yaml in the helm package, and also requires the - mapping file in config to be blank. - """ - self.config = config - self.nfd_jinja2_template_path = ( - Path(__file__).parent - / "templates" - / CNF_DEFINITION_JINJA2_SOURCE_TEMPLATE_FILENAME - ) - self.manifest_jinja2_template_path = ( - Path(__file__).parent - / "templates" - / CNF_MANIFEST_JINJA2_SOURCE_TEMPLATE_FILENAME - ) - self.output_directory: Path = self.config.output_directory_for_build - self._cnfd_bicep_path = ( - self.output_directory / CNF_DEFINITION_BICEP_TEMPLATE_FILENAME - ) - self._tmp_dir: Optional[Path] = None - - self.artifacts: List[Artifact] = [] - self.nf_application_configurations: List[NFApplicationConfiguration] = [] - self.deployment_parameter_schema: Dict[str, Any] = SCHEMA_PREFIX - self.interactive = interactive - - def generate_nfd(self) -> None: - """Generate a CNF NFD which comprises a group, an Artifact Manifest and an NFDV.""" - - # Create temporary directory. - with tempfile.TemporaryDirectory() as tmpdirname: - self._tmp_dir = Path(tmpdirname) - 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 - - # Create a chart mapping schema if none has been passed in. - if not helm_package.path_to_mappings: - self._generate_chart_value_mappings(helm_package) - - # Get schema for each chart - # (extract mappings and relevant parts of the schema) - # + Add that schema to the big schema. - self.deployment_parameter_schema["properties"].update( - self._get_chart_mapping_schema(helm_package) - ) - - # Get all image line matches for files in the chart. - # Do this here so we don't have to do it multiple times. - image_line_matches = self._find_image_parameter_from_chart( - helm_package - ) - - # Creates a flattened list of image registry paths to prevent set error - image_registry_paths: List[str] = [] - for image_info in image_line_matches: - image_registry_paths += image_info.parameter - - # Generate the NF application configuration for the chart - # passed to jinja2 renderer to render bicep template - self.nf_application_configurations.append( - self._generate_nf_application_config( - helm_package, - image_registry_paths, - self._find_image_pull_secrets_parameter_from_chart( - helm_package - ), - ) - ) - # Workout the list of artifacts for the chart and - # update the list for the NFD with any unique artifacts. - chart_artifacts = self._get_artifact_list( - helm_package, image_line_matches - ) - self.artifacts += [ - a for a in chart_artifacts if a not in self.artifacts - ] - self._write_nfd_bicep_file() - self._write_schema_to_file() - self._write_manifest_bicep_file() - self._copy_to_output_directory() - print( - f"Generated NFD bicep template created in {self.output_directory}" - ) - print( - "Please review these templates. When you are happy with them run " - "`az aosm nfd publish` with the same arguments." - ) - except InvalidTemplateError as e: - raise e - - @property - def nfd_bicep_path(self) -> Optional[Path]: - """Returns the path to the bicep file for the NFD if it has been created.""" - if self._cnfd_bicep_path.exists(): - return self._cnfd_bicep_path - return None - - def _extract_chart(self, path: Path) -> None: - """ - Extract the chart into the tmp directory. - - :param path: The path to helm package - """ - assert self._tmp_dir - - logger.debug("Extracting helm package %s", path) - - file_extension = path.suffix - if file_extension in (".gz", ".tgz"): - with tarfile.open(path, "r:gz") as tar: - tar.extractall(path=self._tmp_dir) - - elif file_extension == ".tar": - with tarfile.open(path, "r:") as tar: - tar.extractall(path=self._tmp_dir) - - else: - raise InvalidTemplateError( - f"ERROR: The helm package '{path}' is not a .tgz, .tar or .tar.gz file." - " Please fix this and run the command again." - ) - - def _generate_chart_value_mappings(self, helm_package: HelmPackageConfig) -> None: - """ - Optional function to create a chart value mappings file with every value being a deployParameter. - - Expected use when a helm chart is very simple and user wants every value to be a - deployment parameter. - """ - assert self._tmp_dir - logger.debug( - "Creating chart value mappings file for %s", helm_package.path_to_chart - ) - print(f"Creating chart value mappings file for {helm_package.path_to_chart}.") - - # Get all the values files in the chart - top_level_values_yaml = self._read_top_level_values_yaml(helm_package) - - mapping_to_write = self._replace_values_with_deploy_params( - top_level_values_yaml, None - ) - - # Write the mapping to a file - mapping_directory: Path = self._tmp_dir / GENERATED_VALUES_MAPPINGS_DIR_NAME - mapping_directory.mkdir(exist_ok=True) - mapping_filepath = ( - mapping_directory / f"{helm_package.name}-generated-mapping.yaml" - ) - with open(mapping_filepath, "w", encoding="UTF-8") as mapping_file: - yaml.dump(mapping_to_write, mapping_file) - - # Update the config that points to the mapping file - helm_package.path_to_mappings = str(mapping_filepath) - - def _read_top_level_values_yaml( - self, helm_package: HelmPackageConfig - ) -> Dict[str, Any]: - """ - Return a dictionary of the values.yaml|yml read from the root of the helm package. - - :param helm_package: The helm package to look in - :type helm_package: HelmPackageConfig - :raises FileOperationError: if no values.yaml|yml found - :return: A dictionary of the yaml read from the file - :rtype: Dict[str, Any] - """ - assert self._tmp_dir - for file in Path(self._tmp_dir / helm_package.name).iterdir(): - if file.name in ("values.yaml", "values.yml"): - with file.open(encoding="UTF-8") as values_file: - values_yaml = yaml.safe_load(values_file) - return values_yaml - - raise FileOperationError( - "Cannot find top level values.yaml/.yml file in Helm package." - ) - - def _write_manifest_bicep_file(self) -> None: - """Write the bicep file for the Artifact Manifest to the temp directory.""" - assert self._tmp_dir - - with open(self.manifest_jinja2_template_path, "r", encoding="UTF-8") as f: - template: Template = Template( - f.read(), - undefined=StrictUndefined, - ) - - bicep_contents: str = template.render( - artifacts=self.artifacts, - ) - - path = self._tmp_dir / CNF_MANIFEST_BICEP_TEMPLATE_FILENAME - with open(path, "w", encoding="utf-8") as f: - f.write(bicep_contents) - - logger.info("Created artifact manifest bicep template: %s", path) - - def _write_nfd_bicep_file(self) -> None: - """Write the bicep file for the NFD to the temp directory.""" - assert self._tmp_dir - with open(self.nfd_jinja2_template_path, "r", encoding="UTF-8") as f: - template: Template = Template( - f.read(), - undefined=StrictUndefined, - ) - - bicep_contents: str = template.render( - nf_application_configurations=self.nf_application_configurations, - ) - - path = self._tmp_dir / CNF_DEFINITION_BICEP_TEMPLATE_FILENAME - with open(path, "w", encoding="utf-8") as f: - f.write(bicep_contents) - - logger.info("Created NFD bicep template: %s", path) - - def _write_schema_to_file(self) -> None: - """Write the schema to file deploymentParameters.json to the temp directory.""" - logger.debug("Create deploymentParameters.json") - assert self._tmp_dir - - full_schema = self._tmp_dir / DEPLOYMENT_PARAMETERS_FILENAME - with open(full_schema, "w", encoding="UTF-8") as f: - json.dump(self.deployment_parameter_schema, f, indent=4) - - logger.debug("%s created", full_schema) - - def _copy_to_output_directory(self) -> None: - """ - Copy files from the temp directory to the output directory. - - Files are the config mappings, schema and bicep templates (artifact manifest and - NFDV). - """ - assert self._tmp_dir - - logger.info("Create NFD bicep %s", self.output_directory) - - Path(self.output_directory / SCHEMAS_DIR_NAME).mkdir( - parents=True, exist_ok=True - ) - - # Copy the nfd and the manifest bicep files to the output directory - shutil.copy( - self._tmp_dir / CNF_DEFINITION_BICEP_TEMPLATE_FILENAME, - self.output_directory, - ) - shutil.copy( - self._tmp_dir / CNF_MANIFEST_BICEP_TEMPLATE_FILENAME, self.output_directory - ) - - # Copy any generated values mappings YAML files to the corresponding directory in - # the output directory so that the user can edit them and re-run the build if - # required - if Path(self._tmp_dir / GENERATED_VALUES_MAPPINGS_DIR_NAME).exists(): - shutil.copytree( - self._tmp_dir / GENERATED_VALUES_MAPPINGS_DIR_NAME, - self.output_directory / GENERATED_VALUES_MAPPINGS_DIR_NAME, - ) - - # Copy the JSON config mappings and deploymentParameters schema that are used - # for the NFD to the output directory - shutil.copytree( - self._tmp_dir / CONFIG_MAPPINGS_DIR_NAME, - self.output_directory / CONFIG_MAPPINGS_DIR_NAME, - dirs_exist_ok=True, - ) - shutil.copy( - self._tmp_dir / DEPLOYMENT_PARAMETERS_FILENAME, - self.output_directory / SCHEMAS_DIR_NAME / DEPLOYMENT_PARAMETERS_FILENAME, - ) - - logger.info("Copied files to %s", self.output_directory) - - def _generate_nf_application_config( - self, - helm_package: HelmPackageConfig, - image_registry_path: List[str], - image_pull_secret_line_matches: List[str], - ) -> NFApplicationConfiguration: - """Generate NF application config.""" - (name, version) = self._get_chart_name_and_version(helm_package) - - registry_values_paths = set(image_registry_path) - image_pull_secrets_values_paths = set(image_pull_secret_line_matches) - - return NFApplicationConfiguration( - name=helm_package.name, - chartName=name, - chartVersion=version, - releaseName=name, - dependsOnProfile=helm_package.depends_on, - registryValuesPaths=list(registry_values_paths), - imagePullSecretsValuesPaths=list(image_pull_secrets_values_paths), - valueMappingsFile=self._jsonify_value_mappings(helm_package), - ) - - @staticmethod - def _find_yaml_files(directory: Path) -> Iterator[Path]: - """ - Find all yaml files recursively in given directory. - - :param directory: The directory to search. - """ - yield from directory.glob("**/*.yaml") - yield from directory.glob("**/*.yml") - - def _find_image_parameter_from_chart( - self, helm_package_config: HelmPackageConfig - ) -> List[ImageInfo]: - """ - Find pattern matches in Helm chart for the names of the image parameters. - - :param helm_package: The helm package config. - - Returns list of tuples containing the list of image - paths and the name and version of the image. e.g. (Values.foo.bar.repoPath, foo, - 1.2.3) - """ - assert self._tmp_dir - chart_dir = self._tmp_dir / helm_package_config.name - matches = [] - path = [] - - for file in self._find_yaml_files(chart_dir): - with open(file, "r", encoding="UTF-8") as f: - logger.debug("Searching for %s in %s", IMAGE_START_STRING, file) - for line in f: - if IMAGE_START_STRING in line: - logger.debug("Found %s in %s", IMAGE_START_STRING, line) - path = re.findall(IMAGE_PATH_REGEX, line) - - # If "image:", search for chart name and version - name_and_version = re.search(IMAGE_NAME_AND_VERSION_REGEX, line) - logger.debug( - "Regex match for name and version is %s", - name_and_version, - ) - - if name_and_version and len(name_and_version.groups()) == 2: - logger.debug( - "Found image name and version %s %s", - name_and_version.group("name"), - name_and_version.group("version"), - ) - matches.append( - ImageInfo( - path, - name_and_version.group("name"), - name_and_version.group("version"), - ) - ) - else: - logger.debug("No image name and version found") - return matches - - def _find_image_pull_secrets_parameter_from_chart( - self, helm_package_config: HelmPackageConfig - ) -> List[str]: - """ - Find pattern matches in Helm chart for the ImagePullSecrets parameter. - - :param helm_package: The helm package config. - - Returns list of lists containing image pull - secrets paths, e.g. Values.foo.bar.imagePullSecret - """ - assert self._tmp_dir - chart_dir = self._tmp_dir / helm_package_config.name - matches = [] - path = [] - - for file in self._find_yaml_files(chart_dir): - with open(file, "r", encoding="UTF-8") as f: - logger.debug( - "Searching for %s in %s", IMAGE_PULL_SECRETS_START_STRING, file - ) - for line in f: - if IMAGE_PULL_SECRETS_START_STRING in line: - logger.debug( - "Found %s in %s", IMAGE_PULL_SECRETS_START_STRING, line - ) - path = re.findall(IMAGE_PATH_REGEX, line) - matches += path - return matches - - def _get_artifact_list( - self, - helm_package: HelmPackageConfig, - image_line_matches: List[ImageInfo], - ) -> List[Artifact]: - """ - Get the list of artifacts for the chart. - - :param helm_package: The helm package config. - :param image_line_matches: The list of image line matches. - """ - artifact_list = [] - (name, version) = self._get_chart_name_and_version(helm_package) - helm_artifact = Artifact(name, version) - - artifact_list.append(helm_artifact) - for image_info in image_line_matches: - artifact_list.append(Artifact(image_info.name, image_info.version)) - - return artifact_list - - def _get_chart_mapping_schema( - self, helm_package: HelmPackageConfig - ) -> Dict[Any, Any]: - """ - Get the schema for the non default values (those with {deploymentParameter...}). - Based on the user provided values schema. - - param helm_package: The helm package config. - """ - assert self._tmp_dir - logger.debug("Get chart mapping schema for %s", helm_package.name) - - mappings_path = helm_package.path_to_mappings - values_schema = self._tmp_dir / helm_package.name / CNF_VALUES_SCHEMA_FILENAME - if not Path(mappings_path).exists(): - raise InvalidTemplateError( - f"ERROR: The helm package '{helm_package.name}' does not have a valid values" - " mappings file. The file at '{helm_package.path_to_mappings}' does not exist." - "\nPlease fix this and run the command again." - ) - if not values_schema.exists(): - raise InvalidTemplateError( - f"ERROR: The helm package '{helm_package.name}' is missing {CNF_VALUES_SCHEMA_FILENAME}." - "\nPlease fix this and run the command again." - ) - - with open(mappings_path, "r", encoding="utf-8") as stream: - values_data = yaml.load(stream, Loader=yaml.SafeLoader) - - with open(values_schema, "r", encoding="utf-8") as f: - schema_data = json.load(f) - - try: - deploy_params_dict = self.traverse_dict( - values_data, DEPLOYMENT_PARAMETER_MAPPING_REGEX - ) - logger.debug("Deploy params dict is %s", deploy_params_dict) - new_schema = self.search_schema(deploy_params_dict, schema_data) - except KeyError as e: - raise InvalidTemplateError( - "ERROR: There is a problem with your schema or values for the helm" - f" package '{helm_package.name}'." - "\nPlease fix this and run the command again." - ) from e - - logger.debug("Generated chart mapping schema for %s", helm_package.name) - return new_schema - - @staticmethod - def traverse_dict( - dict_to_search: Dict[Any, Any], target_regex: str - ) -> Dict[str, List[str]]: - """ - Traverse the dictionary provided and return a dictionary of all the values that match the target regex, - with the key being the deploy parameter and the value being the path (as a list) to the value. - e.g. {"foo": ["global", "foo", "bar"]} - - :param d: The dictionary to traverse. - :param target: The regex to search for. - """ - - # pylint: disable=too-many-nested-blocks - @dataclass - class DictNode: - # The dictionary under this node - sub_dict: Dict[Any, Any] - - # The path to this node under the main dictionary - position_path: List[str] - - # Initialize the stack with the dictionary and an empty path - stack: List[DictNode] = [DictNode(dict_to_search, [])] - result = {} # Initialize empty dictionary to store the results - while stack: # While there are still items in the stack - # Pop the last item from the stack and unpack it into node (the dictionary) and path - node = stack.pop() - - # For each key-value pair in the popped item - for key, value in node.sub_dict.items(): - # If the value is a dictionary - if isinstance(value, dict): - # Add the dictionary to the stack with the path - stack.append(DictNode(value, node.position_path + [key])) - - # If the value is a string + matches target regex - elif isinstance(value, str): - # Take the match i.e, foo from {deployParameter.foo} - match = re.search(target_regex, value) - - # Add it to the result dictionary with its path as the value - if match: - result[match.group(1)] = node.position_path + [key] - - elif isinstance(value, list): - logger.debug("Found a list %s", value) - for item in value: - logger.debug("Found an item %s", item) - - if isinstance(item, str): - match = re.search(target_regex, item) - - if match: - result[match.group(1)] = node.position_path + [key] - - elif isinstance(item, dict): - stack.append(DictNode(item, node.position_path + [key])) - - elif isinstance(item, list): - # We should fix this but for now just log a warning and - # carry on - logger.warning( - "Values mapping file contains a list of lists " - "at path %s, which this tool cannot parse. " - "Please check the output configMappings and schemas " - "files and check that they are as required.", - node.position_path + [key], - ) - return result - - @staticmethod - def search_schema( - deployParams_paths: Dict[str, List[str]], full_schema - ) -> Dict[str, Dict[str, str]]: - """ - Search through the provided schema for the types of the deployment parameters. - This assumes that the type of the key will be the type of the deployment parameter. - e.g. if foo: {deployParameter.bar} and foo is type string, then bar is type string. - - Returns a dictionary of the deployment parameters in the format: - {"foo": {"type": "string"}, "bar": {"type": "string"}} - - param deployParams_paths: a dictionary of all the deploy parameters to search for, - with the key being the deploy parameter and the value being the - path to the value. - e.g. {"foo": ["global", "foo", "bar"]} - param full_schema: The schema to search through. - """ - new_schema = {} - no_schema_list = [] - for deploy_param, path_list in deployParams_paths.items(): - logger.debug( - "Searching for %s in schema at path %s", deploy_param, path_list - ) - node = full_schema - for path in path_list: - if "properties" in node.keys(): - logger.debug( - "Searching properties for %s in schema at path %s", - deploy_param, - path, - ) - node = node["properties"][path] - else: - logger.debug("No schema node found for %s", deploy_param) - no_schema_list.append(deploy_param) - new_schema.update({deploy_param: {"type": "string"}}) - if deploy_param not in new_schema: - param_type = node.get("type", None) - if param_type == "array": - # If the type is an array, we need to get the type of the items. - # (This currently only supports a single type, not a list of types. - # If a list is provided, we default to string.) - array_item_schema = node.get("items", {}) - if isinstance(array_item_schema, dict): - param_type = array_item_schema.get("type", None) - else: - logger.debug("Array item schema is not a dict (probably a list)") - param_type = None - if not param_type: - logger.debug("No type found for %s", deploy_param) - no_schema_list.append(deploy_param) - param_type = "string" - new_schema.update({deploy_param: {"type": param_type}}) - if no_schema_list: - logger.warning( - "No schema or type found for deployment parameter(s): %s", no_schema_list - ) - logger.warning( - "We default these parameters to type string. " - "Please edit schemas/%s in the output before publishing " - "if this is wrong", - DEPLOYMENT_PARAMETERS_FILENAME, - ) - return new_schema - - def _replace_values_with_deploy_params( - self, - values_yaml_dict, - param_prefix: Optional[str] = None, - ) -> Dict[Any, Any]: - """ - Given the yaml dictionary read from values.yaml, replace all the values with {deploymentParameter.keyname}. - - Thus creating a values mapping file if the user has not provided one in config. - """ - logger.debug("Replacing values with deploy parameters") - final_values_mapping_dict: Dict[Any, Any] = {} - for k, v in values_yaml_dict.items(): # pylint: disable=too-many-nested-blocks - # if value is a string and contains deployParameters. - logger.debug("Processing key %s", k) - param_name = k if param_prefix is None else f"{param_prefix}_{k}" - if isinstance(v, dict): - final_values_mapping_dict[k] = self._replace_values_with_deploy_params( - v, param_name - ) - elif isinstance(v, list): - final_values_mapping_dict[k] = [] - for index, item in enumerate(v): - param_name = ( - f"{param_prefix}_{k}_{index}" - if param_prefix - else f"{k}_{index}" - ) - if isinstance(item, dict): - final_values_mapping_dict[k].append( - self._replace_values_with_deploy_params(item, param_name) - ) - elif isinstance(item, (str, int, bool)) or not item: - if self.interactive: - if not input_ack( - "y", f"Expose parameter {param_name}? y/n " - ): - logger.debug("Excluding parameter %s", param_name) - final_values_mapping_dict[k].append(item) - continue - replacement_value = f"{{deployParameters.{param_name}}}" - final_values_mapping_dict[k].append(replacement_value) - else: - raise ValueError( - f"Found an unexpected type {type(item)} of key {k} in " - "values.yaml, cannot generate values mapping file." - ) - elif isinstance(v, (str, int, bool)) or not v: - # Replace the parameter with {deploymentParameter.keyname} - # If v is blank we don't know what type it is. Assuming it is an - # empty string (but do this after checking for dict and list) - if self.interactive: - # Interactive mode. Prompt user to include or exclude parameters - # This requires the enter key after the y/n input which isn't ideal - if not input_ack("y", f"Expose parameter {param_name}? y/n "): - logger.debug("Excluding parameter %s", param_name) - final_values_mapping_dict.update({k: v}) - continue - replacement_value = f"{{deployParameters.{param_name}}}" - - # add the schema for k (from the big schema) to the (smaller) schema - final_values_mapping_dict.update({k: replacement_value}) - else: - raise ValueError( - f"Found an unexpected type {type(v)} of key {k} in values.yaml, " - "cannot generate values mapping file." - ) - - return final_values_mapping_dict - - def _get_chart_name_and_version( - self, helm_package: HelmPackageConfig - ) -> Tuple[str, str]: - """Get the name and version of the chart.""" - assert self._tmp_dir - chart_path = self._tmp_dir / helm_package.name / "Chart.yaml" - - if not chart_path.exists(): - raise InvalidTemplateError( - f"There is no Chart.yaml file in the helm package '{helm_package.name}'. " - "\nPlease fix this and run the command again." - ) - - with open(chart_path, "r", encoding="utf-8") as f: - data = yaml.load(f, Loader=yaml.FullLoader) - if "name" in data and "version" in data: - chart_name = data["name"] - chart_version = data["version"] - else: - raise FileOperationError( - "A name or version is missing from Chart.yaml in the helm package" - f" '{helm_package.name}'." - "\nPlease fix this and run the command again." - ) - - return (chart_name, chart_version) - - def _jsonify_value_mappings(self, helm_package: HelmPackageConfig) -> str: - """Yaml->JSON values mapping file, then return the filename.""" - assert self._tmp_dir - mappings_yaml_file = helm_package.path_to_mappings - mappings_dir = self._tmp_dir / CONFIG_MAPPINGS_DIR_NAME - mappings_output_file = mappings_dir / f"{helm_package.name}-mappings.json" - - mappings_dir.mkdir(exist_ok=True) - - with open(mappings_yaml_file, "r", encoding="utf-8") as f: - data = yaml.load(f, Loader=yaml.FullLoader) - - with open(mappings_output_file, "w", encoding="utf-8") as file: - json.dump(data, file, indent=4) - - logger.debug("Generated parameter mappings for %s", helm_package.name) - return f"{helm_package.name}-mappings.json" diff --git a/src/aosm/azext_aosm/generate_nfd/nfd_generator_base.py b/src/aosm/azext_aosm/generate_nfd/nfd_generator_base.py deleted file mode 100644 index 4141665dfdf..00000000000 --- a/src/aosm/azext_aosm/generate_nfd/nfd_generator_base.py +++ /dev/null @@ -1,25 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains a base class for generating NFDs.""" -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Optional - -from knack.log import get_logger - -logger = get_logger(__name__) - - -class NFDGenerator(ABC): - """A class for generating an NFD from a config file.""" - - @abstractmethod - def generate_nfd(self) -> None: - ... - - @property - @abstractmethod - def nfd_bicep_path(self) -> Optional[Path]: - ... diff --git a/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py b/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py deleted file mode 100644 index e415817594e..00000000000 --- a/src/aosm/azext_aosm/generate_nfd/vnf_nfd_generator.py +++ /dev/null @@ -1,340 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains a class for generating VNF NFDs and associated resources.""" - -import json -import shutil -import tempfile -from functools import cached_property -from pathlib import Path -from typing import Any, Dict, Optional - -from knack.log import get_logger - -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, - DEPLOYMENT_PARAMETERS_FILENAME, - EXTRA_VHD_PARAMETERS, - OPTIONAL_DEPLOYMENT_PARAMETERS_FILENAME, - OPTIONAL_DEPLOYMENT_PARAMETERS_HEADING, - SCHEMA_PREFIX, - SCHEMAS_DIR_NAME, - TEMPLATE_PARAMETERS_FILENAME, - VHD_PARAMETERS_FILENAME, - VNF_DEFINITION_BICEP_TEMPLATE_FILENAME, - VNF_MANIFEST_BICEP_TEMPLATE_FILENAME, -) -from azext_aosm.util.utils import input_ack, snake_case_to_camel_case - -logger = get_logger(__name__) - -# Different types are used in ARM templates and NFDs. The list accepted by NFDs is -# documented in the AOSM meta-schema. This will be published in the future but for now -# can be found in -# https://microsoft.sharepoint.com/:w:/t/NSODevTeam/Ec7ovdKroSRIv5tumQnWIE0BE-B2LykRcll2Qb9JwfVFMQ -ARM_TO_JSON_PARAM_TYPES: Dict[str, str] = { - "int": "integer", - "securestring": "string", - "bool": "boolean", -} - - -class VnfNfdGenerator(NFDGenerator): - # pylint: disable=too-many-instance-attributes - """ - VNF NFD Generator. - - This takes a source ARM template and a config file, and outputs: - - A bicep file for the NFDV - - Parameters files that are used by the NFDV bicep file, these are the - deployParameters and the mapping profiles of those deploy parameters - - A bicep file for the Artifact manifests - - @param order_params: whether to order the deployment and template output parameters - with those without a default first, then those with a default. - Those without a default will definitely be required to be - exposed, those with a default may not be. - @param interactive: whether to prompt the user to confirm the parameters to be - exposed. - """ - - 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 - - self._vnfd_bicep_path = Path( - self.output_directory, VNF_DEFINITION_BICEP_TEMPLATE_FILENAME - ) - self._manifest_bicep_path = Path( - self.output_directory, VNF_MANIFEST_BICEP_TEMPLATE_FILENAME - ) - self.order_params = order_params - self.interactive = interactive - self._tmp_dir: Optional[Path] = None - self.image_name = f"{self.config.nf_name}Image" - - def generate_nfd(self) -> None: - """ - Generate a VNF NFD which comprises an group, an Artifact Manifest and a NFDV. - - Create a bicep template for an NFD from the ARM template for the VNF. - """ - # Create temporary directory. - with tempfile.TemporaryDirectory() as tmpdirname: - self._tmp_dir = Path(tmpdirname) - - self._create_parameter_files() - self._copy_to_output_directory() - print( - f"Generated NFD bicep templates created in {self.output_directory}" - ) - print( - "Please review these templates. When you are happy with them run " - "`az aosm nfd publish` with the same arguments." - ) - - @property - def nfd_bicep_path(self) -> Optional[Path]: - """Returns the path to the bicep file for the NFD if it has been created.""" - if self._vnfd_bicep_path.exists(): - return self._vnfd_bicep_path - return None - - @property - def manifest_bicep_path(self) -> Optional[Path]: - """Returns the path to the bicep file for the NFD if it has been created.""" - if self._manifest_bicep_path.exists(): - return self._manifest_bicep_path - return None - - @cached_property - def vm_parameters(self) -> Dict[str, Any]: - """The parameters from the VM ARM template.""" - with open(self.arm_template_path, "r", encoding="utf-8") as _file: - data = json.load(_file) - if "parameters" in data: - parameters: Dict[str, Any] = data["parameters"] - else: - print( - "No parameters found in the template provided. " - "Your NFD will have no deployParameters" - ) - parameters = {} - - return parameters - - @property - def vm_parameters_ordered(self) -> Dict[str, Any]: - """The parameters from the VM ARM template, ordered as those without defaults then those with.""" - vm_parameters_no_default: Dict[str, Any] = {} - vm_parameters_with_default: Dict[str, Any] = {} - has_default_field: bool = False - has_default: bool = False - - for key in self.vm_parameters: - # Order parameters into those with and without defaults - has_default_field = "defaultValue" in self.vm_parameters[key] - has_default = ( - has_default_field - and not self.vm_parameters[key]["defaultValue"] == "" - ) - - if has_default: - vm_parameters_with_default[key] = self.vm_parameters[key] - else: - vm_parameters_no_default[key] = self.vm_parameters[key] - - return {**vm_parameters_no_default, **vm_parameters_with_default} - - def _create_parameter_files(self) -> None: - """Create the deployment, template and VHD parameter files.""" - assert self._tmp_dir - tmp_schemas_directory: Path = self._tmp_dir / SCHEMAS_DIR_NAME - tmp_schemas_directory.mkdir() - self.write_deployment_parameters(tmp_schemas_directory) - - tmp_mappings_directory: Path = self._tmp_dir / CONFIG_MAPPINGS_DIR_NAME - tmp_mappings_directory.mkdir() - self.write_template_parameters(tmp_mappings_directory) - self.write_vhd_parameters(tmp_mappings_directory) - - def write_deployment_parameters(self, directory: Path) -> None: - """ - Write out the NFD deploymentParameters.json file to `directory` - - :param directory: The directory to put this file in. - """ - logger.debug("Create deploymentParameters.json") - - nfd_parameters = {} - nfd_parameters_with_default = {} - vm_parameters_to_exclude = [] - - vm_parameters = ( - self.vm_parameters_ordered - if self.order_params - else self.vm_parameters - ) - - for key in vm_parameters: - if key == self.config.image_name_parameter: - # There is only one correct answer for the image name, so don't ask the - # user, instead it is hardcoded in config mappings. - continue - - # Order parameters into those without and then with defaults - has_default_field = "defaultValue" in self.vm_parameters[key] - has_default = ( - has_default_field - and not self.vm_parameters[key]["defaultValue"] == "" - ) - - if self.interactive and has_default: - # Interactive mode. Prompt user to include or exclude parameters - # This requires the enter key after the y/n input which isn't ideal - if not input_ack("y", f"Expose parameter {key}? y/n "): - logger.debug("Excluding parameter %s", key) - vm_parameters_to_exclude.append(key) - continue - - # Map ARM parameter types to JSON parameter types accepted by AOSM - arm_type = self.vm_parameters[key]["type"] - json_type = ARM_TO_JSON_PARAM_TYPES.get(arm_type.lower(), arm_type) - - if has_default: - nfd_parameters_with_default[key] = {"type": json_type} - - nfd_parameters[key] = {"type": json_type} - - # Now we are out of the vm_parameters loop, we can remove the excluded - # parameters so they don't get included in templateParameters.json - # Remove from both ordered and unordered dicts - for key in vm_parameters_to_exclude: - self.vm_parameters.pop(key, None) - - deployment_parameters_path = directory / DEPLOYMENT_PARAMETERS_FILENAME - - # Heading for the deployParameters schema - deploy_parameters_full: Dict[str, Any] = SCHEMA_PREFIX - deploy_parameters_full["properties"].update(nfd_parameters) - - with open(deployment_parameters_path, "w", encoding="utf-8") as _file: - _file.write(json.dumps(deploy_parameters_full, indent=4)) - - logger.debug("%s created", deployment_parameters_path) - if self.order_params: - print( - "Deployment parameters for the generated NFDV are ordered by those " - "without defaults first to make it easier to choose which to expose." - ) - - # Extra output file to help the user know which parameters are optional - if not self.interactive: - if nfd_parameters_with_default: - optional_deployment_parameters_path = ( - directory / OPTIONAL_DEPLOYMENT_PARAMETERS_FILENAME - ) - with open( - optional_deployment_parameters_path, "w", encoding="utf-8" - ) as _file: - _file.write(OPTIONAL_DEPLOYMENT_PARAMETERS_HEADING) - _file.write( - json.dumps(nfd_parameters_with_default, indent=4) - ) - print( - "Optional ARM parameters detected. Created " - f"{OPTIONAL_DEPLOYMENT_PARAMETERS_FILENAME} to help you choose which " - "to expose." - ) - - def write_template_parameters(self, directory: Path) -> None: - """ - Write out the NFD templateParameters.json file to `directory`. - - :param directory: The directory to put this file in. - """ - logger.debug("Create %s", TEMPLATE_PARAMETERS_FILENAME) - vm_parameters = ( - self.vm_parameters_ordered - if self.order_params - else self.vm_parameters - ) - - template_parameters = {} - - for key in vm_parameters: - if key == self.config.image_name_parameter: - template_parameters[key] = self.image_name - continue - - template_parameters[key] = f"{{deployParameters.{key}}}" - - template_parameters_path = directory / TEMPLATE_PARAMETERS_FILENAME - - with open(template_parameters_path, "w", encoding="utf-8") as _file: - _file.write(json.dumps(template_parameters, indent=4)) - - logger.debug("%s created", template_parameters_path) - - def write_vhd_parameters(self, directory: Path) -> None: - """ - Write out the NFD vhdParameters.json file to `directory`. - - :param directory: The directory to put this file in. - """ - vhd_config = self.config.vhd - # vhdImageMappingRuleProfile userConfiguration within the NFDV API accepts azureDeployLocation - # as the location where the image resource should be created from the VHD. The CLI does not - # expose this as it defaults to the NF deploy location, and we can't think of situations where - # it should be different. - vhd_parameters = { - "imageName": self.image_name, - **{ - snake_case_to_camel_case(key): value - for key, value in vhd_config.__dict__.items() - if key in EXTRA_VHD_PARAMETERS and value is not None - }, - } - - vhd_parameters_path = directory / VHD_PARAMETERS_FILENAME - with open(vhd_parameters_path, "w", encoding="utf-8") as _file: - _file.write(json.dumps(vhd_parameters, indent=4)) - - logger.debug("%s created", vhd_parameters_path) - - def _copy_to_output_directory(self) -> None: - """Copy the static bicep templates and generated config mappings and schema into the build output directory.""" - logger.info("Create NFD bicep %s", self.output_directory) - assert self._tmp_dir - Path(self.output_directory).mkdir(exist_ok=True) - - static_bicep_templates_dir = Path(__file__).parent / "templates" - - static_vnfd_bicep_path = ( - static_bicep_templates_dir / VNF_DEFINITION_BICEP_TEMPLATE_FILENAME - ) - shutil.copy(static_vnfd_bicep_path, self.output_directory) - - static_manifest_bicep_path = ( - static_bicep_templates_dir / VNF_MANIFEST_BICEP_TEMPLATE_FILENAME - ) - shutil.copy(static_manifest_bicep_path, self.output_directory) - # Copy everything in the temp directory to the output directory - shutil.copytree( - self._tmp_dir, - self.output_directory, - dirs_exist_ok=True, - ) - - logger.info("Copied files to %s", self.output_directory) diff --git a/src/aosm/azext_aosm/generate_nsd/nf_ret.py b/src/aosm/azext_aosm/generate_nsd/nf_ret.py deleted file mode 100644 index 69589e69242..00000000000 --- a/src/aosm/azext_aosm/generate_nsd/nf_ret.py +++ /dev/null @@ -1,185 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Handles the creation of a resource element template for a network function.""" - -import json - -from typing import Dict, Any, List, Union -from knack.log import get_logger - -from azext_aosm._configuration import NFDRETConfiguration -from azext_aosm.util.constants import CNF, VNF -from azext_aosm.util.management_clients import ApiClients -from azext_aosm.vendored_sdks.models import NetworkFunctionDefinitionVersion, NFVIType - - -logger = get_logger(__name__) - - -class NFRETGenerator: - """Represents a single network function resource element template within an NSD.""" - - def __init__( - self, api_clients: ApiClients, config: NFDRETConfiguration, cg_schema_name: str - ) -> None: - self.config = config - self.cg_schema_name = cg_schema_name - nfdv = self._get_nfdv(config, api_clients) - print( - f"Finding the deploy parameters for {self.config.name}:{self.config.version}" - ) - - if not nfdv.properties.deploy_parameters: - raise NotImplementedError( - f"NFDV {self.config.name} has no deploy parameters, cannot generate NSD." - ) - self.deploy_parameters: Dict[str, Any] = json.loads( - nfdv.properties.deploy_parameters - ) - - self.nfd_group_name = self.config.name.replace("-", "_") - self.nfdv_parameter_name = f"{self.nfd_group_name}_nfd_version" - self.config_mapping_filename = f"{self.config.name}_config_mapping.json" - - @staticmethod - def _get_nfdv( - config: NFDRETConfiguration, api_clients: ApiClients - ) -> NetworkFunctionDefinitionVersion: - """Get the existing NFDV resource object.""" - print( - f"Reading existing NFDV resource object {config.version} from group {config.name}" - ) - nfdv_object = api_clients.aosm_client.network_function_definition_versions.get( - resource_group_name=config.publisher_resource_group, - publisher_name=config.publisher, - network_function_definition_group_name=config.name, - network_function_definition_version_name=config.version, - ) - return nfdv_object - - @property - def config_mappings(self) -> Dict[str, Any]: - """ - Return the contents of the config mapping file for this RET. - - Output will look something like: - { - "deploymentParametersObject": { - "deploymentParameters": [ - "{configurationparameters('foo_ConfigGroupSchema').bar.deploymentParameters}" - ] - }, - "nginx_nfdg_nfd_version": "{configurationparameters('foo_ConfigGroupSchema').bar.bar_nfd_version}", - "managedIdentity": "{configurationparameters('foo_ConfigGroupSchema').managedIdentity}", - "customLocationId": "{configurationparameters('foo_ConfigGroupSchema').bar.customLocationId}" - } - """ - nf = self.config.name - - logger.debug("Create %s", self.config_mapping_filename) - - deployment_parameters: Union[ - str, List[str] - ] = f"{{configurationparameters('{self.cg_schema_name}').{nf}.deploymentParameters}}" - - if not self.config.multiple_instances: - assert isinstance(deployment_parameters, str) - deployment_parameters = [deployment_parameters] - - deployment_parameters_object = {"deploymentParameters": deployment_parameters} - - version_parameter = ( - f"{{configurationparameters('{self.cg_schema_name}')." - f"{nf}.{self.nfdv_parameter_name}}}" - ) - - config_mappings = { - "deploymentParametersObject": deployment_parameters_object, - self.nfdv_parameter_name: version_parameter, - "managedIdentity": f"{{configurationparameters('{self.cg_schema_name}').managedIdentity}}", - } - - if self.config.type == CNF: - config_mappings[ - "customLocationId" - ] = f"{{configurationparameters('{self.cg_schema_name}').{nf}.customLocationId}}" - - return config_mappings - - @property - def nf_bicep_substitutions(self) -> Dict[str, Any]: - """Returns the jinja2 parameters for the NF bicep template template.""" - return { - "network_function_name": self.config.name, - "publisher_name": self.config.publisher, - "publisher_resource_group": self.config.publisher_resource_group, - "network_function_definition_group_name": (self.config.name), - "network_function_definition_version_parameter": (self.nfdv_parameter_name), - "network_function_definition_offering_location": ( - self.config.publisher_offering_location - ), - # Ideally we would use the network_function_type from reading the actual - # NF, as we do for deployParameters, but the SDK currently doesn't - # support this and needs to be rebuilt to do so. - "nfvi_type": ( - NFVIType.AZURE_CORE.value # type: ignore[attr-defined] # pylint: disable=no-member - if self.config.type == VNF - else NFVIType.AZURE_ARC_KUBERNETES.value # type: ignore[attr-defined] # pylint: disable=no-member - ), - "CNF": self.config.type == CNF, - } - - @property - def config_schema_snippet(self) -> Dict[str, Any]: - """Return the CGS snippet for this NF.""" - nfdv_version_description_string = ( - f"The version of the {self.config.name} " - "NFD to use. This version must be compatible with (have the same " - "parameters exposed as) " - f"{self.config.name}." - ) - - if self.config.multiple_instances: - deploy_parameters = { - "type": "array", - "items": { - "type": "object", - "properties": self.deploy_parameters["properties"], - }, - } - else: - deploy_parameters = { - "type": "object", - "properties": self.deploy_parameters["properties"], - } - - nf_schema: Dict[str, Any] = { - "type": "object", - "properties": { - "deploymentParameters": deploy_parameters, - self.nfdv_parameter_name: { - "type": "string", - "description": nfdv_version_description_string, - }, - }, - "required": ["deploymentParameters", self.nfdv_parameter_name], - } - - if self.config.type == CNF: - custom_location_description_string = ( - "The custom location ID of the ARC-Enabled AKS Cluster to deploy the CNF " - "to. Should be of the form " - "'/subscriptions/{subscriptionId}/resourcegroups" - "/{resourceGroupName}/providers/microsoft.extendedlocation/" - "customlocations/{customLocationName}'" - ) - - nf_schema["properties"]["customLocationId"] = { - "type": "string", - "description": custom_location_description_string, - } - nf_schema["required"].append("customLocationId") - - return nf_schema diff --git a/src/aosm/azext_aosm/generate_nsd/nsd_generator.py b/src/aosm/azext_aosm/generate_nsd/nsd_generator.py deleted file mode 100644 index adee03677e6..00000000000 --- a/src/aosm/azext_aosm/generate_nsd/nsd_generator.py +++ /dev/null @@ -1,261 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Contains a class for generating NSDs and associated resources.""" -import json -import os -import shutil -import tempfile -from functools import cached_property -from typing import Any, Dict - -from jinja2 import Template -from knack.log import get_logger - -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, - NF_TEMPLATE_JINJA2_SOURCE_TEMPLATE, - NSD_ARTIFACT_MANIFEST_BICEP_FILENAME, - NSD_ARTIFACT_MANIFEST_SOURCE_TEMPLATE_FILENAME, - NSD_BICEP_FILENAME, - NSD_DEFINITION_JINJA2_SOURCE_TEMPLATE, - SCHEMAS_DIR_NAME, - TEMPLATES_DIR_NAME, -) -from azext_aosm.util.management_clients import ApiClients - -logger = get_logger(__name__) - -# Different types are used in Bicep templates and NFDs. The list accepted by NFDs is -# documented in the AOSM meta-schema. This will be published in the future but for now -# can be found in -# https://microsoft.sharepoint.com/:w:/t/NSODevTeam/Ec7ovdKroSRIv5tumQnWIE0BE-B2LykRcll2Qb9JwfVFMQ -NFV_TO_BICEP_PARAM_TYPES: Dict[str, str] = { - "integer": "int", - "boolean": "bool", -} - - -class NSDGenerator: # pylint: disable=too-few-public-methods - """ - NSD Generator. - - This takes a config file and a set of NFDV deploy_parameters and outputs: - - A bicep file for the NSDV - - Parameters files that are used by the NSDV bicep file, these are the - schemas and the mapping profiles of those schemas parameters - - A bicep file for the Artifact manifest - - A bicep and JSON file defining the Network Function that will - be deployed by the NSDV - """ - - 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 = [] - - 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.""" - logger.info("Generate NSD bicep templates") - - # Create temporary folder. - with tempfile.TemporaryDirectory() as tmpdirname: - self._write_config_group_schema_json(tmpdirname) - self._write_config_mapping_files(tmpdirname) - self._write_nsd_manifest(tmpdirname) - self._write_nf_bicep_files(tmpdirname) - self._write_nsd_bicep(tmpdirname) - - self._copy_to_output_folder(tmpdirname) - print( - "Generated NSD bicep templates created in" - f" {self.config.output_directory_for_build}" - ) - print( - "Please review these templates. When you are happy with them run " - "`az aosm nsd publish` with the same arguments." - ) - - @cached_property - def _config_group_schema_dict(self) -> Dict[str, Any]: - """ - :return: The Config Group Schema as a dictionary. - - See src/aosm/azext_aosm/tests/latest/nsd_output/*/schemas for examples of the - output from this function. - """ - managed_identity_description_string = ( - "The managed identity to use to deploy NFs within this SNS. This should " - "be of the form '/subscriptions/{subscriptionId}/resourceGroups/" - "{resourceGroupName}/providers/Microsoft.ManagedIdentity/" - "userAssignedIdentities/{identityName}. " - "If you wish to use a system assigned identity, set this to a blank string." - ) - - properties = { - nf.config.name: nf.config_schema_snippet for nf in self.nf_ret_generators - } - - properties.update( - { - "managedIdentity": { - "type": "string", - "description": managed_identity_description_string, - } - } - ) - - required = [nf.config.name for nf in self.nf_ret_generators] - required.append("managedIdentity") - - cgs_dict: Dict[str, Any] = { - "$schema": "https://json-schema.org/draft-07/schema#", - "title": self.config.cg_schema_name, - "type": "object", - "properties": properties, - "required": required, - } - - return cgs_dict - - def _write_config_group_schema_json(self, output_directory) -> None: - """Create a file containing the json schema for the CGS.""" - temp_schemas_folder_path = os.path.join(output_directory, SCHEMAS_DIR_NAME) - os.mkdir(temp_schemas_folder_path) - - logger.debug("Create %s.json", self.config.cg_schema_name) - - schema_path = os.path.join( - temp_schemas_folder_path, f"{self.config.cg_schema_name}.json" - ) - - with open(schema_path, "w", encoding="utf-8") as _file: - _file.write(json.dumps(self._config_group_schema_dict, indent=4)) - - logger.debug("%s created", schema_path) - - def _write_config_mapping_files(self, output_directory) -> None: - """Write out a config mapping file for each NF.""" - temp_mappings_folder_path = os.path.join( - output_directory, CONFIG_MAPPINGS_DIR_NAME - ) - - os.mkdir(temp_mappings_folder_path) - - for nf in self.nf_ret_generators: - config_mappings_path = os.path.join( - temp_mappings_folder_path, nf.config_mapping_filename - ) - - with open(config_mappings_path, "w", encoding="utf-8") as _file: - _file.write(json.dumps(nf.config_mappings, indent=4)) - - logger.debug("%s created", config_mappings_path) - - def _write_nf_bicep_files(self, output_directory) -> None: - """ - Write bicep files for deploying NFs. - - In the publish step these bicep files will be uploaded to the publisher storage - account as artifacts. - """ - for nf in self.nf_ret_generators: - substitutions = {"location": self.config.location} - substitutions.update(nf.nf_bicep_substitutions) - - self._generate_bicep( - NF_TEMPLATE_JINJA2_SOURCE_TEMPLATE, - os.path.join(output_directory, nf.config.nf_bicep_filename), - substitutions, - ) - - def _write_nsd_bicep(self, output_directory) -> None: - """Write out the NSD bicep file.""" - ret_names = [nf.config.resource_element_name for nf in self.nf_ret_generators] - arm_template_names = [ - nf.config.arm_template.artifact_name for nf in self.nf_ret_generators - ] - config_mapping_files = [ - nf.config_mapping_filename for nf in self.nf_ret_generators - ] - - # We want the armTemplateVersion to be the same as the NSD Version. That means - # that if we create a new NSDV then the existing artifacts won't be overwritten. - params = { - "nfvi_site_name": self.config.nfvi_site_name, - "armTemplateNames": arm_template_names, - "armTemplateVersion": self.config.nsd_version, - "cg_schema_name": self.config.cg_schema_name, - "nsdv_description": self.config.nsdv_description, - "ResourceElementName": ret_names, - "configMappingFiles": config_mapping_files, - "nf_count": len(self.nf_ret_generators), - } - - self._generate_bicep( - self.nsd_bicep_template_name, - os.path.join(output_directory, self.nsd_bicep_output_name), - params, - ) - - def _write_nsd_manifest(self, output_directory) -> None: - """Write out the NSD manifest bicep file.""" - logger.debug("Create NSD manifest") - - self._generate_bicep( - NSD_ARTIFACT_MANIFEST_SOURCE_TEMPLATE_FILENAME, - os.path.join(output_directory, NSD_ARTIFACT_MANIFEST_BICEP_FILENAME), - {}, - ) - - @staticmethod - def _generate_bicep( - template_name: str, output_file_name: str, params: Dict[Any, Any] - ) -> None: - """ - Render the bicep templates with the correct parameters and copy them into the build output folder. - - :param template_name: The name of the template to render - :param output_file_name: The name of the output file - :param params: The parameters to render the template with - """ - - code_dir = os.path.dirname(__file__) - - bicep_template_path = os.path.join(code_dir, TEMPLATES_DIR_NAME, template_name) - - with open(bicep_template_path, "r", encoding="utf-8") as file: - bicep_contents = file.read() - - bicep_template = Template(bicep_contents) - - # Render all the relevant parameters in the bicep template - rendered_template = bicep_template.render(**params) - - with open(output_file_name, "w", encoding="utf-8") as file: - file.write(rendered_template) - - def _copy_to_output_folder(self, temp_dir) -> None: - """Copy the bicep templates, config mappings and schema into the build output folder.""" - - logger.info("Create NSD bicep %s", self.config.output_directory_for_build) - os.mkdir(self.config.output_directory_for_build) - - shutil.copytree( - temp_dir, - self.config.output_directory_for_build, - dirs_exist_ok=True, - ) - - logger.info("Copied files to %s", self.config.output_directory_for_build) diff --git a/src/aosm/azext_aosm/generate_nsd/templates/artifact_manifest_template.bicep b/src/aosm/azext_aosm/generate_nsd/templates/artifact_manifest_template.bicep deleted file mode 100644 index 34ac9ca3fdb..00000000000 --- a/src/aosm/azext_aosm/generate_nsd/templates/artifact_manifest_template.bicep +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. - -// This file creates an Artifact Manifest for a NSD -param location string -@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') -param publisherName string -@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') -param acrArtifactStoreName string -@description('Name of the manifest to deploy for the ACR-backed Artifact Store') -param acrManifestNames array -@description('The name under which to store the ARM template') -param armTemplateNames array -@description('The version that you want to name the NFM template artifact, in format A.B.C. e.g. 6.13.0. If testing for development, you can use any numbers you like.') -param armTemplateVersion string - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup() -} - -resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { - parent: publisher - name: acrArtifactStoreName -} - -resource acrArtifactManifests 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = [for (values, i) in armTemplateNames: { - parent: acrArtifactStore - name: acrManifestNames[i] - location: location - properties: { - artifacts: [ - { - artifactName: armTemplateNames[i] - artifactType: 'ArmTemplate' - artifactVersion: armTemplateVersion - } - ] - } -}] diff --git a/src/aosm/azext_aosm/generate_nsd/templates/nf_template.bicep.j2 b/src/aosm/azext_aosm/generate_nsd/templates/nf_template.bicep.j2 deleted file mode 100644 index 8a53daa9edf..00000000000 --- a/src/aosm/azext_aosm/generate_nsd/templates/nf_template.bicep.j2 +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Highly Confidential Material -// -// The template that the NSD invokes to create the Network Function from a published NFDV. - -@description('Publisher where the NFD is published') -param publisherName string = '{{publisher_name}}' - -@description('Resource group where the NFD publisher exists') -param publisherResourceGroup string = '{{publisher_resource_group}}' - -@description('NFD Group name for the Network Function') -param networkFunctionDefinitionGroupName string = '{{network_function_definition_group_name}}' - -@description('NFD version') -param {{network_function_definition_version_parameter}} string - -@description('The managed identity that should be used to create the NF.') -param managedIdentity string - -{%- if CNF %} -@description('The custom location of the ARC-enabled AKS cluster to create the NF.') -param customLocationId string -{%- endif %} - -param location string = '{{location}}' - -param nfviType string = '{{nfvi_type}}' - -param resourceGroupId string = resourceGroup().id - -@secure() -param deploymentParametersObject object - -var deploymentParameters = deploymentParametersObject.deploymentParameters - -var identityObject = (managedIdentity == '') ? { - type: 'SystemAssigned' -} : { - type: 'UserAssigned' - userAssignedIdentities: { - '${managedIdentity}': {} - } -} - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup(publisherResourceGroup) -} - -resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { - parent: publisher - name: networkFunctionDefinitionGroupName -} - -resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { - parent: nfdg - name: {{network_function_definition_version_parameter}} - -} - -resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deploymentParameters: { - name: '{{network_function_name}}${i}' - location: location - identity: identityObject - properties: { - networkFunctionDefinitionVersionResourceReference: { - id: nfdv.id - idType: 'Open' - } - nfviType: nfviType -{%- if CNF %} - nfviId: customLocationId -{%- else %} - nfviId: resourceGroupId -{%- endif %} - allowSoftwareUpdate: true - configurationType: 'Secret' - secretDeploymentValues: string(values) - } -}] diff --git a/src/aosm/azext_aosm/inputs/__init__.py b/src/aosm/azext_aosm/inputs/__init__.py new file mode 100644 index 00000000000..34913fb394d --- /dev/null +++ b/src/aosm/azext_aosm/inputs/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/aosm/azext_aosm/inputs/arm_template_input.py b/src/aosm/azext_aosm/inputs/arm_template_input.py new file mode 100644 index 00000000000..394dac0afef --- /dev/null +++ b/src/aosm/azext_aosm/inputs/arm_template_input.py @@ -0,0 +1,112 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import copy +import json +from pathlib import Path +from typing import Any, Dict, Optional + +from knack.log import get_logger + +from azext_aosm.common.constants import BASE_SCHEMA +from azext_aosm.inputs.base_input import BaseInput + +logger = get_logger(__name__) + + +class ArmTemplateInput(BaseInput): + """ + A utility class for working with ARM template inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param template_path: The path to the ARM template file. + :type template_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + template_path: Path, + default_config: Optional[Dict[str, Any]] = None, + ): + super().__init__(artifact_name, artifact_version, default_config) + self.template_path = template_path + + def get_defaults(self) -> Dict[str, Any]: + """ + Gets the default values for configuring the input. + + :return: A dictionary containing the default values. + :rtype: Dict[str, Any] + """ + logger.info("Getting default values for ARM template input") + default_config = self.default_config or {} + logger.debug( + "Default values for ARM template input: %s", + json.dumps(default_config, indent=4), + ) + + return copy.deepcopy(default_config) + + def get_schema(self) -> Dict[str, Any]: + """ + Gets the schema for the ARM template input. + + :return: A dictionary containing the schema. + :rtype: Dict[str, Any] + """ + logger.debug("Getting schema for ARM template input %s.", self.artifact_name) + arm_template_schema = copy.deepcopy(BASE_SCHEMA) + with open(self.template_path, "r", encoding="utf-8") as _file: + data = json.load(_file) + + if "parameters" in data: + self._generate_schema_from_arm_params(arm_template_schema, data["parameters"]) + else: + logger.warning( + "No parameters found in the template provided. " + "Your NFD will have no deployParameters" + ) + logger.debug( + "Schema for ARM template input: %s", + json.dumps(arm_template_schema, indent=4), + ) + + return copy.deepcopy(arm_template_schema) + + def _generate_schema_from_arm_params( + self, schema: Dict[str, Any], parameters: Dict[str, Any] + ) -> None: + """ + Generates the schema from the parameters. + + :param schema: The schema to generate. + :type schema: Dict[str, Any] + :param parameters: The parameters to generate the schema from. + :type parameters: Dict[str, Any] + """ + logger.debug("Generating schema from parameters") + for key, value in parameters.items(): + if "defaultValue" not in value: + schema["required"].append(key) + if value["type"] in ("object", "secureObject"): + schema["properties"][key] = { + "type": "object", + "properties": {}, + "required": [], + } + if "properties" in value: + self._generate_schema_from_arm_params( + schema["properties"][key], value["properties"] + ) + else: + schema["properties"][key] = {"type": value["type"]} + if "defaultValue" in value: + schema["properties"][key]["default"] = value["defaultValue"] diff --git a/src/aosm/azext_aosm/inputs/base_input.py b/src/aosm/azext_aosm/inputs/base_input.py new file mode 100644 index 00000000000..b577758b742 --- /dev/null +++ b/src/aosm/azext_aosm/inputs/base_input.py @@ -0,0 +1,49 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional + + +class BaseInput(ABC): + """ + Base class for all inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param default_config: The default configuration for the input. Defaults to None. + :type default_config: Optional[Dict[str, Any]] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + default_config: Optional[Dict[str, Any]] = None, + ): + self.artifact_name = artifact_name + self.artifact_version = artifact_version + self.default_config = default_config or {} + + @abstractmethod + def get_defaults(self) -> Dict[str, Any]: + """ + Abstract method to get the default values for the input. + + :return: A dictionary containing the default values. + :rtype: Dict[str, Any] + """ + raise NotImplementedError + + @abstractmethod + def get_schema(self) -> Dict[str, Any]: + """ + Abstract method to get the schema for the input. + + :return: A dictionary containing the schema. + :rtype: Dict[str, Any] + """ + raise NotImplementedError diff --git a/src/aosm/azext_aosm/inputs/helm_chart_input.py b/src/aosm/azext_aosm/inputs/helm_chart_input.py new file mode 100644 index 00000000000..1a21aa75844 --- /dev/null +++ b/src/aosm/azext_aosm/inputs/helm_chart_input.py @@ -0,0 +1,448 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import copy +import json +import shutil +import subprocess +import tempfile +import warnings +from dataclasses import dataclass +from os import PathLike +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple, Union + +import genson +import ruamel.yaml +import yaml +from knack.log import get_logger +from ruamel.yaml.error import ReusedAnchorWarning + +from azext_aosm.common.exceptions import ( + DefaultValuesNotFoundError, + MissingChartDependencyError, + SchemaGetOrGenerateError, + TemplateValidationError, +) +from azext_aosm.common.utils import check_tool_installed, extract_tarfile +# from azext_aosm.common.constants import CNF_VALUES_SCHEMA_FILENAME +from azext_aosm.inputs.base_input import BaseInput + +logger = get_logger(__name__) +yaml_processor = ruamel.yaml.YAML(typ="safe", pure=True) +warnings.simplefilter("ignore", ReusedAnchorWarning) + + +@dataclass +class HelmChartMetadata: + """ + Represents the metadata of a Helm chart. + + :param name: The name of the Helm chart. + :type name: str + :param version: The version of the Helm chart. + :type version: str + :param dependencies: The dependencies of the Helm chart. + :type dependencies: List[str] + """ + + name: str + version: str + dependencies: List[str] + + +@dataclass +class HelmChartTemplate: + """ + Represents a template in a Helm chart. + + :param name: The name of the template. + :type name: str + :param data: The data of the template. + :type data: List[str] + """ + + name: str + data: List[str] + + +class HelmChartInput(BaseInput): + """ + A utility class for working with Helm chart inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param chart_path: The path to the Helm chart. + :type chart_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + :param default_config_path: The path to the default configuration. + :type default_config_path: Optional[str] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + chart_path: Path, + default_config: Optional[Dict[str, Any]] = None, + default_config_path: Optional[str] = None, + ): + super().__init__(artifact_name, artifact_version, default_config) + self.chart_path = chart_path + self._temp_dir_path = Path(tempfile.mkdtemp()) + if chart_path.is_dir(): + self._chart_dir = chart_path + else: + self._chart_dir = extract_tarfile(chart_path, self._temp_dir_path) + self._validate() + self.metadata = self._get_metadata() + self.helm_template: Optional[str] = None + self.default_config_path = default_config_path + + @staticmethod + def from_chart_path( + chart_path: Path, + default_config: Optional[Dict[str, Any]] = None, + default_config_path: Optional[str] = None, + ) -> "HelmChartInput": + """ + Creates a HelmChartInput object from a path to a Helm chart. + + :param chart_path: The path to the Helm chart. This should either be a path to a folder or a tar file. + :type chart_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + :param default_config_path: The path to the default configuration. + :type default_config_path: Optional[str] + :return: A HelmChartInput object. + :rtype: HelmChartInput + """ + logger.debug("Creating Helm chart input from chart path '%s'", chart_path) + temp_dir = Path(tempfile.mkdtemp()) + if not chart_path.exists(): + raise FileNotFoundError( + f"ERROR: The Helm chart '{chart_path}' does not exist." + ) + logger.debug("Unpacking Helm chart to %s", temp_dir) + if chart_path.is_dir(): + unpacked_chart_path = Path(chart_path) + else: + unpacked_chart_path = extract_tarfile(chart_path, temp_dir) + + name, version = HelmChartInput._get_name_and_version(unpacked_chart_path) + + shutil.rmtree(temp_dir) + + logger.debug("Deleted temporary directory %s", temp_dir) + + return HelmChartInput( + artifact_name=name, + artifact_version=version, + chart_path=chart_path, + default_config=default_config, + default_config_path=default_config_path, + ) + + def validate_template(self) -> None: + """ + Perform validation on the Helm chart template by running `helm template` command. + + :return: Error message if the `helm template` command fails. + """ + logger.debug("Performing validation on Helm chart %s.", self.artifact_name) + + check_tool_installed("helm") + + if self.default_config_path: + cmd: List[Union[str, PathLike]] = [ + "helm", + "template", + self.artifact_name, + self.chart_path, + "--values", + self.default_config_path, + ] + else: + cmd = [ + "helm", + "template", + self.artifact_name, + self.chart_path, + ] + + try: + result = subprocess.run(cmd, capture_output=True, check=True) + helm_template_output = result.stdout + self.helm_template = helm_template_output.decode() + + logger.debug( + "Helm template output for Helm chart %s:\n%s", + self.artifact_name, + helm_template_output, + ) + except subprocess.CalledProcessError as error: + # Return the error message without raising an error. + # The errors are going to be collected into a file by the caller of this function. + error_message = error.stderr.decode() + + # Remove part of the message of the error. This is because this message will polute + # the error file. We are not running the helm command with the --debug flag, + # because during testing this flag did not produce any useful output + # (the invalid YAML was not printed out). If at a later date we find that this flag + # does produce a useful output, we can run the helm command with the --debug flag. + error_message = error_message.replace( + "\nUse --debug flag to render out invalid YAML", "" + ) + raise TemplateValidationError(error_message) + + def validate_values(self) -> None: + """ + Confirm that the default values are provided or that a values.yaml file exists in the + Helm chart directory. + + :raises DefaultValuesNotFoundError: If the values.yaml and default values do not exist. + """ + logger.debug("Getting default values for Helm chart %s", self.artifact_name) + + try: + self.default_config or self._read_values_yaml_from_chart() + except FileNotFoundError: + logger.error("No values found for Helm chart '%s'", self.chart_path) + raise DefaultValuesNotFoundError( + "ERROR: No default values found for the Helm chart" + f" '{self.chart_path}'. Please provide default values" + " or add a values.yaml file to the Helm chart." + ) + + def get_defaults(self) -> Dict[str, Any]: + """ + Retrieves the default values for the Helm chart. + + :return: The default values for the Helm chart. + :rtype: Dict[str, Any] + """ + default_config = self.default_config or self._read_values_yaml_from_chart() + logger.debug( + "Default values for Helm chart input: %s", + json.dumps(default_config, indent=4), + ) + return copy.deepcopy(default_config) + + def get_schema(self) -> Dict[str, Any]: + """ + Retrieves the schema for the Helm chart. + + If the Helm chart contains a values.schema.json file, then that file + will be used as the schema. Otherwise, a schema will be generated from + the default values in the values.yaml file. + + :return: The schema for the Helm chart. + :rtype: Dict[str, Any] + :raises SchemaGetOrGenerateError: If an error occurred while trying to generate or retrieve the schema. + """ + logger.info("Getting schema for Helm chart input %s.", self.artifact_name) + try: + # The following commented out code was used to get the values.schema.json file from the Helm chart, and + # only generate the schema from values.yaml as a backup. Unfortunately, the values.schema.json file is too + # flexible, and handling all the many ways it can be written is too complex (at least for now). Instead, + # we always generate the schema we use from values.yaml / default values. + # values.schema.yaml is still used by `helm template`, which we call, so if present in the chart, we will + # check that values.yaml complies with values.schema.yaml. This is still useful functionality. + # The code is being left commented out in case we want to revisit this in the future. + + # schema = None + # # Use the schema provided in the chart if there is one. + # for file in self._chart_dir.iterdir(): + # if file.name == CNF_VALUES_SCHEMA_FILENAME: + # logger.debug("Using schema from chart %s", file) + # with file.open(encoding="UTF-8") as schema_file: + # schema = json.load(schema_file) + + # if not schema: + # # Otherwise, generate a schema from the default values or values in values.yaml. + + # Note the following code that builds the schema from defaults has been unindented after above comments. + + logger.debug("Generating schema from default values") + built_schema = genson.Schema() + built_schema.add_object(self.get_defaults()) + schema = built_schema.to_dict() + + logger.debug( + "Schema for Helm chart input:\n%s", + json.dumps(schema, indent=4), + ) + + return schema + except FileNotFoundError as error: + logger.error("No schema found for Helm chart '%s'", self.chart_path) + raise SchemaGetOrGenerateError( + "ERROR: Encountered an error while trying to generate or" + f" retrieve the helm chart values schema:\n{error}" + ) from error + + def get_dependencies(self) -> List["HelmChartInput"]: + """ + Get the dependency charts for the Helm chart. + + :return: The dependency charts for the Helm chart. + :rtype: List[HelmChartInput] + :raises MissingChartDependencyError: If a dependency chart is missing. + """ + logger.debug( + "Getting dependency charts for Helm chart input, '%s'", self.artifact_name + ) + # All dependency charts should be located in the charts directory. + dependency_chart_dir = Path(self._chart_dir, "charts") + + if not dependency_chart_dir.exists(): + # If there is no charts directory, then there are no dependencies. + logger.debug("No dependency charts found for Helm chart input") + assert len(self.metadata.dependencies) == 0 + return [] + + # For each chart in the charts directory, create a HelmChartInput object. + dependency_charts = [ + HelmChartInput.from_chart_path(Path(chart_dir), None) + for chart_dir in dependency_chart_dir.iterdir() + ] + + # Check that the charts found in the charts directory match the + # all the dependency names defined in Chart.yaml. + for dependency in self.metadata.dependencies: + if dependency not in [ + dependency_chart.metadata.name for dependency_chart in dependency_charts + ]: + logger.error( + "Missing dependency chart '%s' for Helm chart '%s'", + dependency, + self.chart_path, + ) + raise MissingChartDependencyError( + f"ERROR: The Helm chart '{self.metadata.name}' has a" + f"dependency on the chart '{dependency}' which is not" + "a local dependency." + ) + + return dependency_charts + + def get_templates(self) -> List[HelmChartTemplate]: + """ + Get the templates for the Helm chart. + + :return: The templates for the Helm chart. + :rtype: List[HelmChartTemplate] + """ + logger.debug("Getting templates for Helm chart input %s", self.artifact_name) + + # Template files are located in the templates directory. + template_dir = Path(self._chart_dir, "templates") + templates: List[HelmChartTemplate] = [] + + if not template_dir.exists(): + logger.debug("No templates found for Helm chart input") + return templates + + for file in template_dir.iterdir(): + # Template files are only ever YAML files. + if file.name.endswith(".yaml"): + with file.open(encoding="UTF-8") as template_file: + template_data = template_file.readlines() + + templates.append(HelmChartTemplate(name=file.name, data=template_data)) + + return templates + + @staticmethod + def _get_name_and_version(chart_dir: Path) -> Tuple[str, str]: + """ + Retrieves the name and version of the Helm chart. + + :param chart_dir: The path to the Helm chart directory. + :type chart_dir: Path + :return: The name and version of the Helm chart. + :rtype: Tuple[str, str] + """ + logger.debug("Getting name and version for Helm chart") + chart_yaml_path = Path(chart_dir, "Chart.yaml") + + with chart_yaml_path.open(encoding="UTF-8") as chart_yaml_file: + chart_yaml = yaml.safe_load(chart_yaml_file) + + chart_name = chart_yaml["name"] + chart_version = chart_yaml["version"] + + logger.debug("Chart name: %s", chart_name) + logger.debug("Chart version: %s", chart_version) + + return chart_name, chart_version + + def _validate(self) -> None: + """ + Validates the Helm chart by checking if the Chart.yaml file exists. + + :raises FileNotFoundError: If the Chart.yaml file does not exist. + """ + if not Path(self._chart_dir, "Chart.yaml").exists(): + logger.error( + "Chart.yaml file not found in Helm chart '%s'", self.chart_path + ) + raise FileNotFoundError( + f"ERROR: The Helm chart '{self.chart_path}' does not contain" + "a Chart.yaml file." + ) + + def _get_metadata(self) -> HelmChartMetadata: + """ + Retrieves the metadata of the Helm chart. + + :return: The metadata of the Helm chart. + :rtype: HelmChartMetadata + """ + logger.debug("Getting metadata for Helm chart input") + # The metadata is stored in the Chart.yaml file. + chart_yaml_path = Path(self._chart_dir, "Chart.yaml") + + with chart_yaml_path.open(encoding="UTF-8") as chart_yaml_file: + chart_yaml = yaml.safe_load(chart_yaml_file) + + # We only need the name of the dependency charts. + dependencies = [ + dependency["name"] for dependency in chart_yaml.get("dependencies", []) + ] + + return HelmChartMetadata( + name=chart_yaml["name"], + version=chart_yaml["version"], + dependencies=dependencies, + ) + + def _read_values_yaml_from_chart(self) -> Dict[str, Any]: + """ + Reads the values.yaml file in the Helm chart directory. + + :return: The contents of the values.yaml file. + :rtype: Dict[str, Any] + :raises FileNotFoundError: If the values.yaml file does not exist. + """ + logger.debug("Reading values.yaml file") + for file in self._chart_dir.iterdir(): + if file.name.endswith(("values.yaml", "values.yml")): + with file.open(encoding="UTF-8") as f: + content = yaml_processor.load(f) + return content + + logger.error( + "values.yaml|yml file not found in Helm chart '%s'", self.chart_path + ) + raise FileNotFoundError( + f"ERROR: The Helm chart '{self.chart_path}' does not contain" + "a values.yaml file." + ) + + def __del__(self): + shutil.rmtree(self._temp_dir_path) diff --git a/src/aosm/azext_aosm/inputs/nexus_image_input.py b/src/aosm/azext_aosm/inputs/nexus_image_input.py new file mode 100644 index 00000000000..58dfb77e052 --- /dev/null +++ b/src/aosm/azext_aosm/inputs/nexus_image_input.py @@ -0,0 +1,57 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from typing import Any, Dict, Optional +from knack.log import get_logger +from azext_aosm.inputs.base_input import BaseInput + +logger = get_logger(__name__) + + +class NexusImageFileInput(BaseInput): + """ + A utility class for working with VHD file inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param file_path: The path to the VHD file. + :type file_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + :param blob_sas_uri: The blob SAS URI. + :type blob_sas_uri: Optional[str] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + source_acr_registry: str, + default_config: Optional[Dict[str, Any]] = None, + ): + super().__init__(artifact_name, artifact_version, default_config) + self.source_acr_registry = source_acr_registry + + def get_defaults(self) -> Dict[str, Any]: + """ + Gets the default values for configuring the input. + + For Nexus images, there are no defaults. + :return: An empty dictionary. + :rtype: Dict[str, Any] + """ + return {} + + def get_schema(self) -> Dict[str, Any]: + """ + Gets the schema for the file input. + + For Nexus images, there is no schema. + :return: An empty dictionary. + :rtype: Dict[str, Any] + """ + return {} diff --git a/src/aosm/azext_aosm/inputs/nfd_input.py b/src/aosm/azext_aosm/inputs/nfd_input.py new file mode 100644 index 00000000000..e508b897cc8 --- /dev/null +++ b/src/aosm/azext_aosm/inputs/nfd_input.py @@ -0,0 +1,166 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import copy +import json +from pathlib import Path +from typing import Any, Dict, Optional + +from knack.log import get_logger + +from azext_aosm.common.constants import BASE_SCHEMA +from azext_aosm.inputs.base_input import BaseInput +from azext_aosm.vendored_sdks.models import ( + NetworkFunctionDefinitionVersion, + ContainerizedNetworkFunctionDefinitionVersion, + VirtualNetworkFunctionDefinitionVersion, +) + +logger = get_logger(__name__) + + +class NFDInput(BaseInput): + """ + A utility class for working with Network Function Definition inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param network_function_definition: The network function definition. + :type network_function_definition: NetworkFunctionDefinitionVersion + :param arm_template_output_path: The path to the ARM template output. + :type arm_template_output_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + network_function_definition: NetworkFunctionDefinitionVersion, + arm_template_output_path: Path, + default_config: Optional[Dict[str, Any]] = None, + ): + super().__init__(artifact_name, artifact_version, default_config) + self.network_function_definition = network_function_definition + self.arm_template_output_path = arm_template_output_path + + def get_defaults(self) -> Dict[str, Any]: + """ + Gets the default values for configuring the input. + + :return: A dictionary containing the default values. + :rtype: Dict[str, Any] + """ + if self.network_function_definition.id: + logger.debug( + "network_function_definition.id for NFD input: %s", + self.network_function_definition.id, + ) + split_id = self.network_function_definition.id.split("/") + publisher_name: str = split_id[8] + nfdg_name: str = split_id[10] + publisher_resource_group: str = split_id[4] + else: + raise ValueError("No Network Function ID found") + + logger.info("Getting default values for NFD Input") + + base_defaults = { + "configObject": { + "location": self.default_config["location"], + "publisherName": publisher_name, + "nfdgName": nfdg_name, + "publisherResourceGroup": publisher_resource_group, + } + } + + # This horrendous if statement is required because: + # - the 'properties' and 'network_function_template' attributes are optional + # - the isinstance check is because the base NetworkFunctionDefinitionVersionPropertiesFormat class + # doesn't define the network_function_template attribute, even though both subclasses do. + # Not switching to EAFP style because mypy doesn't account for `except AttributeError` (for good reason). + # Similar test required in the NFD processor, but we can't deduplicate the code because mypy doesn't + # propagate type narrowing from isinstance(). + if ( + self.network_function_definition.properties + and isinstance( + self.network_function_definition.properties, + ( + ContainerizedNetworkFunctionDefinitionVersion, + VirtualNetworkFunctionDefinitionVersion, + ), + ) + and self.network_function_definition.properties.network_function_template + and self.network_function_definition.properties.network_function_template.nfvi_type + not in ("AzureArcKubernetes", "AzureOperatorNexus") + ): + base_defaults["configObject"]["customLocationId"] = "" + + logger.debug( + "Default values for NFD Input: %s", json.dumps(base_defaults, indent=4) + ) + + return copy.deepcopy(base_defaults) + + def get_schema(self) -> Dict[str, Any]: + """ + Gets the parameter schema for configuring the input. + + :return: A dictionary containing the schema. + :rtype: Dict[str, Any] + :raises ValueError: If no deployment parameters schema is found on the network function definition version. + """ + logger.debug("Getting schema for NFD Input %s.", self.artifact_name) + schema_properties = { + "configObject": { + "type": "object", + "properties": { + "location": {"type": "string"}, + "publisherName": {"type": "string"}, + "nfdgName": {"type": "string"}, + "nfdv": {"type": "string"}, + "publisherResourceGroup": {"type": "string"}, + "deployParameters": { + "type": "array", + "items": {"type": "object"}, + }, + "customLocationId": {"type": "string"}, + "managedIdentityId": {"type": "string"}, + }, + "required": [ + "location", + "publisherName", + "nfdgName", + "nfdv", + "publisherResourceGroup", + "deployParameters", + "customLocationId", + "managedIdentityId", + ], + } + } + + schema_required_properties = ["configObject"] + + schema = copy.deepcopy(BASE_SCHEMA) + schema["properties"] = schema_properties + schema["required"] = schema_required_properties + + nfdv_properties = self.network_function_definition.properties + + if nfdv_properties and nfdv_properties.deploy_parameters: + schema["properties"]["configObject"]["properties"]["deployParameters"][ + "items" + ] = json.loads(nfdv_properties.deploy_parameters) + + logger.debug("Schema for NFD Input: %s", json.dumps(schema, indent=4)) + + return copy.deepcopy(schema) + + raise ValueError( + "No deployment parameters schema found on the network function definition version." + ) diff --git a/src/aosm/azext_aosm/inputs/vhd_file_input.py b/src/aosm/azext_aosm/inputs/vhd_file_input.py new file mode 100644 index 00000000000..d2eab8af48a --- /dev/null +++ b/src/aosm/azext_aosm/inputs/vhd_file_input.py @@ -0,0 +1,95 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import copy +import json +from pathlib import Path +from typing import Any, Dict, Optional + +from knack.log import get_logger +from azext_aosm.common.utils import snake_case_to_camel_case +from azext_aosm.common.constants import BASE_SCHEMA +from azext_aosm.inputs.base_input import BaseInput + +logger = get_logger(__name__) + + +class VHDFileInput(BaseInput): + """ + A utility class for working with VHD file inputs. + + :param artifact_name: The name of the artifact. + :type artifact_name: str + :param artifact_version: The version of the artifact. + :type artifact_version: str + :param file_path: The path to the VHD file. + :type file_path: Path + :param default_config: The default configuration. + :type default_config: Optional[Dict[str, Any]] + :param blob_sas_uri: The blob SAS URI. + :type blob_sas_uri: Optional[str] + """ + + def __init__( + self, + artifact_name: str, + artifact_version: str, + file_path: Optional[Path] = None, + blob_sas_uri: Optional[str] = None, + default_config: Optional[Dict[str, Any]] = None, + ): + super().__init__(artifact_name, artifact_version, default_config) + self.file_path = file_path + self.blob_sas_uri = blob_sas_uri + + formatted_config = {} + for (key, value) in self.default_config.items(): + # This must be an integer, but is a string in the input file + if key == "image_disk_size_GB": + value = int(value) + if key == "image_api_version": + key = "apiVersion" + formatted_key = snake_case_to_camel_case(key) + formatted_config[formatted_key] = value + self.default_config = formatted_config + + def get_defaults(self) -> Dict[str, Any]: + """ + Gets the default values for configuring the input. + + :return: A dictionary containing the default values. + :rtype: Dict[str, Any] + """ + logger.info("Getting default values for VHD file input") + default_config = self.default_config or {} + logger.debug( + "Default values for VHD file Input: %s", + json.dumps(default_config, indent=4), + ) + return copy.deepcopy(default_config) + + def get_schema(self) -> Dict[str, Any]: + """ + Gets the schema for the VHD file input. + + :return: A dictionary containing the schema. + :rtype: Dict[str, Any] + """ + logger.debug("Getting schema for VHD file input %s.", self.artifact_name) + vhd_properties = { + "imageName": {"type": "string"}, + "azureDeployLocation": {"type": "string"}, + "imageDiskSizeGB": {"type": "integer"}, + "imageOsState": {"type": "string"}, + "imageHyperVGeneration": {"type": "string"}, + "apiVersion": {"type": "string"}, + } + vhd_required = ["imageName"] + + schema = copy.deepcopy(BASE_SCHEMA) + schema["properties"].update(vhd_properties) + schema["required"] += vhd_required + + logger.debug("Schema for VHD file input: %s", json.dumps(schema, indent=4)) + return copy.deepcopy(schema) diff --git a/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template-invalid-chart.jsonc b/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template-invalid-chart.jsonc new file mode 100644 index 00000000000..0e9f678c14b --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template-invalid-chart.jsonc @@ -0,0 +1,18 @@ +{ + "location": "uksouth", + "publisher_name": "sunnyclipub", + "publisher_resource_group_name": "sunny-uksouth", + "nf_name": "nf-agent-cnf", + "version": "0.1.0", + "acr_artifact_store_name": "sunny-nfagent-acr-2", + "images": { + "source_registry": "--this was copied here and renamed from https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/4a0479c0-b795-4d0f-96fd-c7edd2a2928f/resourceGroups/pez-nfagent-pipelines/providers/Microsoft.ContainerRegistry/registries/peznfagenttemp/overview new one was /subscriptions/c7bd9d96-70dd-4f61-af56-6e0abd8d80b5/resourceGroups/sunny-nfagent-acr-HostedResources-4CDE264A/providers/Microsoft.ContainerRegistry/registries/SunnyclipubSunnyNfagentAcre00abc1832" + }, + "helm_packages": [ + { + "name": "nf-agent-cnf", + "path_to_chart": "{{tests_directory}}/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid", + "default_values": "" + } + ] +} diff --git a/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template.jsonc b/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template.jsonc new file mode 100644 index 00000000000..712bacbb322 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/input_file_templates/input-nf-agent-cnf-template.jsonc @@ -0,0 +1,18 @@ +{ + "location": "uksouth", + "publisher_name": "sunnyclipub", + "publisher_resource_group_name": "sunny-uksouth", + "nf_name": "nf-agent-cnf", + "version": "0.1.0", + "acr_artifact_store_name": "sunny-nfagent-acr-2", + "images": { + "source_registry": "--this was copied here and renamed from https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/4a0479c0-b795-4d0f-96fd-c7edd2a2928f/resourceGroups/pez-nfagent-pipelines/providers/Microsoft.ContainerRegistry/registries/peznfagenttemp/overview new one was /subscriptions/c7bd9d96-70dd-4f61-af56-6e0abd8d80b5/resourceGroups/sunny-nfagent-acr-HostedResources-4CDE264A/providers/Microsoft.ContainerRegistry/registries/SunnyclipubSunnyNfagentAcre00abc1832" + }, + "helm_packages": [ + { + "name": "nf-agent-cnf", + "path_to_chart": "{{tests_directory}}/latest/mock_cnf/helm-charts/nf-agent-cnf", + "default_values": "" + } + ] +} diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/cnf_mocks/nginxdemo-0.1.0.tgz b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/cnf_mocks/nginxdemo-0.1.0.tgz similarity index 100% rename from src/aosm/azext_aosm/tests/latest/scenario_test_mocks/cnf_mocks/nginxdemo-0.1.0.tgz rename to src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/cnf_mocks/nginxdemo-0.1.0.tgz diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_input_template.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_input_template.jsonc new file mode 100644 index 00000000000..e0186ac4fd1 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_input_template.jsonc @@ -0,0 +1,29 @@ +{ + // Azure location to use when creating resources. + "location": "uksouth", + // Name of the Publisher resource you want your definition published to. + // Will be created if it does not exist. + "publisher_name": "automated-cli-test-nginx-publisher", + // Optional. Resource group for the Publisher resource. + // Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "{{publisher_resource_group_name}}", + // Optional. Name of the ACR Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "nginx-acr", + // Name of NF definition. + "nf_name": "nginx", + // Version of the NF definition in A.B.C format. + "version": "1.0.0", + // List of registries from which to pull the image(s). + // For example [sourceacr.azurecr.io/test, myacr2.azurecr.io]. + "image_sources": ["docker.io"], + + // List of Helm packages to be included in the CNF. + "helm_packages": [ + { + "name": "nginxdemo", + "path_to_chart": "{{path_to_chart}}", + "default_values": "" + } + ] +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_nsd_input_template.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_nsd_input_template.jsonc new file mode 100644 index 00000000000..c30d764da45 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/cnf_nsd_input_template.jsonc @@ -0,0 +1,19 @@ +{ + "publisher_name": "automated-cli-test-nginx-publisher", + "publisher_resource_group_name": "{{publisher_resource_group_name}}", + "acr_artifact_store_name": "nginx-nsd-acr", + "location": "uksouth", + "network_functions": [ + { + "publisher": "automated-cli-test-nginx-publisher", + "publisher_resource_group": "{{publisher_resource_group_name}}", + "name": "nginx-nfdg", + "version": "1.0.0", + "publisher_offering_location": "uksouth", + "type": "cnf" + } + ], + "nsd_name": "nginx-nsdg", + "nsd_version": "1.0.0", + "nsdv_description": "Deploys a basic NGINX CNF" +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multi_nf_nsd.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multi_nf_nsd.jsonc new file mode 100644 index 00000000000..931de114d1c --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multi_nf_nsd.jsonc @@ -0,0 +1,47 @@ +{ + "location": "uksouth", + "publisher_name": "automated-cli-test-ubuntu-publisher", + "publisher_resource_group_name": "cli_test_nsd", + "acr_artifact_store_name": "ubuntu-acr", + "resource_element_templates": [ + { + "resource_element_type": "NF", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "automated-cli-test-ubuntu-publisher", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "cli_test_nsd", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "ubuntu-vm", + // 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. + "version": "1.0.0", + // The region that the NFDV is published to. + "publisher_offering_location": "uksouth", + // Type of Network Function. Valid values are 'cnf' or 'vnf'. + "type": "vnf" + } + }, + { + "resource_element_type": "NF", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "automated-cli-test-ubuntu-publisher", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "cli_test_nsd", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "nginx", + // 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. + "version": "1.0.0", + // The region that the NFDV is published to. + "publisher_offering_location": "uksouth", + // Type of Network Function. Valid values are 'cnf' or 'vnf'. + "type": "cnf" + } + } + ], + "nsd_name": "multi-nf", + "nsd_version": "1.0.0", + "nsdv_description": "Plain ubuntu VM" +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multiple_instances.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multiple_instances.jsonc new file mode 100644 index 00000000000..08c22b057dd --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/input_multiple_instances.jsonc @@ -0,0 +1,29 @@ +{ + "location": "uksouth", + "publisher_name": "automated-cli-test-ubuntu-publisher", + "publisher_resource_group_name": "cli_test_nsd", + "acr_artifact_store_name": "ubuntu-acr", + "resource_element_templates": [ + { + "resource_element_type": "NF", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "automated-cli-test-ubuntu-publisher", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "cli_test_nsd", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "ubuntu-vm", + // 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. + "version": "1.0.0", + // The region that the NFDV is published to. + "publisher_offering_location": "uksouth", + // Type of Network Function. Valid values are 'cnf' or 'vnf'. + "type": "vnf" + } + } + ], + "nsd_name": "ubuntu", + "nsd_version": "1.0.0", + "nsdv_description": "Plain ubuntu VM" +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/nsd_core_input.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/nsd_core_input.jsonc new file mode 100644 index 00000000000..dc8ffcc1782 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/nsd_core_input.jsonc @@ -0,0 +1,29 @@ +{ + "location": "uksouth", + "publisher_name": "automated-cli-test-ubuntu-publisher", + "publisher_resource_group_name": "test_publisher_name", + "acr_artifact_store_name": "ubuntu-acr", + "resource_element_templates": [ + { + "resource_element_type": "NF", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "automated-cli-test-ubuntu-publisher", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "test_publisher_name", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "ubuntu-vm", + // 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. + "version": "1.0.0", + // The region that the NFDV is published to. + "publisher_offering_location": "uksouth", + // Type of Network Function. Valid values are 'cnf' or 'vnf'. + "type": "vnf" + } + } + ], + "nsd_name": "ubuntu", + "nsd_version": "1.0.0", + "nsdv_description": "Plain ubuntu VM" +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_template.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_template.jsonc new file mode 100644 index 00000000000..a75e968d6d8 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_template.jsonc @@ -0,0 +1,33 @@ +{ + // Azure location to use when creating resources. + "location": "uksouth", + // Name of the Publisher resource you want your definition published to. + // Will be created if it does not exist. + "publisher_name": "automated-cli-test-ubuntu-publisher", + // Optional. Resource group for the Publisher resource. + // Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "{{publisher_resource_group_name}}", + // Optional. Name of the ACR Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "ubuntu-acr", + // Name of NF definition. + "nf_name": "ubuntu-vm", + // Version of the NF definition in A.B.C format. + "version": "1.0.0", + // Optional. Name of the storage account Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "blob_artifact_store_name": "ubuntu-blob-store", + // ARM template configuration. + "arm_templates": [ + { + "artifact_name": "automated-cli-tests-artifact", + "file_path": "{{arm_template_path}}", + "version": "1.0.0" + } + ], + "vhd": { + "artifact_name": "automated-cli-tests-vhd", + "file_path": "{{vhd_path}}", + "version": "1-0-0" + } +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_with_sas_token_template.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_with_sas_token_template.jsonc new file mode 100644 index 00000000000..d34fd05e7ba --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_input_with_sas_token_template.jsonc @@ -0,0 +1,46 @@ +{ + // Azure location to use when creating resources. + "location": "uksouth", + // Name of the Publisher resource you want your definition published to. + // Will be created if it does not exist. + "publisher_name": "automated-cli-test-ubuntu-publisher", + // Optional. Resource group for the Publisher resource. + // Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "{{publisher_resource_group_name}}", + // Optional. Name of the ACR Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "ubuntu-acr", + // Name of NF definition. + "nf_name": "ubuntu-vm", + // Version of the NF definition in A.B.C format. + "version": "1.0.0", + // Optional. Name of the storage account Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "blob_artifact_store_name": "ubuntu-blob-store", + // ARM template configuration. + "arm_templates": [ + { + "artifact_name": "automated-cli-tests-artifact", + "file_path": "{{arm_template_path}}", + "version": "1.0.0" + } + ], + // VHD image configuration. + "vhd": { + // Version of the artifact in A-B-C format. Note the '-' (dash) not '.' (dot). + "version": "1-0-0", + // Supply either file_path or blob_sas_url, not both. + // SAS URL of the blob artifact you wish to copy to your Artifact Store. + // Leave as empty string if not required. Use Linux slash (/) file separator even if running on Windows. + "blob_sas_url": "https://a/dummy/sas-url", + // Optional. Specifies the size of empty data disks in gigabytes. + // This value cannot be larger than 1023 GB. Delete if not required. + "image_disk_size_GB": "30", + // Optional. Specifies the HyperVGenerationType of the VirtualMachine created from the image. + // Valid values are V1 and V2. V1 is the default if not specified. Delete if not required. + "image_hyper_v_generation": "V1", + // Optional. The ARM API version used to create the Microsoft.Compute/images resource. + // Delete if not required. + "image_api_version": "2023-03-01" + } +} diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_nsd_input_template.jsonc b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_nsd_input_template.jsonc new file mode 100644 index 00000000000..bdfff0c8592 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/mock_input_templates/vnf_nsd_input_template.jsonc @@ -0,0 +1,29 @@ +{ + "location": "uksouth", + "publisher_name": "automated-cli-test-ubuntu-publisher", + "publisher_resource_group_name": "{{publisher_resource_group_name}}", + "acr_artifact_store_name": "ubuntu-acr", + "resource_element_templates": [ + { + "resource_element_type": "NF", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "automated-cli-test-ubuntu-publisher", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "{{publisher_resource_group_name}}", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "ubuntu-vm", + // 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. + "version": "1.0.0", + // The region that the NFDV is published to. + "publisher_offering_location": "uksouth", + // Type of Network Function. Valid values are 'cnf' or 'vnf'. + "type": "vnf" + } + } + ], + "nsd_name": "ubuntu", + "nsd_version": "1.0.0", + "nsdv_description": "Plain ubuntu VM" +} diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/vnf_mocks/ubuntu.vhd b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/vnf_mocks/ubuntu.vhd similarity index 100% rename from src/aosm/azext_aosm/tests/latest/scenario_test_mocks/vnf_mocks/ubuntu.vhd rename to src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/vnf_mocks/ubuntu.vhd diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/vnf_mocks/ubuntu_template.json b/src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/vnf_mocks/ubuntu_template.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/scenario_test_mocks/vnf_mocks/ubuntu_template.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/integration_test_mocks/vnf_mocks/ubuntu_template.json diff --git a/src/aosm/azext_aosm/tests/latest/metaschema.json b/src/aosm/azext_aosm/tests/latest/integration_tests/metaschema.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/metaschema.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/metaschema.json diff --git a/src/aosm/azext_aosm/tests/latest/metaschema_modified.json b/src/aosm/azext_aosm/tests/latest/integration_tests/metaschema_modified.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/metaschema_modified.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/metaschema_modified.json diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/all_deploy.parameters.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/all_deploy.parameters.json new file mode 100644 index 00000000000..1c3b3874541 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/all_deploy.parameters.json @@ -0,0 +1,10 @@ +{ + "location": "uksouth", + "publisherName": "automated-cli-test-ubuntu-publisher", + "publisherResourceGroupName": "test_publisher_name", + "acrArtifactStoreName": "ubuntu-acr", + "acrManifestName": "ubuntu-nsd-manifest-1-0-0", + "nsDesignGroup": "ubuntu", + "nsDesignVersion": "1.0.0", + "nfviSiteName": "ubuntu_NFVI" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifactManifest/deploy.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifactManifest/deploy.bicep new file mode 100644 index 00000000000..58fdfc5792d --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifactManifest/deploy.bicep @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an Artifact Manifest for a NSD +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of the Artifact Manifest to create') +param acrManifestName string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the nsdbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +// If using publish command, this is created from deploying the nsdbase.bicep +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +// Artifact manifest from ARMTemplate and NF RET artifacts +resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: acrArtifactStore + name: acrManifestName + location: location + properties: { + artifacts: [ + { + artifactName: 'ubuntu' + artifactType: 'OCIArtifact' + artifactVersion: '1.0.0' + } + ] + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/artifacts.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/artifacts.json new file mode 100644 index 00000000000..ec8ac11f130 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/artifacts.json @@ -0,0 +1,9 @@ +[ + { + "type": "ACRFromLocalFile", + "artifact_name": "ubuntu", + "artifact_type": "ArmTemplate", + "artifact_version": "1.0.0", + "file_path": "artifacts/ubuntu-vm.bicep" + } +] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/ubuntu-vm.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/ubuntu-vm.bicep new file mode 100644 index 00000000000..e6d8d4ad288 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/artifacts/ubuntu-vm.bicep @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Highly Confidential Material +// +// The template that the NSD invokes to create the Network Function from a published NFDV. + +@secure() +param configObject object + +var resourceGroupId = resourceGroup().id + +var identityObject = (configObject.managedIdentityId == '') ? { + type: 'SystemAssigned' +} : { + type: 'UserAssigned' + userAssignedIdentities: { + '${configObject.managedIdentityId}': {} + } +} + +var nfdvSymbolicName = '${configObject.publisherName}/${configObject.nfdgName}/${configObject.nfdv}' + +resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { + name: nfdvSymbolicName + scope: resourceGroup(configObject.publisherResourceGroup) +} + +resource nfResource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in configObject.deployParameters: { + name: '${configObject.nfdgName}${i}' + location: configObject.location + identity: identityObject + properties: { + networkFunctionDefinitionVersionResourceReference: { + id: nfdv.id + idType: 'Open' + } + nfviType: 'AzureCore' + nfviId: (configObject.customLocationId == '') ? resourceGroupId : configObject.customLocationId + allowSoftwareUpdate: true + configurationType: 'Open' + deploymentValues: string(values) + } +}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/base/deploy.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/base/deploy.bicep new file mode 100644 index 00000000000..6c08d804390 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/base/deploy.bicep @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for an NSD +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of an Network Service Design Group') +param nsDesignGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The NSD Group is the parent resource under which all NSD versions will be created. +resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' = { + parent: publisher + name: nsDesignGroup + location: location +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/index.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/index.json new file mode 100644 index 00000000000..8b5c7a1dce7 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/index.json @@ -0,0 +1,22 @@ +[ + { + "name": "base", + "type": "bicep", + "only_delete_on_clean": false + }, + { + "name": "artifactManifest", + "type": "bicep", + "only_delete_on_clean": false + }, + { + "name": "artifacts", + "type": "artifact", + "only_delete_on_clean": false + }, + { + "name": "nsdDefinition", + "type": "bicep", + "only_delete_on_clean": false + } +] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/schemas/ubuntu_ConfigGroupSchema.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/config-group-schema.json similarity index 52% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/schemas/ubuntu_ConfigGroupSchema.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/config-group-schema.json index 5393c2ba01f..99154389645 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/schemas/ubuntu_ConfigGroupSchema.json +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/config-group-schema.json @@ -1,12 +1,15 @@ { "$schema": "https://json-schema.org/draft-07/schema#", - "title": "ubuntu_ConfigGroupSchema", + "title": "ConfigGroupSchema", "type": "object", "properties": { - "ubuntu-vm-nfdg": { + "ubuntu": { "type": "object", "properties": { - "deploymentParameters": { + "nfdv": { + "type": "string" + }, + "deployParameters": { "type": "array", "items": { "type": "object", @@ -26,23 +29,18 @@ } } }, - "ubuntu_vm_nfdg_nfd_version": { - "type": "string", - "description": "The version of the ubuntu-vm-nfdg NFD to use. This version must be compatible with (have the same parameters exposed as) ubuntu-vm-nfdg." + "managedIdentityId": { + "type": "string" } }, "required": [ - "deploymentParameters", - "ubuntu_vm_nfdg_nfd_version" + "nfdv", + "deployParameters", + "managedIdentityId" ] - }, - "managedIdentity": { - "type": "string", - "description": "The managed identity to use to deploy NFs within this SNS. This should be of the form '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}. If you wish to use a system assigned identity, set this to a blank string." } }, "required": [ - "ubuntu-vm-nfdg", - "managedIdentity" + "ubuntu" ] } \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/nsd_definition.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/deploy.bicep similarity index 83% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/nsd_definition.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/deploy.bicep index fc8fc21b958..10c59347665 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/nsd_definition.bicep +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/deploy.bicep @@ -19,19 +19,21 @@ param nfviSiteName string = 'ubuntu_NFVI' // The publisher resource is the top level AOSM resource under which all other designer resources // are created. +// If using publish command, this is created from deploying the nsdbase.bicep resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { name: publisherName scope: resourceGroup() } // The artifact store is the resource in which all the artifacts required to deploy the NF are stored. -// The artifact store is created by the az aosm CLI before this template is deployed. +// If using publish command, this is created from deploying the nsdbase.bicep resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { parent: publisher name: acrArtifactStoreName } -// Created up-front, the NSD Group is the parent resource under which all NSD versions will be created. +// The NSD Group is the parent resource under which all NSD versions will be created. +// If using publish command, this is created from deploying the nsdbase.bicep resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' existing = { parent: publisher name: nsDesignGroup @@ -42,34 +44,33 @@ resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups // The operator will create a config group values object that will satisfy this schema. resource cgSchema 'Microsoft.Hybridnetwork/publishers/configurationGroupSchemas@2023-09-01' = { parent: publisher - name: 'ubuntu_ConfigGroupSchema' + name: 'ConfigGroupSchema' location: location properties: { - schemaDefinition: string(loadJsonContent('schemas/ubuntu_ConfigGroupSchema.json')) + schemaDefinition: string(loadJsonContent('config-group-schema.json')) } } + // The NSD version +// This will deploy an NSDV in 'Preview' state. It should be changed to 'Active' once it is finalised. resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions@2023-09-01' = { parent: nsdGroup name: nsDesignVersion location: location properties: { description: 'Plain ubuntu VM' - // The version state can be Preview, Active or Deprecated. - // Once in an Active state, the NSDV becomes immutable. - versionState: 'Preview' // The `configurationgroupsSchemaReferences` field contains references to the schemas required to // be filled out to configure this NSD. configurationGroupSchemaReferences: { - ubuntu_ConfigGroupSchema: { + ConfigGroupSchema: { id: cgSchema.id } } // This details the NFVIs that should be available in the Site object created by the operator. nfvisFromSite: { nfvi1: { - name: nfviSiteName + name: '${nfviSiteName}1' type: 'AzureCore' } } @@ -77,7 +78,7 @@ resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngrou // to the values in the CG schemas. resourceElementTemplates: [ { - name: 'ubuntu-vm-nfdg_nf_artifact_resource_element' + name: 'ubuntu' // The type of resource element can be ArmResourceDefinition, ConfigurationDefinition or NetworkFunctionDefinition. type: 'NetworkFunctionDefinition' // The configuration object may be different for different types of resource element. @@ -87,13 +88,13 @@ resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngrou artifactStoreReference: { id: acrArtifactStore.id } - artifactName: 'ubuntu-vm-nfdg_nf_artifact' + artifactName: 'ubuntu' artifactVersion: '1.0.0' } templateType: 'ArmTemplate' // The parameter values map values from the CG schema, to values required by the template // deployed by this resource element. - parameterValues: string(loadJsonContent('configMappings/ubuntu-vm-nfdg_config_mapping.json')) + parameterValues: string(loadJsonContent('ubuntu-mappings.json')) } dependsOnProfile: { installDependsOn: [] diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/ubuntu-mappings.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/ubuntu-mappings.json new file mode 100644 index 00000000000..2069e00040a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build/nsdDefinition/ubuntu-mappings.json @@ -0,0 +1,12 @@ +{ + "configObject": { + "location": "uksouth", + "publisherName": "pub", + "nfdgName": "ubuntu", + "publisherResourceGroup": "rg", + "customLocationId": "", + "nfdv": "{configurationparameters('ConfigGroupSchema').ubuntu.nfdv}", + "deployParameters": "{configurationparameters('ConfigGroupSchema').ubuntu.deployParameters}", + "managedIdentityId": "{configurationparameters('ConfigGroupSchema').ubuntu.managedIdentityId}" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/all_deploy.parameters.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/all_deploy.parameters.json new file mode 100644 index 00000000000..9a752ff6027 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/all_deploy.parameters.json @@ -0,0 +1,10 @@ +{ + "location": "uksouth", + "publisherName": "automated-cli-test-ubuntu-publisher", + "publisherResourceGroupName": "cli_test_nsd", + "acrArtifactStoreName": "ubuntu-acr", + "acrManifestName": "ubuntu-nsd-manifest-1-0-0", + "nsDesignGroup": "ubuntu", + "nsDesignVersion": "1.0.0", + "nfviSiteName": "ubuntu_NFVI" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifactManifest/deploy.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifactManifest/deploy.bicep new file mode 100644 index 00000000000..58fdfc5792d --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifactManifest/deploy.bicep @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an Artifact Manifest for a NSD +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of the Artifact Manifest to create') +param acrManifestName string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +// If using publish command, this is created from deploying the nsdbase.bicep +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +// If using publish command, this is created from deploying the nsdbase.bicep +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +// Artifact manifest from ARMTemplate and NF RET artifacts +resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: acrArtifactStore + name: acrManifestName + location: location + properties: { + artifacts: [ + { + artifactName: 'ubuntu' + artifactType: 'OCIArtifact' + artifactVersion: '1.0.0' + } + ] + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/artifacts.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/artifacts.json new file mode 100644 index 00000000000..ec8ac11f130 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/artifacts.json @@ -0,0 +1,9 @@ +[ + { + "type": "ACRFromLocalFile", + "artifact_name": "ubuntu", + "artifact_type": "ArmTemplate", + "artifact_version": "1.0.0", + "file_path": "artifacts/ubuntu-vm.bicep" + } +] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/ubuntu-vm.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/ubuntu-vm.bicep new file mode 100644 index 00000000000..e6d8d4ad288 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/artifacts/ubuntu-vm.bicep @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Highly Confidential Material +// +// The template that the NSD invokes to create the Network Function from a published NFDV. + +@secure() +param configObject object + +var resourceGroupId = resourceGroup().id + +var identityObject = (configObject.managedIdentityId == '') ? { + type: 'SystemAssigned' +} : { + type: 'UserAssigned' + userAssignedIdentities: { + '${configObject.managedIdentityId}': {} + } +} + +var nfdvSymbolicName = '${configObject.publisherName}/${configObject.nfdgName}/${configObject.nfdv}' + +resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { + name: nfdvSymbolicName + scope: resourceGroup(configObject.publisherResourceGroup) +} + +resource nfResource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in configObject.deployParameters: { + name: '${configObject.nfdgName}${i}' + location: configObject.location + identity: identityObject + properties: { + networkFunctionDefinitionVersionResourceReference: { + id: nfdv.id + idType: 'Open' + } + nfviType: 'AzureCore' + nfviId: (configObject.customLocationId == '') ? resourceGroupId : configObject.customLocationId + allowSoftwareUpdate: true + configurationType: 'Open' + deploymentValues: string(values) + } +}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/base/deploy.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/base/deploy.bicep new file mode 100644 index 00000000000..6c08d804390 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/base/deploy.bicep @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates the base AOSM resources for an NSD +param location string +@description('Name of a publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of an Network Service Design Group') +param nsDesignGroup string + +// The publisher resource is the top level AOSM resource under which all other designer resources +// are created. +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' = { + name: publisherName + location: location + properties: { scope: 'Private'} +} + +// The artifact store is the resource in which all the artifacts required to deploy the NF are stored. +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' = { + parent: publisher + name: acrArtifactStoreName + location: location + properties: { + storeType: 'AzureContainerRegistry' + } +} + +// The NSD Group is the parent resource under which all NSD versions will be created. +resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' = { + parent: publisher + name: nsDesignGroup + location: location +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/index.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/index.json new file mode 100644 index 00000000000..8b5c7a1dce7 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/index.json @@ -0,0 +1,22 @@ +[ + { + "name": "base", + "type": "bicep", + "only_delete_on_clean": false + }, + { + "name": "artifactManifest", + "type": "bicep", + "only_delete_on_clean": false + }, + { + "name": "artifacts", + "type": "artifact", + "only_delete_on_clean": false + }, + { + "name": "nsdDefinition", + "type": "bicep", + "only_delete_on_clean": false + } +] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/config-group-schema.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/config-group-schema.json new file mode 100644 index 00000000000..99154389645 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/config-group-schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "ConfigGroupSchema", + "type": "object", + "properties": { + "ubuntu": { + "type": "object", + "properties": { + "nfdv": { + "type": "string" + }, + "deployParameters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "virtualNetworkId": { + "type": "string" + }, + "sshPublicKeyAdmin": { + "type": "string" + } + } + } + }, + "managedIdentityId": { + "type": "string" + } + }, + "required": [ + "nfdv", + "deployParameters", + "managedIdentityId" + ] + } + }, + "required": [ + "ubuntu" + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/nsd_definition.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/deploy.bicep similarity index 83% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build/nsd_definition.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/deploy.bicep index fc8fc21b958..10c59347665 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/nsd_definition.bicep +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/deploy.bicep @@ -19,19 +19,21 @@ param nfviSiteName string = 'ubuntu_NFVI' // The publisher resource is the top level AOSM resource under which all other designer resources // are created. +// If using publish command, this is created from deploying the nsdbase.bicep resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { name: publisherName scope: resourceGroup() } // The artifact store is the resource in which all the artifacts required to deploy the NF are stored. -// The artifact store is created by the az aosm CLI before this template is deployed. +// If using publish command, this is created from deploying the nsdbase.bicep resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { parent: publisher name: acrArtifactStoreName } -// Created up-front, the NSD Group is the parent resource under which all NSD versions will be created. +// The NSD Group is the parent resource under which all NSD versions will be created. +// If using publish command, this is created from deploying the nsdbase.bicep resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups@2023-09-01' existing = { parent: publisher name: nsDesignGroup @@ -42,34 +44,33 @@ resource nsdGroup 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups // The operator will create a config group values object that will satisfy this schema. resource cgSchema 'Microsoft.Hybridnetwork/publishers/configurationGroupSchemas@2023-09-01' = { parent: publisher - name: 'ubuntu_ConfigGroupSchema' + name: 'ConfigGroupSchema' location: location properties: { - schemaDefinition: string(loadJsonContent('schemas/ubuntu_ConfigGroupSchema.json')) + schemaDefinition: string(loadJsonContent('config-group-schema.json')) } } + // The NSD version +// This will deploy an NSDV in 'Preview' state. It should be changed to 'Active' once it is finalised. resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions@2023-09-01' = { parent: nsdGroup name: nsDesignVersion location: location properties: { description: 'Plain ubuntu VM' - // The version state can be Preview, Active or Deprecated. - // Once in an Active state, the NSDV becomes immutable. - versionState: 'Preview' // The `configurationgroupsSchemaReferences` field contains references to the schemas required to // be filled out to configure this NSD. configurationGroupSchemaReferences: { - ubuntu_ConfigGroupSchema: { + ConfigGroupSchema: { id: cgSchema.id } } // This details the NFVIs that should be available in the Site object created by the operator. nfvisFromSite: { nfvi1: { - name: nfviSiteName + name: '${nfviSiteName}1' type: 'AzureCore' } } @@ -77,7 +78,7 @@ resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngrou // to the values in the CG schemas. resourceElementTemplates: [ { - name: 'ubuntu-vm-nfdg_nf_artifact_resource_element' + name: 'ubuntu' // The type of resource element can be ArmResourceDefinition, ConfigurationDefinition or NetworkFunctionDefinition. type: 'NetworkFunctionDefinition' // The configuration object may be different for different types of resource element. @@ -87,13 +88,13 @@ resource nsdVersion 'Microsoft.Hybridnetwork/publishers/networkservicedesigngrou artifactStoreReference: { id: acrArtifactStore.id } - artifactName: 'ubuntu-vm-nfdg_nf_artifact' + artifactName: 'ubuntu' artifactVersion: '1.0.0' } templateType: 'ArmTemplate' // The parameter values map values from the CG schema, to values required by the template // deployed by this resource element. - parameterValues: string(loadJsonContent('configMappings/ubuntu-vm-nfdg_config_mapping.json')) + parameterValues: string(loadJsonContent('ubuntu-mappings.json')) } dependsOnProfile: { installDependsOn: [] diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/ubuntu-mappings.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/ubuntu-mappings.json new file mode 100644 index 00000000000..2069e00040a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_instances/nsdDefinition/ubuntu-mappings.json @@ -0,0 +1,12 @@ +{ + "configObject": { + "location": "uksouth", + "publisherName": "pub", + "nfdgName": "ubuntu", + "publisherResourceGroup": "rg", + "customLocationId": "", + "nfdv": "{configurationparameters('ConfigGroupSchema').ubuntu.nfdv}", + "deployParameters": "{configurationparameters('ConfigGroupSchema').ubuntu.deployParameters}", + "managedIdentityId": "{configurationparameters('ConfigGroupSchema').ubuntu.managedIdentityId}" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/artifact_manifest.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep similarity index 100% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build/artifact_manifest.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json similarity index 79% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json index 82ca9cac7d8..6ae3a0320fc 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/nginx-nfdg_config_mapping.json @@ -1,7 +1,7 @@ { - "deploymentParametersObject": { - "deploymentParameters": [ - "{configurationparameters('multinf_ConfigGroupSchema').nginx-nfdg.deploymentParameters}" + "deployParametersObject": { + "deployParameters": [ + "{configurationparameters('multinf_ConfigGroupSchema').nginx-nfdg.deployParameters}" ] }, "nginx_nfdg_nfd_version": "{configurationparameters('multinf_ConfigGroupSchema').nginx-nfdg.nginx_nfdg_nfd_version}", diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json similarity index 73% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json index af03596e75b..e4889d44108 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/configMappings/ubuntu-nfdg_config_mapping.json @@ -1,7 +1,7 @@ { - "deploymentParametersObject": { - "deploymentParameters": [ - "{configurationparameters('multinf_ConfigGroupSchema').ubuntu-nfdg.deploymentParameters}" + "deployParametersObject": { + "deployParameters": [ + "{configurationparameters('multinf_ConfigGroupSchema').ubuntu-nfdg.deployParameters}" ] }, "ubuntu_nfdg_nfd_version": "{configurationparameters('multinf_ConfigGroupSchema').ubuntu-nfdg.ubuntu_nfdg_nfd_version}", diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep similarity index 92% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep index 0656ec5263a..f17ffba0050 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/nginx-nfdg_nf.bicep @@ -27,9 +27,9 @@ param nfviType string = 'AzureArcKubernetes' param resourceGroupId string = resourceGroup().id @secure() -param deploymentParametersObject object +param deployParametersObject object -var deploymentParameters = deploymentParametersObject.deploymentParameters +var deployParameters = deployParametersObject.deployParameters var identityObject = (managedIdentity == '') ? { type: 'SystemAssigned' @@ -56,7 +56,7 @@ resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroup } -resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deploymentParameters: { +resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deployParameters: { name: 'nginx-nfdg${i}' location: location identity: identityObject @@ -71,4 +71,4 @@ resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [fo configurationType: 'Secret' secretDeploymentValues: string(values) } -}] \ No newline at end of file +}] diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/nsd_definition.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/nsd_definition.bicep similarity index 100% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/nsd_definition.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/nsd_definition.bicep diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json similarity index 94% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json index 1d120f6c538..3c210397fdf 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/schemas/multinf_ConfigGroupSchema.json @@ -6,7 +6,7 @@ "nginx-nfdg": { "type": "object", "properties": { - "deploymentParameters": { + "deployParameters": { "type": "object", "properties": { "serviceAccount_create": { @@ -27,7 +27,7 @@ } }, "required": [ - "deploymentParameters", + "deployParameters", "nginx_nfdg_nfd_version", "customLocationId" ] @@ -35,7 +35,7 @@ "ubuntu-nfdg": { "type": "object", "properties": { - "deploymentParameters": { + "deployParameters": { "type": "object", "properties": { "location": { @@ -58,7 +58,7 @@ } }, "required": [ - "deploymentParameters", + "deployParameters", "ubuntu_nfdg_nfd_version" ] }, diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep similarity index 91% rename from src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep rename to src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep index 8d85ac699e2..aefbb6dcd4f 100644 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/nsd_output/test_build_multiple_nfs/ubuntu-nfdg_nf.bicep @@ -25,9 +25,9 @@ param nfviType string = 'AzureCore' param resourceGroupId string = resourceGroup().id @secure() -param deploymentParametersObject object +param deployParametersObject object -var deploymentParameters = deploymentParametersObject.deploymentParameters +var deployParameters = deployParametersObject.deployParameters var identityObject = (managedIdentity == '') ? { type: 'SystemAssigned' @@ -54,7 +54,7 @@ resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroup } -resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deploymentParameters: { +resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deployParameters: { name: 'ubuntu-nfdg${i}' location: location identity: identityObject @@ -69,4 +69,4 @@ resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [fo configurationType: 'Secret' secretDeploymentValues: string(values) } -}] \ No newline at end of file +}] diff --git a/src/aosm/azext_aosm/tests/latest/recording_processors.py b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recording_processors.py similarity index 78% rename from src/aosm/azext_aosm/tests/latest/recording_processors.py rename to src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recording_processors.py index ae7b36b061f..22fd9453059 100644 --- a/src/aosm/azext_aosm/tests/latest/recording_processors.py +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recording_processors.py @@ -7,14 +7,16 @@ # the recordings so that we can avoid checking in secrets to the repo. # -------------------------------------------------------------------------------------------- -from azure.cli.testsdk.scenario_tests import RecordingProcessor -from azure.cli.testsdk.scenario_tests.utilities import is_text_payload import json import re +from azure.cli.testsdk.scenario_tests import RecordingProcessor +from azure.cli.testsdk.scenario_tests.utilities import is_text_payload + MOCK_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" MOCK_SAS_URI = "https://xxxxxxxxxxxxxxx.blob.core.windows.net" MOCK_STORAGE_ACCOUNT_SR = "&si=StorageAccountAccessPolicy&sr=xxxxxxxxxxxxxxxxxxxx" +MOCK_USERNAME = "xxxxxxxxxxx@microsoft.com" BLOB_STORE_URI_REGEX = r"https:\/\/[a-zA-Z0-9]+\.blob\.core\.windows\.net" STORAGE_ACCOUNT_SR_REGEX = r"&si=StorageAccountAccessPolicy&sr=.*" @@ -85,3 +87,29 @@ def process_request(self, request): pass return request + + +class UsernameReplacer(RecordingProcessor): + def process_response(self, response): + CREATEDBY = "createdBy" + LASTMODIFIEDBY = "lastModifiedBy" + SYSTEMDATA = "systemData" + if is_text_payload(response) and response["body"]["string"]: + try: + response_body = json.loads(response["body"]["string"]) + + if SYSTEMDATA not in response_body: + return response + + system_data = response_body[SYSTEMDATA] + + if CREATEDBY in system_data: + system_data[CREATEDBY] = MOCK_USERNAME + if LASTMODIFIEDBY in system_data: + system_data[LASTMODIFIEDBY] = MOCK_USERNAME + + response_body[SYSTEMDATA] = system_data + response["body"]["string"] = json.dumps(response_body) + except TypeError: + pass + return response diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_cnf_nfd_build_and_publish.yaml b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_cnf_nfd_build_and_publish.yaml new file mode 100644 index 00000000000..e48375f210a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_cnf_nfd_build_and_publish.yaml @@ -0,0 +1,1653 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: HEAD + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001?api-version=2022-09-01 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Wed, 15 May 2024 15:45:27 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: C24736659FD5488D8582658A4872D54A Ref B: AMS231020512033 Ref C: 2024-05-15T15:45:27Z' + status: + code: 204 + message: No Content +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "16280524387813352742"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "nfDefinitionGroup": + {"type": "string", "metadata": {"description": "Name of a Network Function Definition + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"value": "nginx-acr"}, + "nfDefinitionGroup": {"value": "nginx"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '1887' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715787933", + "name": "AOSM_CLI_deployment_1715787933", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16280524387813352742", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": "717adb6d-3ec1-4f3d-90e6-ec8b47939dca", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers", "locations": ["uksouth"]}, {"resourceType": "publishers/artifactStores", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkfunctiondefinitiongroups", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-nginx-publisher/nginx-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx", + "resourceType": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups", + "resourceName": "automated-cli-test-nginx-publisher/nginx"}], "validatedResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '3026' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:45:34 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: C1D84EF9D32E4E8689289F75A0C61C4F Ref B: AMS231020512033 Ref C: 2024-05-15T15:45:34Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "16280524387813352742"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "nfDefinitionGroup": + {"type": "string", "metadata": {"description": "Name of a Network Function Definition + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"value": "nginx-acr"}, + "nfDefinitionGroup": {"value": "nginx"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '1887' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715787933", + "name": "AOSM_CLI_deployment_1715787933", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16280524387813352742", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}}, "mode": "Incremental", "provisioningState": "Accepted", "timestamp": + "2024-05-15T15:45:35.354406Z", "duration": "PT0.0005935S", "correlationId": + "f0403d49-cca1-4802-b52a-f238847e2ffb", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkfunctiondefinitiongroups", "locations": ["uksouth"]}]}], + "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-nginx-publisher/nginx-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx", + "resourceType": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups", + "resourceName": "automated-cli-test-nginx-publisher/nginx"}]}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715787933/operationStatuses/08584858189504049589?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '2407' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:45:34 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1198' + x-msedge-ref: + - 'Ref A: 09CCBA4CDFD645DD9AC43C66793D5B5F Ref B: AMS231020512033 Ref C: 2024-05-15T15:45:34Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:45:34 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: E364349D5422433EA2FE41289FF6C17C Ref B: AMS231020512033 Ref C: 2024-05-15T15:45:35Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:46:04 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: C4C93C07060C4E62BF7A35EB8543E504 Ref B: AMS231020512033 Ref C: 2024-05-15T15:46:05Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:46:34 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: F84BFB8127014A68B44079D21C15161E Ref B: AMS231020512033 Ref C: 2024-05-15T15:46:35Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:47:04 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 2DFA308573F54A9AB3A5E1A2A03DB452 Ref B: AMS231020512033 Ref C: 2024-05-15T15:47:05Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:47:35 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 7997DC90B599460FB234A944D6263A6B Ref B: AMS231020512033 Ref C: 2024-05-15T15:47:35Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:48:06 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 54ED4E0B40D74B388BBFC6D46088E239 Ref B: AMS231020512033 Ref C: 2024-05-15T15:48:07Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:48:36 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 074683E34C6849BBBB75505779220D34 Ref B: AMS231020512033 Ref C: 2024-05-15T15:48:37Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:49:06 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 27076DE1E7CE4E96A96FAF19526AC4CA Ref B: AMS231020512033 Ref C: 2024-05-15T15:49:07Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:49:36 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 16CC1F39AE6047E1B6904BAB85C4EB8F Ref B: AMS231020512033 Ref C: 2024-05-15T15:49:37Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858189504049589?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:06 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 28A577FA6F494890ACE304DBB549F9E5 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:07Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715787933", + "name": "AOSM_CLI_deployment_1715787933", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16280524387813352742", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "2024-05-15T15:49:40.0429444Z", "duration": "PT4M4.6891319S", "correlationId": + "f0403d49-cca1-4802-b52a-f238847e2ffb", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkfunctiondefinitiongroups", "locations": ["uksouth"]}]}], + "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-nginx-publisher/nginx-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-nginx-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx", + "resourceType": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups", + "resourceName": "automated-cli-test-nginx-publisher/nginx"}], "outputResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '3041' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:06 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 8AD05E5274FF40C08AAB8316AF00A8E7 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:07Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-hybridnetwork/unknown Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr/artifactManifests/nginx-acr-manifest-1-0-0?api-version=2023-09-01 + response: + body: + string: '{"error": {"code": "ResourceNotFound", "message": "The Resource ''Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr/artifactManifests/nginx-acr-manifest-1-0-0'' + under resource group ''cli_test_cnf_nfd_000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '336' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:07 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: 047DD02C6AA44EBA98A783E68A1BB1D5 Ref B: AMS231022012027 Ref C: 2024-05-15T15:50:07Z' + status: + code: 404 + message: Not Found +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "8068031912378000682"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the manifest to deploy for the ACR-backed Artifact Store"}}}, "resources": + [{"type": "Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "nginxdemo", "artifactType": "OCIArtifact", "artifactVersion": "0.1.0"}, {"artifactName": + "nginx", "artifactType": "OCIArtifact", "artifactVersion": "stable"}]}}]}, "parameters": + {"location": {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-nginx-publisher"}, + "acrArtifactStoreName": {"value": "nginx-acr"}, "acrManifestName": {"value": + "nginx-acr-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '1517' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788212", + "name": "AOSM_CLI_deployment_1715788212", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "8068031912378000682", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "acrManifestName": {"type": "String", "value": + "nginx-acr-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": + "0d4e3b2e-2dfb-498e-9356-42bf3bcee085", "providers": [{"namespace": "Microsoft.Hybridnetwork", + "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "validatedResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr/artifactManifests/nginx-acr-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1239' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:12 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 364B03708AE64FE0B3B168D60DF0AC79 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:12Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "8068031912378000682"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the manifest to deploy for the ACR-backed Artifact Store"}}}, "resources": + [{"type": "Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "nginxdemo", "artifactType": "OCIArtifact", "artifactVersion": "0.1.0"}, {"artifactName": + "nginx", "artifactType": "OCIArtifact", "artifactVersion": "stable"}]}}]}, "parameters": + {"location": {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-nginx-publisher"}, + "acrArtifactStoreName": {"value": "nginx-acr"}, "acrManifestName": {"value": + "nginx-acr-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '1517' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788212", + "name": "AOSM_CLI_deployment_1715788212", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "8068031912378000682", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "acrManifestName": {"type": "String", "value": + "nginx-acr-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Accepted", "timestamp": "2024-05-15T15:50:13.8230612Z", "duration": "PT0.0005394S", + "correlationId": "51454ea2-5690-41a5-80fd-270f98207b1f", "providers": [{"namespace": + "Microsoft.Hybridnetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": []}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788212/operationStatuses/08584858186719967850?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '980' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: A30C475B698A43A0AAC5A3C8379CE2B1 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:13Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858186719967850?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 22C508C2781249708F15B34C998DED89 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:13Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858186719967850?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:50:43 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 043B0B8A67E842CC96CC6B8726276AC0 Ref B: AMS231020512033 Ref C: 2024-05-15T15:50:43Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858186719967850?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:51:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: B3D3F99E90644C16A94D2AE58B801455 Ref B: AMS231020512033 Ref C: 2024-05-15T15:51:14Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858186719967850?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:51:44 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 43891796A2A444E48413A82CD170D705 Ref B: AMS231020512033 Ref C: 2024-05-15T15:51:45Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788212", + "name": "AOSM_CLI_deployment_1715788212", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "8068031912378000682", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "acrManifestName": {"type": "String", "value": + "nginx-acr-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "2024-05-15T15:51:39.5757505Z", "duration": "PT1M25.7532287S", + "correlationId": "51454ea2-5690-41a5-80fd-270f98207b1f", "providers": [{"namespace": + "Microsoft.Hybridnetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "outputResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr/artifactManifests/nginx-acr-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1255' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:51:44 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 243BCB2313064E078ED1F884EC550A43 Ref B: AMS231020512033 Ref C: 2024-05-15T15:51:45Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-hybridnetwork/unknown Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-nginx-publisher/artifactStores/nginx-acr/artifactManifests/nginx-acr-manifest-1-0-0/listCredential?api-version=2023-09-01 + response: + body: + string: '{"username": "nginx-acr-manifest-1-0-0", "acrToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "acrServerUrl": "https://automatedclitestnginxpublishernginxacr02f144b6e2.azurecr.io", + "repositories": ["nginxdemo", "nginx"], "expiry": "2024-05-16T15:51:49.7749+00:00", + "credentialType": "AzureContainerRegistryScopedToken"}' + headers: + cache-control: + - no-cache + content-length: + - '334' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:51:49 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-build-version: + - 1.0.02682.2890 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '1198' + x-msedge-ref: + - 'Ref A: EFBDFA66560545FEA6BCAE9E787EB932 Ref B: AMS231022012011 Ref C: 2024-05-15T15:51:45Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "16747776660806024409"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "nfDefinitionGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Function Definition Group"}}, "nfDefinitionVersion": + {"type": "string", "metadata": {"description": "The version of the NFDV you + want to deploy, in format A.B.C"}}}, "variables": {"$fxv#0": {"$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", "type": "object", "properties": {}}, "$fxv#1": + {"replicaCount": 1, "image": {"repository": "overwriteme", "tag": "stable", + "pullPolicy": "IfNotPresent"}, "imagePullSecrets": [], "nameOverride": "", "fullnameOverride": + "", "serviceAccount": {"create": false, "name": "[null()]"}, "podSecurityContext": + {}, "securityContext": {}, "service": {"type": "ClusterIP", "port": 80}, "ingress": + {"enabled": false, "annotations": {}, "hosts": [{"host": "chart-example.local", + "paths": []}], "tls": []}, "resources": {}, "nodeSelector": {}, "tolerations": + [], "affinity": {}}}, "resources": [{"type": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''), parameters(''nfDefinitionVersion''))]", "location": + "[parameters(''location'')]", "properties": {"deployParameters": "[string(variables(''$fxv#0''))]", + "networkFunctionType": "ContainerizedNetworkFunction", "networkFunctionTemplate": + {"nfviType": "AzureArcKubernetes", "networkFunctionApplications": [{"artifactType": + "HelmPackage", "name": "nginxdemo", "dependsOnProfile": {"installDependsOn": + [], "uninstallDependsOn": [], "updateDependsOn": []}, "artifactProfile": {"artifactStore": + {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]"}, "helmArtifactProfile": {"helmPackageName": + "nginxdemo", "helmPackageVersionRange": "0.1.0", "registryValuesPaths": [], + "imagePullSecretsValuesPaths": []}}, "deployParametersMappingRuleProfile": {"applicationEnablement": + "Enabled", "helmMappingRuleProfile": {"releaseNamespace": "nginxdemo", "releaseName": + "nginxdemo", "helmPackageVersion": "0.1.0", "values": "[string(variables(''$fxv#1''))]"}}}]}}}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"value": "nginx-acr"}, + "nfDefinitionGroup": {"value": "nginx"}, "nfDefinitionVersion": {"value": "1.0.0"}}, + "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '3120' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788332", + "name": "AOSM_CLI_deployment_1715788332", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16747776660806024409", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, "mode": + "Incremental", "provisioningState": "Succeeded", "timestamp": "0001-01-01T00:00:00Z", + "duration": "PT0S", "correlationId": "ec9be03c-9634-44c6-9873-b81af4c77788", + "providers": [{"namespace": "Microsoft.Hybridnetwork", "resourceTypes": [{"resourceType": + "publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "locations": ["uksouth"]}]}], "dependencies": [], "validatedResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx/networkfunctiondefinitionversions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1327' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:52:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 3EC9B196BB5E4A6B905E556B6F65F26C Ref B: AMS231020512033 Ref C: 2024-05-15T15:52:13Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.8.9.13224", "templateHash": "16747776660806024409"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "nfDefinitionGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Function Definition Group"}}, "nfDefinitionVersion": + {"type": "string", "metadata": {"description": "The version of the NFDV you + want to deploy, in format A.B.C"}}}, "variables": {"$fxv#0": {"$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", "type": "object", "properties": {}}, "$fxv#1": + {"replicaCount": 1, "image": {"repository": "overwriteme", "tag": "stable", + "pullPolicy": "IfNotPresent"}, "imagePullSecrets": [], "nameOverride": "", "fullnameOverride": + "", "serviceAccount": {"create": false, "name": "[null()]"}, "podSecurityContext": + {}, "securityContext": {}, "service": {"type": "ClusterIP", "port": 80}, "ingress": + {"enabled": false, "annotations": {}, "hosts": [{"host": "chart-example.local", + "paths": []}], "tls": []}, "resources": {}, "nodeSelector": {}, "tolerations": + [], "affinity": {}}}, "resources": [{"type": "Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''), parameters(''nfDefinitionVersion''))]", "location": + "[parameters(''location'')]", "properties": {"deployParameters": "[string(variables(''$fxv#0''))]", + "networkFunctionType": "ContainerizedNetworkFunction", "networkFunctionTemplate": + {"nfviType": "AzureArcKubernetes", "networkFunctionApplications": [{"artifactType": + "HelmPackage", "name": "nginxdemo", "dependsOnProfile": {"installDependsOn": + [], "uninstallDependsOn": [], "updateDependsOn": []}, "artifactProfile": {"artifactStore": + {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]"}, "helmArtifactProfile": {"helmPackageName": + "nginxdemo", "helmPackageVersionRange": "0.1.0", "registryValuesPaths": [], + "imagePullSecretsValuesPaths": []}}, "deployParametersMappingRuleProfile": {"applicationEnablement": + "Enabled", "helmMappingRuleProfile": {"releaseNamespace": "nginxdemo", "releaseName": + "nginxdemo", "helmPackageVersion": "0.1.0", "values": "[string(variables(''$fxv#1''))]"}}}]}}}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"value": "nginx-acr"}, + "nfDefinitionGroup": {"value": "nginx"}, "nfDefinitionVersion": {"value": "1.0.0"}}, + "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '3120' + Content-Type: + - application/json + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788332", + "name": "AOSM_CLI_deployment_1715788332", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16747776660806024409", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, "mode": + "Incremental", "provisioningState": "Accepted", "timestamp": "2024-05-15T15:52:14.874135Z", + "duration": "PT0.0007292S", "correlationId": "faaf77d1-078d-4a78-a313-47abd2f765f9", + "providers": [{"namespace": "Microsoft.Hybridnetwork", "resourceTypes": [{"resourceType": + "publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "locations": ["uksouth"]}]}], "dependencies": []}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788332/operationStatuses/08584858185509487854?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '1057' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:52:14 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: C2C23D8018664680B541F9612BDA90EE Ref B: AMS231020512033 Ref C: 2024-05-15T15:52:14Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858185509487854?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:52:14 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: AA78064EBA6A4368AF9B289823E69032 Ref B: AMS231020512033 Ref C: 2024-05-15T15:52:14Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858185509487854?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:52:44 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 0810E1BE50884958AB08A818F570C24B Ref B: AMS231020512033 Ref C: 2024-05-15T15:52:44Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858185509487854?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:53:14 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 590839FE19744A32B8CE081632D322BE Ref B: AMS231020512033 Ref C: 2024-05-15T15:53:14Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584858185509487854?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:53:44 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: A037827BBA3348D3B1F38CCEF4ADA38F Ref B: AMS231020512033 Ref C: 2024-05-15T15:53:45Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - -b --definition-type + User-Agent: + - AZURECLI/2.56.0 azsdk-python-azure-mgmt-resource/23.1.0b2 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715788332", + "name": "AOSM_CLI_deployment_1715788332", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "16747776660806024409", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-nginx-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "nginx-acr"}, "nfDefinitionGroup": {"type": "String", "value": + "nginx"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, "mode": + "Incremental", "provisioningState": "Succeeded", "timestamp": "2024-05-15T15:53:37.7822316Z", + "duration": "PT1M22.9088258S", "correlationId": "faaf77d1-078d-4a78-a313-47abd2f765f9", + "providers": [{"namespace": "Microsoft.Hybridnetwork", "resourceTypes": [{"resourceType": + "publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "locations": ["uksouth"]}]}], "dependencies": [], "outputResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_cnf_nfd_000001/providers/Microsoft.Hybridnetwork/publishers/automated-cli-test-nginx-publisher/networkfunctiondefinitiongroups/nginx/networkfunctiondefinitionversions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1343' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 15 May 2024 15:53:44 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 42322D5D4C7F4B1283E1E433EFEB591D Ref B: AMS231020512033 Ref C: 2024-05-15T15:53:45Z' + status: + code: 200 + message: OK +version: 1 diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_vnf_nsd_build_and_publish.yaml b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_vnf_nsd_build_and_publish.yaml new file mode 100644 index 00000000000..da4f57897ee --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/recordings/test_vnf_nsd_build_and_publish.yaml @@ -0,0 +1,4854 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: HEAD + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001?api-version=2022-09-01 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Thu, 16 May 2024 14:28:49 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: AD2C76FAD1E54D1F99EA5A3FA94C8C90 Ref B: AMS231020512037 Ref C: 2024-05-16T14:28:50Z' + status: + code: 204 + message: No Content +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "4595863518953475042"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "saArtifactStoreName": + {"type": "string", "metadata": {"description": "Name of a Storage Account-backed + Artifact Store, deployed under the publisher."}}, "nfDefinitionGroup": {"type": + "string", "metadata": {"description": "Name of a Network Function Definition + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''saArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureStorageAccount"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": "ubuntu-acr"}, + "nfDefinitionGroup": {"value": "ubuntu-vm"}, "saArtifactStoreName": {"value": + "ubuntu-blob-store"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '2479' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715869734", + "name": "AOSM_CLI_deployment_1715869734", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4595863518953475042", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": "39932ade-c484-42db-b2dc-28b24b4a1506", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers", "locations": ["uksouth"]}, {"resourceType": "publishers/artifactStores", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkFunctionDefinitionGroups", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-blob-store"}, {"dependsOn": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm", + "resourceType": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu-vm"}], "validatedResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '4011' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:28:53 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 42C7DB1ECEC54FDD92877CFAC5AECA7B Ref B: AMS231020512037 Ref C: 2024-05-16T14:28:53Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "4595863518953475042"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "saArtifactStoreName": + {"type": "string", "metadata": {"description": "Name of a Storage Account-backed + Artifact Store, deployed under the publisher."}}, "nfDefinitionGroup": {"type": + "string", "metadata": {"description": "Name of a Network Function Definition + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''saArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureStorageAccount"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": "ubuntu-acr"}, + "nfDefinitionGroup": {"value": "ubuntu-vm"}, "saArtifactStoreName": {"value": + "ubuntu-blob-store"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '2479' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715869734", + "name": "AOSM_CLI_deployment_1715869734", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4595863518953475042", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}}, "mode": "Incremental", "provisioningState": "Accepted", "timestamp": + "2024-05-16T14:28:54.7216161Z", "duration": "PT0.0006708S", "correlationId": + "8335d8ef-b4bd-48b7-acad-f3325b5c4325", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkFunctionDefinitionGroups", "locations": ["uksouth"]}]}], + "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-blob-store"}, {"dependsOn": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm", + "resourceType": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu-vm"}]}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715869734/operationStatuses/08584857371511338795?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '3169' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:28:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 18CE4F9D0AB14F31979F8C690D9E4B66 Ref B: AMS231020512037 Ref C: 2024-05-16T14:28:54Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:28:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 90F1F27179364F708BC797A50CF132C8 Ref B: AMS231020512037 Ref C: 2024-05-16T14:28:54Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:29:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 01FD5D1C44354DFD9D6586E10202232B Ref B: AMS231020512037 Ref C: 2024-05-16T14:29:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:29:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: AFA6FB8BADDF44A691E6592BDE88B8E5 Ref B: AMS231020512037 Ref C: 2024-05-16T14:29:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:30:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: A0E46971E846476A8E621D334519371E Ref B: AMS231020512037 Ref C: 2024-05-16T14:30:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:30:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: FE8FE3862EF643979F1A6679B29C199F Ref B: AMS231020512037 Ref C: 2024-05-16T14:30:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:31:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: D0536A75A28342799D77A619DA468D42 Ref B: AMS231020512037 Ref C: 2024-05-16T14:31:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:31:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 7ABDB4FA42994E888720C027EBF1649E Ref B: AMS231020512037 Ref C: 2024-05-16T14:31:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:32:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: B1228E75B9984CFFB48A73397691B431 Ref B: AMS231020512037 Ref C: 2024-05-16T14:32:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:32:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: FD92EA18650E407EB8D180B9ADDA1A29 Ref B: AMS231020512037 Ref C: 2024-05-16T14:32:56Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:33:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 1028A3D80F56490DA9ED118A6C002739 Ref B: AMS231020512037 Ref C: 2024-05-16T14:33:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857371511338795?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:33:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: D40A9A6B137F4AFFBD780704F211DEE1 Ref B: AMS231020512037 Ref C: 2024-05-16T14:33:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715869734", + "name": "AOSM_CLI_deployment_1715869734", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4595863518953475042", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "2024-05-16T14:33:46.23604Z", "duration": "PT4M51.5150947S", "correlationId": + "8335d8ef-b4bd-48b7-acad-f3325b5c4325", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkFunctionDefinitionGroups", "locations": ["uksouth"]}]}], + "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-blob-store"}, {"dependsOn": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm", + "resourceType": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu-vm"}], "outputResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '4025' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:33:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: C1AA4C2BCA634D63A57A37BD8EC7D9B7 Ref B: AMS231020512037 Ref C: 2024-05-16T14:33:56Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-vm-acr-manifest-1-0-0?api-version=2023-09-01 + response: + body: + string: '{"error": {"code": "ResourceNotFound", "message": "The Resource ''Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-vm-acr-manifest-1-0-0'' + under resource group ''cli_test_vnf_nsd_000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '342' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:34:28 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: 9ED4EBE79F844B02A195211DD60AD82D Ref B: AMS231020615039 Ref C: 2024-05-16T14:34:29Z' + status: + code: 404 + message: Not Found +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store/artifactManifests/ubuntu-vm-sa-manifest-1-0-0?api-version=2023-09-01 + response: + body: + string: '{"error": {"code": "ResourceNotFound", "message": "The Resource ''Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store/artifactManifests/ubuntu-vm-sa-manifest-1-0-0'' + under resource group ''cli_test_vnf_nsd_000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '348' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:34:28 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: 8F0AD4CD3E97470C84C78F9BD231FD09 Ref B: AMS231020615039 Ref C: 2024-05-16T14:34:29Z' + status: + code: 404 + message: Not Found +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "14347213902282880649"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "saArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an existing Storage Account-backed Artifact Store, deployed under the + publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the manifest to deploy for the ACR-backed Artifact Store"}}, "saManifestName": + {"type": "string", "metadata": {"description": "Name of the manifest to deploy + for the Storage Account-backed Artifact Store"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "automated-cli-tests-artifact", "artifactType": "ArmTemplate", "artifactVersion": + "1.0.0"}]}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''saArtifactStoreName''), parameters(''saManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "automated-cli-tests-vhd", "artifactType": "VhdImageFile", "artifactVersion": + "1-0-0"}]}}]}, "parameters": {"location": {"value": "uksouth"}, "publisherName": + {"value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": + "ubuntu-acr"}, "acrManifestName": {"value": "ubuntu-vm-acr-manifest-1-0-0"}, + "saArtifactStoreName": {"value": "ubuntu-blob-store"}, "saManifestName": {"value": + "ubuntu-vm-sa-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '2298' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870072", + "name": "AOSM_CLI_deployment_1715870072", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14347213902282880649", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "acrManifestName": {"type": "String", "value": + "ubuntu-vm-acr-manifest-1-0-0"}, "saManifestName": {"type": "String", "value": + "ubuntu-vm-sa-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": + "91d4d736-f279-4b5b-b9b7-ec0174d29ed0", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "validatedResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-vm-acr-manifest-1-0-0"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store/artifactManifests/ubuntu-vm-sa-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1665' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:34:31 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: C2A163E32ED54BC78625B0B86D3F33A1 Ref B: AMS231020512037 Ref C: 2024-05-16T14:34:31Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "14347213902282880649"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "saArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an existing Storage Account-backed Artifact Store, deployed under the + publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the manifest to deploy for the ACR-backed Artifact Store"}}, "saManifestName": + {"type": "string", "metadata": {"description": "Name of the manifest to deploy + for the Storage Account-backed Artifact Store"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "automated-cli-tests-artifact", "artifactType": "ArmTemplate", "artifactVersion": + "1.0.0"}]}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''saArtifactStoreName''), parameters(''saManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "automated-cli-tests-vhd", "artifactType": "VhdImageFile", "artifactVersion": + "1-0-0"}]}}]}, "parameters": {"location": {"value": "uksouth"}, "publisherName": + {"value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": + "ubuntu-acr"}, "acrManifestName": {"value": "ubuntu-vm-acr-manifest-1-0-0"}, + "saArtifactStoreName": {"value": "ubuntu-blob-store"}, "saManifestName": {"value": + "ubuntu-vm-sa-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '2298' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870072", + "name": "AOSM_CLI_deployment_1715870072", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14347213902282880649", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "acrManifestName": {"type": "String", "value": + "ubuntu-vm-acr-manifest-1-0-0"}, "saManifestName": {"type": "String", "value": + "ubuntu-vm-sa-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Accepted", "timestamp": "2024-05-16T14:34:33.1985759Z", "duration": "PT0.0007643S", + "correlationId": "21b20187-393d-45e2-a70b-c66918f9485c", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": []}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870072/operationStatuses/08584857368125612072?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '1138' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:34:32 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: CDE37942554E4C30A4ED42459DFD0AA1 Ref B: AMS231020512037 Ref C: 2024-05-16T14:34:32Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857368125612072?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:34:32 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 6C9D0379DCED491C8D90F69FEA165FE4 Ref B: AMS231020512037 Ref C: 2024-05-16T14:34:33Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857368125612072?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:35:02 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: D4E298279358489DB07D929BD0E7F4F5 Ref B: AMS231020512037 Ref C: 2024-05-16T14:35:03Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857368125612072?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:35:32 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 1F7CECC7194D4233B79F7AFE4DFEEBC6 Ref B: AMS231020512037 Ref C: 2024-05-16T14:35:33Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857368125612072?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:03 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 37CB452424B8445D93C2C936672EFD2A Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:03Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857368125612072?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:33 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 6D3D321334984AFCAB406B73FEC6F953 Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:33Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870072", + "name": "AOSM_CLI_deployment_1715870072", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14347213902282880649", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "acrManifestName": {"type": "String", "value": + "ubuntu-vm-acr-manifest-1-0-0"}, "saManifestName": {"type": "String", "value": + "ubuntu-vm-sa-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "2024-05-16T14:36:16.4466891Z", "duration": "PT1M43.2488775S", + "correlationId": "21b20187-393d-45e2-a70b-c66918f9485c", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "outputResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-vm-acr-manifest-1-0-0"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store/artifactManifests/ubuntu-vm-sa-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1681' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:33 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 65274D6A07BC4C64A8A092C50CC4336A Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:33Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-vm-acr-manifest-1-0-0/listCredential?api-version=2023-09-01 + response: + body: + string: '{"username": "ubuntu-vm-acr-manifest-1-0-0", "acrToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "acrServerUrl": "https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io", + "repositories": ["automated-cli-tests-artifact"], "expiry": "2024-05-17T14:36:38.2463159+00:00", + "credentialType": "AzureContainerRegistryScopedToken"}' + headers: + cache-control: + - no-cache + content-length: + - '353' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:37 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-build-version: + - 1.0.02682.2890 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 332D362C122F436BB36AEFF3BE0E2427 Ref B: AMS231032608051 Ref C: 2024-05-16T14:36:34Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/ + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "pull"}, {"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '314' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:automated-cli-tests-artifact:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aautomated-cli-tests-artifact%3Apull%2Cpush + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.316667' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - d53e5025-f57c-4558-9611-09ba06a94983 + location: + - /v2/automated-cli-tests-artifact/blobs/uploads/d53e5025-f57c-4558-9611-09ba06a94983?_nouploadcache=false&_state=8xdnSSRzHZDciJvrYKnJx8CBGshX8ah9RaXTnvnkxD97Ik5hbWUiOiJhdXRvbWF0ZWQtY2xpLXRlc3RzLWFydGlmYWN0IiwiVVVJRCI6ImQ1M2U1MDI1LWY1N2MtNDU1OC05NjExLTA5YmEwNmE5NDk4MyIsIk9mZnNldCI6MCwiU3RhcnRlZEF0IjoiMjAyNC0wNS0xNlQxNDozNjozOS4yMzMzNjcyMTRaIn0%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: "{\n \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#\",\n + \ \"contentVersion\": \"1.0.0.0\",\n \"metadata\": {\n \"_generator\": {\n + \ \"name\": \"bicep\",\n \"version\": \"0.8.9.13224\",\n \"templateHash\": + \"14979664264804385741\"\n }\n },\n \"parameters\": {\n \"location\": + {\n \"type\": \"string\",\n \"defaultValue\": \"[resourceGroup().location]\"\n + \ },\n \"subnetName\": {\n \"type\": \"string\"\n },\n \"ubuntuVmName\": + {\n \"type\": \"string\",\n \"defaultValue\": \"ubuntu-vm\"\n },\n + \ \"virtualNetworkId\": {\n \"type\": \"string\"\n },\n \"sshPublicKeyAdmin\": + {\n \"type\": \"string\"\n },\n \"imageName\": {\n \"type\": + \"string\"\n }\n },\n \"variables\": {\n \"imageResourceGroup\": \"[resourceGroup().name]\",\n + \ \"subscriptionId\": \"[subscription().subscriptionId]\",\n \"vmSizeSku\": + \"Standard_D2s_v3\"\n },\n \"resources\": [\n {\n \"type\": \"Microsoft.Network/networkInterfaces\",\n + \ \"apiVersion\": \"2021-05-01\",\n \"name\": \"[format('{0}_nic', + parameters('ubuntuVmName'))]\",\n \"location\": \"[parameters('location')]\",\n + \ \"properties\": {\n \"ipConfigurations\": [\n {\n \"name\": + \"ipconfig1\",\n \"properties\": {\n \"subnet\": {\n + \ \"id\": \"[format('{0}/subnets/{1}', parameters('virtualNetworkId'), + parameters('subnetName'))]\"\n },\n \"primary\": true,\n + \ \"privateIPAddressVersion\": \"IPv4\"\n }\n }\n + \ ]\n }\n },\n {\n \"type\": \"Microsoft.Compute/virtualMachines\",\n + \ \"apiVersion\": \"2021-07-01\",\n \"name\": \"[parameters('ubuntuVmName')]\",\n + \ \"location\": \"[parameters('location')]\",\n \"properties\": {\n + \ \"hardwareProfile\": {\n \"vmSize\": \"[variables('vmSizeSku')]\"\n + \ },\n \"storageProfile\": {\n \"imageReference\": {\n + \ \"id\": \"[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', + variables('subscriptionId'), variables('imageResourceGroup')), 'Microsoft.Compute/images', + parameters('imageName'))]\"\n },\n \"osDisk\": {\n \"osType\": + \"Linux\",\n \"name\": \"[format('{0}_disk', parameters('ubuntuVmName'))]\",\n + \ \"createOption\": \"FromImage\",\n \"caching\": \"ReadWrite\",\n + \ \"writeAcceleratorEnabled\": false,\n \"managedDisk\": + \"[json('{\\\"storageAccountType\\\": \\\"Premium_LRS\\\"}')]\",\n \"deleteOption\": + \"Delete\",\n \"diskSizeGB\": 30\n }\n },\n \"osProfile\": + {\n \"computerName\": \"[parameters('ubuntuVmName')]\",\n \"adminUsername\": + \"azureuser\",\n \"linuxConfiguration\": {\n \"disablePasswordAuthentication\": + true,\n \"ssh\": {\n \"publicKeys\": [\n {\n + \ \"path\": \"/home/azureuser/.ssh/authorized_keys\",\n \"keyData\": + \"[parameters('sshPublicKeyAdmin')]\"\n }\n ]\n + \ },\n \"provisionVMAgent\": true,\n \"patchSettings\": + {\n \"patchMode\": \"ImageDefault\",\n \"assessmentMode\": + \"ImageDefault\"\n }\n },\n \"secrets\": [],\n + \ \"allowExtensionOperations\": true\n },\n \"networkProfile\": + {\n \"networkInterfaces\": [\n {\n \"id\": + \"[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]\"\n + \ }\n ]\n }\n },\n \"dependsOn\": [\n \"[resourceId('Microsoft.Network/networkInterfaces', + format('{0}_nic', parameters('ubuntuVmName')))]\"\n ]\n }\n ]\n}" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '3591' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/d53e5025-f57c-4558-9611-09ba06a94983?_nouploadcache=false&_state=8xdnSSRzHZDciJvrYKnJx8CBGshX8ah9RaXTnvnkxD97Ik5hbWUiOiJhdXRvbWF0ZWQtY2xpLXRlc3RzLWFydGlmYWN0IiwiVVVJRCI6ImQ1M2U1MDI1LWY1N2MtNDU1OC05NjExLTA5YmEwNmE5NDk4MyIsIk9mZnNldCI6MCwiU3RhcnRlZEF0IjoiMjAyNC0wNS0xNlQxNDozNjozOS4yMzMzNjcyMTRaIn0%3D&digest=sha256%3Ae71bf56543dc33dc8e550a0c574efe9a4875754a4ddf74347e448dec2462798b + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-content-digest: + - sha256:e71bf56543dc33dc8e550a0c574efe9a4875754a4ddf74347e448dec2462798b + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/automated-cli-tests-artifact/blobs/sha256:e71bf56543dc33dc8e550a0c574efe9a4875754a4ddf74347e448dec2462798b + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/ + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "pull"}, {"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '314' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:automated-cli-tests-artifact:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aautomated-cli-tests-artifact%3Apull%2Cpush + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.3' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - b3156925-14b9-407f-8f4c-c4999986117f + location: + - /v2/automated-cli-tests-artifact/blobs/uploads/b3156925-14b9-407f-8f4c-c4999986117f?_nouploadcache=false&_state=-h9TZbERV2wU9XwXY8AgrKojJehLG5Q4wloS1HK_dyB7Ik5hbWUiOiJhdXRvbWF0ZWQtY2xpLXRlc3RzLWFydGlmYWN0IiwiVVVJRCI6ImIzMTU2OTI1LTE0YjktNDA3Zi04ZjRjLWM0OTk5OTg2MTE3ZiIsIk9mZnNldCI6MCwiU3RhcnRlZEF0IjoiMjAyNC0wNS0xNlQxNDozNjozOS41MjkzODM1NDZaIn0%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: '{}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/blobs/uploads/b3156925-14b9-407f-8f4c-c4999986117f?_nouploadcache=false&_state=-h9TZbERV2wU9XwXY8AgrKojJehLG5Q4wloS1HK_dyB7Ik5hbWUiOiJhdXRvbWF0ZWQtY2xpLXRlc3RzLWFydGlmYWN0IiwiVVVJRCI6ImIzMTU2OTI1LTE0YjktNDA3Zi04ZjRjLWM0OTk5OTg2MTE3ZiIsIk9mZnNldCI6MCwiU3RhcnRlZEF0IjoiMjAyNC0wNS0xNlQxNDozNjozOS41MjkzODM1NDZaIn0%3D&digest=sha256%3A44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-content-digest: + - sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/automated-cli-tests-artifact/blobs/sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 3591, + "digest": "sha256:e71bf56543dc33dc8e550a0c574efe9a4875754a4ddf74347e448dec2462798b", + "annotations": {"org.opencontainers.image.title": "ubuntu_template.json"}}], + "annotations": {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '504' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/manifests/1.0.0 + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "pull"}, {"Type": + "repository", "Name": "automated-cli-tests-artifact", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '314' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:automated-cli-tests-artifact:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aautomated-cli-tests-artifact%3Apull%2Cpush + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:39 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.283333' + status: + code: 200 + message: OK +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 3591, + "digest": "sha256:e71bf56543dc33dc8e550a0c574efe9a4875754a4ddf74347e448dec2462798b", + "annotations": {"org.opencontainers.image.title": "ubuntu_template.json"}}], + "annotations": {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '504' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/automated-cli-tests-artifact/manifests/1.0.0 + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:40 GMT + docker-content-digest: + - sha256:17d25d33c38f40fda4c9e45a0cec218b3f7f75e9c680ed8dd2a12ed0abdbc36b + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/automated-cli-tests-artifact/manifests/sha256:17d25d33c38f40fda4c9e45a0cec218b3f7f75e9c680ed8dd2a12ed0abdbc36b + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store/artifactManifests/ubuntu-vm-sa-manifest-1-0-0/listCredential?api-version=2023-09-01 + response: + body: + string: '{"storageAccountId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/ubuntu-blob-store-HostedResources-27DC001E/providers/Microsoft.Storage/storageAccounts/27dc001eubuntublobstore0", + "containerCredentials": [{"containerName": "automatedclitestsvhd-1-0-0", "containerSasUri": + "https://xxxxxxxxxxxxxxx.blob.core.windows.net/automatedclitestsvhd-1-0-0?sv=2021-08-06&si=StorageAccountAccessPolicy&sr=xxxxxxxxxxxxxxxxxxxx"}], + "expiry": "2024-05-17T14:36:44.2909519+00:00", "credentialType": "AzureStorageAccountToken"}' + headers: + cache-control: + - no-cache + content-length: + - '533' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:43 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-build-version: + - 1.0.02682.2890 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 0F7F5CE73C3D41A0928517F3F49139F5 Ref B: AMS231032608051 Ref C: 2024-05-16T14:36:40Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.16.0 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + x-ms-blob-content-length: + - '512' + x-ms-blob-type: + - PageBlob + x-ms-date: + - Thu, 16 May 2024 14:36:45 GMT + x-ms-version: + - '2022-11-02' + method: PUT + uri: https://xxxxxxxxxxxxxxx.blob.core.windows.net/automatedclitestsvhd-1-0-0/automatedclitests-1-0-0.vhd?sv=2021-08-06&si=StorageAccountAccessPolicy&sr=xxxxxxxxxxxxxxxxxxxx + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:44 GMT + etag: + - '"0x8DC75B59BFDC601"' + last-modified: + - Thu, 16 May 2024 14:36:44 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2022-11-02' + status: + code: 201 + message: Created +- request: + body: "61 38 6b 92 16 4b cc ac 6f d4 02 5c 6f 62 79 b9 \n8e 62 ae 07 02 1c dc + 73 5b 7a 51 e7 56 4e 4a b0 \n54 4a 93 2e 6b dd 3c b5 8b 60 fa 80 b1 80 1b 89 + \n1e 4d 7d 86 8e 25 76 58 24 8d 21 87 83 06 88 d6 \na4 fd 94 9c 66 b6 db ee + 92 46 f0 25 fc 84 bb f5 \n3f d9 49 28 ea 54 6a 2a 33 fa e0 47 eb 22 af 91 \nd4 + 34 a6 d9 fe 58 cb 54 03 35 d6 45 40 96 4e f3 \n31 ea 78 20 45 e9 f2 3a de cb + 38 53 c0 9c b2 b7 \n12 9e 57 d9 f6 1b cb 20 23 8c 86 d3 40 da 84 c3 \n22 5b + 48 61 63 e2 5f 5f 43 6d 8f 41 fc ce c1 87 \n33 e1 e2 61 63 e2 5f 5" + headers: + Accept: + - application/xml + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '512' + Content-Type: + - application/octet-stream + If-Match: + - '"0x8DC75B59BFDC601"' + User-Agent: + - azsdk-python-storage-blob/12.16.0 Python/3.8.10 (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + x-ms-date: + - Thu, 16 May 2024 14:36:45 GMT + x-ms-page-write: + - update + x-ms-range: + - bytes=0-511 + x-ms-version: + - '2022-11-02' + method: PUT + uri: https://xxxxxxxxxxxxxxx.blob.core.windows.net/automatedclitestsvhd-1-0-0/automatedclitests-1-0-0.vhd?comp=page&sv=2021-08-06&si=StorageAccountAccessPolicy&sr=xxxxxxxxxxxxxxxxxxxx + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Thu, 16 May 2024 14:36:44 GMT + etag: + - '"0x8DC75B59C1A07EE"' + last-modified: + - Thu, 16 May 2024 14:36:44 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-blob-sequence-number: + - '0' + x-ms-content-crc64: + - iWvWqElPrJg= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2022-11-02' + status: + code: 201 + message: Created +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "12072405825285278441"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "saArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an existing Storage Account-backed Artifact Store, deployed under the + publisher."}}, "nfDefinitionGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Function Definition Group"}}, "nfDefinitionVersion": + {"type": "string", "metadata": {"description": "The version of the NFDV you + want to deploy, in format A.B.C"}}}, "variables": {"$fxv#0": {"$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", "type": "object", "properties": {"automated-cli-tests-artifact": + {"type": "object", "properties": {"subnetName": {"type": "string"}, "virtualNetworkId": + {"type": "string"}, "sshPublicKeyAdmin": {"type": "string"}}, "required": ["subnetName", + "virtualNetworkId", "sshPublicKeyAdmin"]}}}, "$fxv#1": {"imageHyperVGeneration": + "V1", "imageName": "ubuntu-vmImage"}, "$fxv#2": {"imageName": "ubuntu-vmImage", + "subnetName": "{deployParameters.automated-cli-tests-artifact.subnetName}", + "virtualNetworkId": "{deployParameters.automated-cli-tests-artifact.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.automated-cli-tests-artifact.sshPublicKeyAdmin}"}}, + "resources": [{"type": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''), parameters(''nfDefinitionVersion''))]", "location": + "[parameters(''location'')]", "properties": {"deployParameters": "[string(variables(''$fxv#0''))]", + "networkFunctionType": "VirtualNetworkFunction", "networkFunctionTemplate": + {"nfviType": "AzureCore", "networkFunctionApplications": [{"artifactType": "VhdImageFile", + "name": "automated-cli-tests-vhd", "dependsOnProfile": null, "artifactProfile": + {"vhdArtifactProfile": {"vhdName": "automated-cli-tests-vhd", "vhdVersion": + "1-0-0"}, "artifactStore": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''saArtifactStoreName''))]"}}, "deployParametersMappingRuleProfile": + {"vhdImageMappingRuleProfile": {"userConfiguration": "[string(variables(''$fxv#1''))]"}, + "applicationEnablement": "Enabled"}}, {"artifactType": "ArmTemplate", "name": + "automated-cli-tests-artifact", "dependsOnProfile": null, "artifactProfile": + {"templateArtifactProfile": {"templateName": "automated-cli-tests-artifact", + "templateVersion": "1.0.0"}, "artifactStore": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''acrArtifactStoreName''))]"}}, "deployParametersMappingRuleProfile": + {"templateMappingRuleProfile": {"templateParameters": "[string(variables(''$fxv#2''))]"}, + "applicationEnablement": "Enabled"}}]}}}]}, "parameters": {"location": {"value": + "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "nfDefinitionGroup": {"value": + "ubuntu-vm"}, "nfDefinitionVersion": {"value": "1.0.0"}, "saArtifactStoreName": + {"value": "ubuntu-blob-store"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '3770' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870208", + "name": "AOSM_CLI_deployment_1715870208", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "12072405825285278441", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, + "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": "0001-01-01T00:00:00Z", + "duration": "PT0S", "correlationId": "c7deb169-866e-4c11-878a-ddc5da87ba22", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions", + "locations": ["uksouth"]}]}], "dependencies": [], "validatedResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm/networkFunctionDefinitionVersions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1411' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:48 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 21DEDC16052943479892D31B36057D91 Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:48Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "12072405825285278441"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "saArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an existing Storage Account-backed Artifact Store, deployed under the + publisher."}}, "nfDefinitionGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Function Definition Group"}}, "nfDefinitionVersion": + {"type": "string", "metadata": {"description": "The version of the NFDV you + want to deploy, in format A.B.C"}}}, "variables": {"$fxv#0": {"$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", "type": "object", "properties": {"automated-cli-tests-artifact": + {"type": "object", "properties": {"subnetName": {"type": "string"}, "virtualNetworkId": + {"type": "string"}, "sshPublicKeyAdmin": {"type": "string"}}, "required": ["subnetName", + "virtualNetworkId", "sshPublicKeyAdmin"]}}}, "$fxv#1": {"imageHyperVGeneration": + "V1", "imageName": "ubuntu-vmImage"}, "$fxv#2": {"imageName": "ubuntu-vmImage", + "subnetName": "{deployParameters.automated-cli-tests-artifact.subnetName}", + "virtualNetworkId": "{deployParameters.automated-cli-tests-artifact.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.automated-cli-tests-artifact.sshPublicKeyAdmin}"}}, + "resources": [{"type": "Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nfDefinitionGroup''), parameters(''nfDefinitionVersion''))]", "location": + "[parameters(''location'')]", "properties": {"deployParameters": "[string(variables(''$fxv#0''))]", + "networkFunctionType": "VirtualNetworkFunction", "networkFunctionTemplate": + {"nfviType": "AzureCore", "networkFunctionApplications": [{"artifactType": "VhdImageFile", + "name": "automated-cli-tests-vhd", "dependsOnProfile": null, "artifactProfile": + {"vhdArtifactProfile": {"vhdName": "automated-cli-tests-vhd", "vhdVersion": + "1-0-0"}, "artifactStore": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''saArtifactStoreName''))]"}}, "deployParametersMappingRuleProfile": + {"vhdImageMappingRuleProfile": {"userConfiguration": "[string(variables(''$fxv#1''))]"}, + "applicationEnablement": "Enabled"}}, {"artifactType": "ArmTemplate", "name": + "automated-cli-tests-artifact", "dependsOnProfile": null, "artifactProfile": + {"templateArtifactProfile": {"templateName": "automated-cli-tests-artifact", + "templateVersion": "1.0.0"}, "artifactStore": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''acrArtifactStoreName''))]"}}, "deployParametersMappingRuleProfile": + {"templateMappingRuleProfile": {"templateParameters": "[string(variables(''$fxv#2''))]"}, + "applicationEnablement": "Enabled"}}]}}}]}, "parameters": {"location": {"value": + "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "nfDefinitionGroup": {"value": + "ubuntu-vm"}, "nfDefinitionVersion": {"value": "1.0.0"}, "saArtifactStoreName": + {"value": "ubuntu-blob-store"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + Content-Length: + - '3770' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870208", + "name": "AOSM_CLI_deployment_1715870208", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "12072405825285278441", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, + "mode": "Incremental", "provisioningState": "Accepted", "timestamp": "2024-05-16T14:36:49.3919603Z", + "duration": "PT0.0001257S", "correlationId": "94c52cd6-6687-4099-b305-c400c7bab006", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions", + "locations": ["uksouth"]}]}], "dependencies": []}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870208/operationStatuses/08584857366763467572?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '1137' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:48 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 1773C241F91245F798AC7D0374B60CA7 Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:48Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857366763467572?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:36:48 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 10F42172564345DB84F7BB6079FADCC9 Ref B: AMS231020512037 Ref C: 2024-05-16T14:36:49Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857366763467572?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:37:19 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: FF5988B9DF4B44EC81099EE6DDBF2BE6 Ref B: AMS231020512037 Ref C: 2024-05-16T14:37:19Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857366763467572?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:37:48 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: AF81C1EB11F94534AD9195D34AC592AB Ref B: AMS231020512037 Ref C: 2024-05-16T14:37:49Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857366763467572?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:19 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 4E3F0AD9D93A454395BD81A04F0DA716 Ref B: AMS231020512037 Ref C: 2024-05-16T14:38:19Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nfd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder --definition-type + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870208", + "name": "AOSM_CLI_deployment_1715870208", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "12072405825285278441", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "saArtifactStoreName": {"type": "String", + "value": "ubuntu-blob-store"}, "nfDefinitionGroup": {"type": "String", "value": + "ubuntu-vm"}, "nfDefinitionVersion": {"type": "String", "value": "1.0.0"}}, + "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": "2024-05-16T14:38:13.9756313Z", + "duration": "PT1M24.5837967S", "correlationId": "94c52cd6-6687-4099-b305-c400c7bab006", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions", + "locations": ["uksouth"]}]}], "dependencies": [], "outputResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm/networkFunctionDefinitionVersions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1427' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:19 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 48BEF6A2C62147F4A1CDDD487E8B5506 Ref B: AMS231020512037 Ref C: 2024-05-16T14:38:19Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd build + Connection: + - keep-alive + ParameterSetName: + - -f + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm/networkFunctionDefinitionVersions/1.0.0?api-version=2023-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkFunctionDefinitionGroups/ubuntu-vm/networkFunctionDefinitionVersions/1.0.0", + "name": "1.0.0", "type": "microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions", + "location": "uksouth", "systemData": {"createdBy": "xxxxxxxxxxx@microsoft.com", + "createdByType": "User", "createdAt": "2024-05-16T14:36:49.8348379Z", "lastModifiedBy": + "xxxxxxxxxxx@microsoft.com", "lastModifiedByType": "User", "lastModifiedAt": + "2024-05-16T14:36:49.8348379Z"}, "properties": {"networkFunctionTemplate": + {"networkFunctionApplications": [{"artifactProfile": {"vhdArtifactProfile": + {"vhdName": "automated-cli-tests-vhd", "vhdVersion": "1-0-0"}, "artifactStore": + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-blob-store"}}, + "deployParametersMappingRuleProfile": {"vhdImageMappingRuleProfile": {"userConfiguration": + "{\"imageHyperVGeneration\":\"V1\",\"imageName\":\"ubuntu-vmImage\"}"}, "applicationEnablement": + "Enabled"}, "artifactType": "VhdImageFile", "dependsOnProfile": null, "name": + "automated-cli-tests-vhd"}, {"artifactProfile": {"templateArtifactProfile": + {"templateName": "automated-cli-tests-artifact", "templateVersion": "1.0.0"}, + "artifactStore": {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr"}}, + "deployParametersMappingRuleProfile": {"templateMappingRuleProfile": {"templateParameters": + "{\"imageName\":\"ubuntu-vmImage\",\"subnetName\":\"{deployParameters.automated-cli-tests-artifact.subnetName}\",\"virtualNetworkId\":\"{deployParameters.automated-cli-tests-artifact.virtualNetworkId}\",\"sshPublicKeyAdmin\":\"{deployParameters.automated-cli-tests-artifact.sshPublicKeyAdmin}\"}"}, + "applicationEnablement": "Enabled"}, "artifactType": "ArmTemplate", "dependsOnProfile": + null, "name": "automated-cli-tests-artifact"}], "nfviType": "AzureCore"}, + "versionState": "Preview", "description": null, "deployParameters": "{\"$schema\":\"https://json-schema.org/draft-07/schema#\",\"title\":\"DeployParametersSchema\",\"type\":\"object\",\"properties\":{\"automated-cli-tests-artifact\":{\"type\":\"object\",\"properties\":{\"subnetName\":{\"type\":\"string\"},\"virtualNetworkId\":{\"type\":\"string\"},\"sshPublicKeyAdmin\":{\"type\":\"string\"}},\"required\":[\"subnetName\",\"virtualNetworkId\",\"sshPublicKeyAdmin\"]}},\"required\":[\"automated-cli-tests-artifact\"]}", + "networkFunctionType": "VirtualNetworkFunction", "provisioningState": "Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '2890' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:20 GMT + etag: + - '"8f025ba3-0000-1100-0000-66461a1a0000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-providerhub-traffic: + - 'True' + x-msedge-ref: + - 'Ref A: 5F7B77EC7239415C9B8B1455BE15F146 Ref B: AMS231022012019 Ref C: 2024-05-16T14:38:20Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: HEAD + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001?api-version=2022-09-01 + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Thu, 16 May 2024 14:38:20 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: B794838D137F49C8945CAB4D85A95288 Ref B: AMS231032609017 Ref C: 2024-05-16T14:38:20Z' + status: + code: 204 + message: No Content +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "14743947880900953734"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "nsDesignGroup": + {"type": "string", "metadata": {"description": "Name of an Network Service Design + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nsDesignGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": "ubuntu-acr"}, + "nsDesignGroup": {"value": "ubuntu"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '1871' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870304", + "name": "AOSM_CLI_deployment_1715870304", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14743947880900953734", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": "e4c54af1-ab5f-4681-b41c-cfd87663f83b", + "providers": [{"namespace": "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": + "publishers", "locations": ["uksouth"]}, {"resourceType": "publishers/artifactStores", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkServiceDesignGroups", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu"}], "validatedResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '3022' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:23 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: E4DBD709D696466995C00F69B3BB6CBD Ref B: AMS231032609017 Ref C: 2024-05-16T14:38:23Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "14743947880900953734"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of a publisher, expected to be in the resource group where you deploy + the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": {"description": + "Name of an ACR-backed Artifact Store, deployed under the publisher."}}, "nsDesignGroup": + {"type": "string", "metadata": {"description": "Name of an Network Service Design + Group"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers", "apiVersion": + "2023-09-01", "name": "[parameters(''publisherName'')]", "location": "[parameters(''location'')]", + "properties": {"scope": "Private"}}, {"type": "Microsoft.HybridNetwork/publishers/artifactStores", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''))]", "location": "[parameters(''location'')]", + "properties": {"storeType": "AzureContainerRegistry"}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', + parameters(''publisherName''))]"]}, {"type": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + parameters(''nsDesignGroup''))]", "location": "[parameters(''location'')]", + "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers'', parameters(''publisherName''))]"]}]}, + "parameters": {"location": {"value": "uksouth"}, "publisherName": {"value": + "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"value": "ubuntu-acr"}, + "nsDesignGroup": {"value": "ubuntu"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '1871' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870304", + "name": "AOSM_CLI_deployment_1715870304", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14743947880900953734", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}}, "mode": "Incremental", "provisioningState": "Accepted", "timestamp": + "2024-05-16T14:38:24.9464939Z", "duration": "PT0.0001087S", "correlationId": + "387d39cd-0238-420d-9282-c11230c1ce5c", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkServiceDesignGroups", "locations": ["uksouth"]}]}], "dependencies": + [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu"}]}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870304/operationStatuses/08584857365808428639?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '2404' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 25A2E0053C524C7E88E33E26575F4EAA Ref B: AMS231032609017 Ref C: 2024-05-16T14:38:24Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: F136CC7AC1874261AD8BE518F1E7E128 Ref B: AMS231032609017 Ref C: 2024-05-16T14:38:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:38:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 8F487FDCAB8244BF8DA0FDA43CC5429D Ref B: AMS231032609017 Ref C: 2024-05-16T14:38:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:39:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: C4FD97BE4A244B06AECC62639665551A Ref B: AMS231032609017 Ref C: 2024-05-16T14:39:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:39:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: B8FBA234874B4151B42BD6621B6FE5B4 Ref B: AMS231032609017 Ref C: 2024-05-16T14:39:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:40:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 172C572C028D46A19773C9E6EB0CD392 Ref B: AMS231032609017 Ref C: 2024-05-16T14:40:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:40:54 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: EA6E773779784D6EADC5061EFB25ADD9 Ref B: AMS231032609017 Ref C: 2024-05-16T14:40:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:41:24 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 4EAD4A37432E4E1EA4BEE251C80918D6 Ref B: AMS231032609017 Ref C: 2024-05-16T14:41:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:41:55 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 48E5400E6467445FB480B24BD7E148C0 Ref B: AMS231032609017 Ref C: 2024-05-16T14:41:55Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857365808428639?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 55EF2630DCB74E52942E2D6AD685273E Ref B: AMS231032609017 Ref C: 2024-05-16T14:42:25Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870304", + "name": "AOSM_CLI_deployment_1715870304", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "14743947880900953734", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}}, "mode": "Incremental", "provisioningState": "Succeeded", "timestamp": + "2024-05-16T14:42:13.5015992Z", "duration": "PT3M48.555214S", "correlationId": + "387d39cd-0238-420d-9282-c11230c1ce5c", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers", "locations": ["uksouth"]}, + {"resourceType": "publishers/artifactStores", "locations": ["uksouth"]}, {"resourceType": + "publishers/networkServiceDesignGroups", "locations": ["uksouth"]}]}], "dependencies": + [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr", + "resourceType": "Microsoft.HybridNetwork/publishers/artifactStores", "resourceName": + "automated-cli-test-ubuntu-publisher/ubuntu-acr"}, {"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher", + "resourceType": "Microsoft.HybridNetwork/publishers", "resourceName": "automated-cli-test-ubuntu-publisher"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu"}], "outputResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '3037' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 410B077F691D4640B7201C2E48B6D09B Ref B: AMS231032609017 Ref C: 2024-05-16T14:42:26Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-nsd-manifest-1-0-0?api-version=2023-09-01 + response: + body: + string: '{"error": {"code": "ResourceNotFound", "message": "The Resource ''Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-nsd-manifest-1-0-0'' + under resource group ''cli_test_vnf_nsd_000001'' was not found. For more details + please go to https://aka.ms/ARMResourceNotFoundFix"}}' + headers: + cache-control: + - no-cache + content-length: + - '339' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:25 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-failure-cause: + - gateway + x-msedge-ref: + - 'Ref A: 905B49EB55EF4013809FB5206415EC98 Ref B: AMS231032609051 Ref C: 2024-05-16T14:42:26Z' + status: + code: 404 + message: Not Found +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "4244437476724292391"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the Artifact Manifest to create"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "ubuntu", "artifactType": "OCIArtifact", "artifactVersion": "1.0.0"}]}}]}, "parameters": + {"location": {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "acrManifestName": {"value": + "ubuntu-nsd-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '1407' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870549", + "name": "AOSM_CLI_deployment_1715870549", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4244437476724292391", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "acrManifestName": {"type": "String", "value": + "ubuntu-nsd-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": + "96f87fd7-808f-4f8d-a63a-7c35cac90921", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "validatedResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-nsd-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1245' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:28 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: F1E9EBA4CBB64D75AA4D1604B0FAF986 Ref B: AMS231032609017 Ref C: 2024-05-16T14:42:29Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "4244437476724292391"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "acrManifestName": {"type": "string", "metadata": {"description": + "Name of the Artifact Manifest to create"}}}, "resources": [{"type": "Microsoft.HybridNetwork/publishers/artifactStores/artifactManifests", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''acrArtifactStoreName''), parameters(''acrManifestName''))]", "location": + "[parameters(''location'')]", "properties": {"artifacts": [{"artifactName": + "ubuntu", "artifactType": "OCIArtifact", "artifactVersion": "1.0.0"}]}}]}, "parameters": + {"location": {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "acrManifestName": {"value": + "ubuntu-nsd-manifest-1-0-0"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '1407' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870549", + "name": "AOSM_CLI_deployment_1715870549", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4244437476724292391", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "acrManifestName": {"type": "String", "value": + "ubuntu-nsd-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Accepted", "timestamp": "2024-05-16T14:42:30.3755991Z", "duration": "PT0.0004511S", + "correlationId": "fef1b45b-a0c6-49e6-bf84-6d64851c8e25", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": []}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870549/operationStatuses/08584857363354380823?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '983' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 70A6E3FFF3594858B28A7438C0A36DBA Ref B: AMS231032609017 Ref C: 2024-05-16T14:42:29Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857363354380823?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: EBAA749B599A434CB08DA7949ADA5E74 Ref B: AMS231032609017 Ref C: 2024-05-16T14:42:30Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857363354380823?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:42:59 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: AF39A65FA1D44C40AB9FECC1C799B783 Ref B: AMS231032609017 Ref C: 2024-05-16T14:43:00Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857363354380823?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:43:29 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: B5BEAB2D529B4796A9CD733705B4EEA0 Ref B: AMS231032609017 Ref C: 2024-05-16T14:43:30Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857363354380823?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:43:59 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: F54CF4D82B63453497067CD4D5C6B235 Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:00Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870549", + "name": "AOSM_CLI_deployment_1715870549", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "4244437476724292391", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "acrManifestName": {"type": "String", "value": + "ubuntu-nsd-manifest-1-0-0"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "2024-05-16T14:43:53.9953047Z", "duration": "PT1M23.6201567S", + "correlationId": "fef1b45b-a0c6-49e6-bf84-6d64851c8e25", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/artifactStores/artifactManifests", + "locations": ["uksouth"]}]}], "dependencies": [], "outputResources": [{"id": + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-nsd-manifest-1-0-0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '1261' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:43:59 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: DC6850A4E0384EFA84026B2D9A3666B3 Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:00Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/artifactStores/ubuntu-acr/artifactManifests/ubuntu-nsd-manifest-1-0-0/listCredential?api-version=2023-09-01 + response: + body: + string: '{"username": "ubuntu-nsd-manifest-1-0-0", "acrToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "acrServerUrl": "https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io", + "repositories": ["ubuntu"], "expiry": "2024-05-17T14:44:07.2937587+00:00", + "credentialType": "AzureContainerRegistryScopedToken"}' + headers: + cache-control: + - no-cache + content-length: + - '328' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:07 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-build-version: + - 1.0.02682.2890 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '1198' + x-msedge-ref: + - 'Ref A: 0B5970F3CCC546C88198BE7E93466981 Ref B: AMS231020615053 Ref C: 2024-05-16T14:44:03Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/ + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "ubuntu", "Action": "pull"}, {"Type": "repository", + "Name": "ubuntu", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '270' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:07 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:ubuntu:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aubuntu%3Apull%2Cpush + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:08 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.316667' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - f5479456-12a5-4a1d-99d6-6611bdeac506 + location: + - /v2/ubuntu/blobs/uploads/f5479456-12a5-4a1d-99d6-6611bdeac506?_nouploadcache=false&_state=XBbBXJjN1Gw0zqRjD00MMm7XtEH9U50pmrSUcjIgS7R7Ik5hbWUiOiJ1YnVudHUiLCJVVUlEIjoiZjU0Nzk0NTYtMTJhNS00YTFkLTk5ZDYtNjYxMWJkZWFjNTA2IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA1LTE2VDE0OjQ0OjA4LjA4NjI0MTEyOVoifQ%3D%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: '{"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "13997939921627854066"}}, "parameters": {"configObject": + {"type": "secureObject"}}, "variables": {"resourceGroupId": "[resourceGroup().id]", + "identityObject": "[if(equals(parameters(''configObject'').managedIdentityId, + ''''), createObject(''type'', ''SystemAssigned''), createObject(''type'', ''UserAssigned'', + ''userAssignedIdentities'', createObject(format(''{0}'', parameters(''configObject'').managedIdentityId), + createObject())))]", "nfdvSymbolicName": "[format(''{0}/{1}/{2}'', parameters(''configObject'').publisherName, + parameters(''configObject'').nfdgName, parameters(''configObject'').nfdv)]"}, + "resources": [{"copy": {"name": "nfResource", "count": "[length(parameters(''configObject'').deployParameters)]"}, + "type": "Microsoft.HybridNetwork/networkFunctions", "apiVersion": "2023-09-01", + "name": "[format(''{0}{1}'', parameters(''configObject'').nfdgName, copyIndex())]", + "location": "[parameters(''configObject'').location]", "identity": "[variables(''identityObject'')]", + "properties": {"networkFunctionDefinitionVersionResourceReference": {"id": "[extensionResourceId(format(''/subscriptions/{0}/resourceGroups/{1}'', + subscription().subscriptionId, parameters(''configObject'').publisherResourceGroup), + ''Microsoft.HybridNetwork/publishers/networkFunctionDefinitionGroups/networkFunctionDefinitionVersions'', + split(variables(''nfdvSymbolicName''), ''/'')[0], split(variables(''nfdvSymbolicName''), + ''/'')[1], split(variables(''nfdvSymbolicName''), ''/'')[2])]", "idType": "Open"}, + "nfviType": "AzureCore", "nfviId": "[if(equals(parameters(''configObject'').customLocationId, + ''''), variables(''resourceGroupId''), parameters(''configObject'').customLocationId)]", + "allowSoftwareUpdate": true, "configurationType": "Open", "deploymentValues": + "[string(parameters(''configObject'').deployParameters[copyIndex()])]"}}]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1981' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/f5479456-12a5-4a1d-99d6-6611bdeac506?_nouploadcache=false&_state=XBbBXJjN1Gw0zqRjD00MMm7XtEH9U50pmrSUcjIgS7R7Ik5hbWUiOiJ1YnVudHUiLCJVVUlEIjoiZjU0Nzk0NTYtMTJhNS00YTFkLTk5ZDYtNjYxMWJkZWFjNTA2IiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA1LTE2VDE0OjQ0OjA4LjA4NjI0MTEyOVoifQ%3D%3D&digest=sha256%3A3ccb87556f82c72b4c80c83273d1ac390d77d09ef3504cce53e2c7dd78338bfe + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-content-digest: + - sha256:3ccb87556f82c72b4c80c83273d1ac390d77d09ef3504cce53e2c7dd78338bfe + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/ubuntu/blobs/sha256:3ccb87556f82c72b4c80c83273d1ac390d77d09ef3504cce53e2c7dd78338bfe + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/ + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "ubuntu", "Action": "pull"}, {"Type": "repository", + "Name": "ubuntu", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '270' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:ubuntu:push,pull" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aubuntu%3Apush%2Cpull + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:08 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.3' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: POST + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/ + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-distribution-api-version: + - registry/2.0 + docker-upload-uuid: + - 143df976-8ed0-40ac-8056-dc3eb0e00301 + location: + - /v2/ubuntu/blobs/uploads/143df976-8ed0-40ac-8056-dc3eb0e00301?_nouploadcache=false&_state=2UrGBckKG-WtSzWuyWWhCWHYHwQYHTU2Ex-XrYTToDB7Ik5hbWUiOiJ1YnVudHUiLCJVVUlEIjoiMTQzZGY5NzYtOGVkMC00MGFjLTgwNTYtZGMzZWIwZTAwMzAxIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA1LTE2VDE0OjQ0OjA4LjM3NzM4NTM3M1oifQ%3D%3D + range: + - 0-0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 202 + message: Accepted +- request: + body: '{}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/octet-stream + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/blobs/uploads/143df976-8ed0-40ac-8056-dc3eb0e00301?_nouploadcache=false&_state=2UrGBckKG-WtSzWuyWWhCWHYHwQYHTU2Ex-XrYTToDB7Ik5hbWUiOiJ1YnVudHUiLCJVVUlEIjoiMTQzZGY5NzYtOGVkMC00MGFjLTgwNTYtZGMzZWIwZTAwMzAxIiwiT2Zmc2V0IjowLCJTdGFydGVkQXQiOiIyMDI0LTA1LTE2VDE0OjQ0OjA4LjM3NzM4NTM3M1oifQ%3D%3D&digest=sha256%3A44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-content-digest: + - sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/ubuntu/blobs/sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 1981, + "digest": "sha256:3ccb87556f82c72b4c80c83273d1ac390d77d09ef3504cce53e2c7dd78338bfe", + "annotations": {"org.opencontainers.image.title": "ubuntu-vm.json"}}], "annotations": + {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '498' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/manifests/1.0.0 + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "ubuntu", "Action": "pull"}, {"Type": "repository", + "Name": "ubuntu", "Action": "push"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '270' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token",service="automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io",scope="repository:ubuntu:pull,push" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Service: + - automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io + User-Agent: + - oras-py + method: GET + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/oauth2/token?service=automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io&scope=repository%3Aubuntu%3Apull%2Cpush + response: + body: + string: '{"access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:08 GMT + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-ms-ratelimit-remaining-calls-per-second: + - '333.283333' + status: + code: 200 + message: OK +- request: + body: '{"schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": {"mediaType": "application/vnd.unknown.config.v1+json", "size": 2, + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"}, + "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar", "size": 1981, + "digest": "sha256:3ccb87556f82c72b4c80c83273d1ac390d77d09ef3504cce53e2c7dd78338bfe", + "annotations": {"org.opencontainers.image.title": "ubuntu-vm.json"}}], "annotations": + {}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '498' + Content-Type: + - application/vnd.oci.image.manifest.v1+json + User-Agent: + - python-requests/2.31.0 + method: PUT + uri: https://automatedclitestubuntupublisherubuntuacrb9e0da5f35.azurecr.io/v2/ubuntu/manifests/1.0.0 + response: + body: + string: '' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '0' + date: + - Thu, 16 May 2024 14:44:08 GMT + docker-content-digest: + - sha256:7516e68c6e8b63fcbb428b1ac84e62da7af611846b0a395a546642d45bfe3141 + docker-distribution-api-version: + - registry/2.0 + location: + - /v2/ubuntu/manifests/sha256:7516e68c6e8b63fcbb428b1ac84e62da7af611846b0a395a546642d45bfe3141 + server: + - AzureContainerRegistry + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 201 + message: Created +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "100495212179770157"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "nsDesignGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Service Design Group"}}, "nsDesignVersion": {"type": + "string", "metadata": {"description": "The version of the NSDV you want to create, + in format A.B.C"}}, "nfviSiteName": {"type": "string", "defaultValue": "ubuntu_NFVI", + "metadata": {"description": "Name of the nfvi site"}}}, "variables": {"$fxv#0": + {"$schema": "https://json-schema.org/draft-07/schema#", "title": "ConfigGroupSchema", + "type": "object", "properties": {"ubuntu": {"type": "object", "properties": + {"nfdv": {"type": "string"}, "deployParameters": {"type": "array", "items": + {"type": "object", "properties": {"automated-cli-tests-artifact": {"type": "object", + "properties": {"subnetName": {"type": "string"}, "virtualNetworkId": {"type": + "string"}, "sshPublicKeyAdmin": {"type": "string"}}, "required": ["subnetName", + "virtualNetworkId", "sshPublicKeyAdmin"]}}, "required": ["automated-cli-tests-artifact"]}}, + "managedIdentityId": {"type": "string"}}, "required": ["nfdv", "deployParameters", + "managedIdentityId"]}}, "required": ["ubuntu"]}, "$fxv#1": {"configObject": + {"location": "uksouth", "publisherName": "automated-cli-test-ubuntu-publisher", + "nfdgName": "ubuntu-vm", "publisherResourceGroup": "cli_test_vnf_nsd_000001", + "customLocationId": "", "nfdv": "{configurationparameters(''ConfigGroupSchema'').ubuntu.nfdv}", + "deployParameters": "{configurationparameters(''ConfigGroupSchema'').ubuntu.deployParameters}", + "managedIdentityId": "{configurationparameters(''ConfigGroupSchema'').ubuntu.managedIdentityId}"}}}, + "resources": [{"type": "Microsoft.HybridNetwork/publishers/configurationGroupSchemas", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + ''ConfigGroupSchema'')]", "location": "[parameters(''location'')]", "properties": + {"schemaDefinition": "[string(variables(''$fxv#0''))]"}}, {"type": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nsDesignGroup''), parameters(''nsDesignVersion''))]", "location": + "[parameters(''location'')]", "properties": {"description": "Plain ubuntu VM", + "configurationGroupSchemaReferences": {"ConfigGroupSchema": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/configurationGroupSchemas'', + parameters(''publisherName''), ''ConfigGroupSchema'')]"}}, "nfvisFromSite": + {"nfvi1": {"name": "[format(''{0}1'', parameters(''nfviSiteName''))]", "type": + "AzureCore"}}, "resourceElementTemplates": [{"name": "ubuntu", "type": "NetworkFunctionDefinition", + "configuration": {"artifactProfile": {"artifactStoreReference": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''acrArtifactStoreName''))]"}, "artifactName": + "ubuntu", "artifactVersion": "1.0.0"}, "templateType": "ArmTemplate", "parameterValues": + "[string(variables(''$fxv#1''))]"}, "dependsOnProfile": {"installDependsOn": + [], "uninstallDependsOn": [], "updateDependsOn": []}}]}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers/configurationGroupSchemas'', + parameters(''publisherName''), ''ConfigGroupSchema'')]"]}]}, "parameters": {"location": + {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "nsDesignGroup": {"value": + "ubuntu"}, "nsDesignVersion": {"value": "1.0.0"}, "nfviSiteName": {"value": + "ubuntu_NFVI"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '4144' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: POST + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/validate?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870652", + "name": "AOSM_CLI_deployment_1715870652", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "100495212179770157", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}, "nsDesignVersion": {"type": "String", "value": "1.0.0"}, "nfviSiteName": + {"type": "String", "value": "ubuntu_NFVI"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "0001-01-01T00:00:00Z", "duration": "PT0S", "correlationId": + "4bccc88c-83ce-4158-83b8-3077c6d362c6", "providers": [{"namespace": "Microsoft.HybridNetwork", + "resourceTypes": [{"resourceType": "publishers/configurationGroupSchemas", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/configurationGroupSchemas/ConfigGroupSchema", + "resourceType": "Microsoft.HybridNetwork/publishers/configurationGroupSchemas", + "resourceName": "automated-cli-test-ubuntu-publisher/ConfigGroupSchema"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu/networkServiceDesignVersions/1.0.0", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu/1.0.0"}], "validatedResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/configurationGroupSchemas/ConfigGroupSchema"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu/networkServiceDesignVersions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '2496' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:11 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1198' + x-msedge-ref: + - 'Ref A: EAB22A0F419E47EB86CD7C27091E9C5E Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:11Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"template": {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": {"_generator": {"name": "bicep", "version": + "0.26.54.24096", "templateHash": "100495212179770157"}}, "parameters": {"location": + {"type": "string"}, "publisherName": {"type": "string", "metadata": {"description": + "Name of an existing publisher, expected to be in the resource group where you + deploy the template"}}, "acrArtifactStoreName": {"type": "string", "metadata": + {"description": "Name of an existing ACR-backed Artifact Store, deployed under + the publisher."}}, "nsDesignGroup": {"type": "string", "metadata": {"description": + "Name of an existing Network Service Design Group"}}, "nsDesignVersion": {"type": + "string", "metadata": {"description": "The version of the NSDV you want to create, + in format A.B.C"}}, "nfviSiteName": {"type": "string", "defaultValue": "ubuntu_NFVI", + "metadata": {"description": "Name of the nfvi site"}}}, "variables": {"$fxv#0": + {"$schema": "https://json-schema.org/draft-07/schema#", "title": "ConfigGroupSchema", + "type": "object", "properties": {"ubuntu": {"type": "object", "properties": + {"nfdv": {"type": "string"}, "deployParameters": {"type": "array", "items": + {"type": "object", "properties": {"automated-cli-tests-artifact": {"type": "object", + "properties": {"subnetName": {"type": "string"}, "virtualNetworkId": {"type": + "string"}, "sshPublicKeyAdmin": {"type": "string"}}, "required": ["subnetName", + "virtualNetworkId", "sshPublicKeyAdmin"]}}, "required": ["automated-cli-tests-artifact"]}}, + "managedIdentityId": {"type": "string"}}, "required": ["nfdv", "deployParameters", + "managedIdentityId"]}}, "required": ["ubuntu"]}, "$fxv#1": {"configObject": + {"location": "uksouth", "publisherName": "automated-cli-test-ubuntu-publisher", + "nfdgName": "ubuntu-vm", "publisherResourceGroup": "cli_test_vnf_nsd_000001", + "customLocationId": "", "nfdv": "{configurationparameters(''ConfigGroupSchema'').ubuntu.nfdv}", + "deployParameters": "{configurationparameters(''ConfigGroupSchema'').ubuntu.deployParameters}", + "managedIdentityId": "{configurationparameters(''ConfigGroupSchema'').ubuntu.managedIdentityId}"}}}, + "resources": [{"type": "Microsoft.HybridNetwork/publishers/configurationGroupSchemas", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}'', parameters(''publisherName''), + ''ConfigGroupSchema'')]", "location": "[parameters(''location'')]", "properties": + {"schemaDefinition": "[string(variables(''$fxv#0''))]"}}, {"type": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "apiVersion": "2023-09-01", "name": "[format(''{0}/{1}/{2}'', parameters(''publisherName''), + parameters(''nsDesignGroup''), parameters(''nsDesignVersion''))]", "location": + "[parameters(''location'')]", "properties": {"description": "Plain ubuntu VM", + "configurationGroupSchemaReferences": {"ConfigGroupSchema": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/configurationGroupSchemas'', + parameters(''publisherName''), ''ConfigGroupSchema'')]"}}, "nfvisFromSite": + {"nfvi1": {"name": "[format(''{0}1'', parameters(''nfviSiteName''))]", "type": + "AzureCore"}}, "resourceElementTemplates": [{"name": "ubuntu", "type": "NetworkFunctionDefinition", + "configuration": {"artifactProfile": {"artifactStoreReference": {"id": "[resourceId(''Microsoft.HybridNetwork/publishers/artifactStores'', + parameters(''publisherName''), parameters(''acrArtifactStoreName''))]"}, "artifactName": + "ubuntu", "artifactVersion": "1.0.0"}, "templateType": "ArmTemplate", "parameterValues": + "[string(variables(''$fxv#1''))]"}, "dependsOnProfile": {"installDependsOn": + [], "uninstallDependsOn": [], "updateDependsOn": []}}]}, "dependsOn": ["[resourceId(''Microsoft.HybridNetwork/publishers/configurationGroupSchemas'', + parameters(''publisherName''), ''ConfigGroupSchema'')]"]}]}, "parameters": {"location": + {"value": "uksouth"}, "publisherName": {"value": "automated-cli-test-ubuntu-publisher"}, + "acrArtifactStoreName": {"value": "ubuntu-acr"}, "nsDesignGroup": {"value": + "ubuntu"}, "nsDesignVersion": {"value": "1.0.0"}, "nfviSiteName": {"value": + "ubuntu_NFVI"}}, "mode": "Incremental"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + Content-Length: + - '4144' + Content-Type: + - application/json + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870652", + "name": "AOSM_CLI_deployment_1715870652", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "100495212179770157", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}, "nsDesignVersion": {"type": "String", "value": "1.0.0"}, "nfviSiteName": + {"type": "String", "value": "ubuntu_NFVI"}}, "mode": "Incremental", "provisioningState": + "Accepted", "timestamp": "2024-05-16T14:44:12.7926253Z", "duration": "PT0.0004389S", + "correlationId": "ed6ab600-6261-4775-b398-619df3c052e9", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/configurationGroupSchemas", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/configurationGroupSchemas/ConfigGroupSchema", + "resourceType": "Microsoft.HybridNetwork/publishers/configurationGroupSchemas", + "resourceName": "automated-cli-test-ubuntu-publisher/ConfigGroupSchema"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu/networkServiceDesignVersions/1.0.0", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu/1.0.0"}]}}' + headers: + azure-asyncoperation: + - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870652/operationStatuses/08584857362329913987?api-version=2022-09-01 + cache-control: + - no-cache + content-length: + - '2008' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:11 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-ratelimit-remaining-subscription-writes: + - '1199' + x-msedge-ref: + - 'Ref A: 012E0DFE438F4AF49E4EB8531387BD9D Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:12Z' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857362329913987?api-version=2022-09-01 + response: + body: + string: '{"status": "Accepted"}' + headers: + cache-control: + - no-cache + content-length: + - '22' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:11 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: E8D0F744F4664A8A9F922B0972DF0496 Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:12Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857362329913987?api-version=2022-09-01 + response: + body: + string: '{"status": "Running"}' + headers: + cache-control: + - no-cache + content-length: + - '21' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:44:43 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: A088F44343DD46AD9A9E19E024D259B9 Ref B: AMS231032609017 Ref C: 2024-05-16T14:44:43Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment/operationStatuses/08584857362329913987?api-version=2022-09-01 + response: + body: + string: '{"status": "Succeeded"}' + headers: + cache-control: + - no-cache + content-length: + - '23' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:45:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: EB69FBDAAAD94C57A38B8B5ACCA12EEC Ref B: AMS231032609017 Ref C: 2024-05-16T14:45:13Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - aosm nsd publish + Connection: + - keep-alive + ParameterSetName: + - --build-output-folder + User-Agent: + - AZURECLI/2.57.0.post20240219064455 azsdk-python-core/1.30.0 Python/3.8.10 + (Linux-5.15.146.1-microsoft-standard-WSL2-x86_64-with-glibc2.29) + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/mock-deployment?api-version=2022-09-01 + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.Resources/deployments/AOSM_CLI_deployment_1715870652", + "name": "AOSM_CLI_deployment_1715870652", "type": "Microsoft.Resources/deployments", + "properties": {"templateHash": "100495212179770157", "parameters": {"location": + {"type": "String", "value": "uksouth"}, "publisherName": {"type": "String", + "value": "automated-cli-test-ubuntu-publisher"}, "acrArtifactStoreName": {"type": + "String", "value": "ubuntu-acr"}, "nsDesignGroup": {"type": "String", "value": + "ubuntu"}, "nsDesignVersion": {"type": "String", "value": "1.0.0"}, "nfviSiteName": + {"type": "String", "value": "ubuntu_NFVI"}}, "mode": "Incremental", "provisioningState": + "Succeeded", "timestamp": "2024-05-16T14:45:07.0098296Z", "duration": "PT54.2176432S", + "correlationId": "ed6ab600-6261-4775-b398-619df3c052e9", "providers": [{"namespace": + "Microsoft.HybridNetwork", "resourceTypes": [{"resourceType": "publishers/configurationGroupSchemas", + "locations": ["uksouth"]}, {"resourceType": "publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "locations": ["uksouth"]}]}], "dependencies": [{"dependsOn": [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/configurationGroupSchemas/ConfigGroupSchema", + "resourceType": "Microsoft.HybridNetwork/publishers/configurationGroupSchemas", + "resourceName": "automated-cli-test-ubuntu-publisher/ConfigGroupSchema"}], + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu/networkServiceDesignVersions/1.0.0", + "resourceType": "Microsoft.HybridNetwork/publishers/networkServiceDesignGroups/networkServiceDesignVersions", + "resourceName": "automated-cli-test-ubuntu-publisher/ubuntu/1.0.0"}], "outputResources": + [{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/configurationGroupSchemas/ConfigGroupSchema"}, + {"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_test_vnf_nsd_000001/providers/Microsoft.HybridNetwork/publishers/automated-cli-test-ubuntu-publisher/networkServiceDesignGroups/ubuntu/networkServiceDesignVersions/1.0.0"}]}}' + headers: + cache-control: + - no-cache + content-length: + - '2510' + content-type: + - application/json; charset=utf-8 + date: + - Thu, 16 May 2024 14:45:13 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-msedge-ref: + - 'Ref A: 8C496190DB164CE9B4779F9ADC5B2987 Ref B: AMS231032609017 Ref C: 2024-05-16T14:45:13Z' + status: + code: 200 + message: OK +version: 1 diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_cnf_build_and_publish.py b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_cnf_build_and_publish.py new file mode 100644 index 00000000000..e8c9e7bf806 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_cnf_build_and_publish.py @@ -0,0 +1,141 @@ +# # -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +"""Integration tests for the cnf definition type commands in the aosm extension. +They test the following commands: + aosm nfd build + aosm nfd publish +""" +# We are only testing the nfd and not the nsd commands here. This is because the nsd commands +# are tested in the vnf tests and the nsd code path is the same for both vnf and cnf. + +import os +import logging +import sys +import unittest.mock as mock +from tempfile import TemporaryDirectory + +from azure.cli.testsdk import ScenarioTest, ResourceGroupPreparer +from knack.log import get_logger +from azext_aosm.tests.latest.integration_tests.utils import ( + mock_in_unit_test, + update_input_file, + get_path_to_test_chart, +) +from azext_aosm.tests.latest.integration_tests.scenario_tests.recording_processors import ( + TokenReplacer, + SasUriReplacer, + BlobStoreUriReplacer, + UsernameReplacer, +) + +logger = get_logger(__name__) + +# We use the TEMPLATE file as a jinja2 template to populate some input parameters at runtime. +NFD_INPUT_TEMPLATE_NAME = "cnf_input_template.jsonc" +NFD_INPUT_FILE_NAME = "cnf_input.jsonc" + + +def patch_call_subprocess_raise_output(unit_test): + """Patch the call_subprocess_raise_output function to return a valid mocked output.""" + + # call_subprocess_raise_output uses subprocess.run to call a command and return the output. + # Subprocess is not recorded by the python vcr which means that in the playback we are + # actually trying to run commands like logging into ACRs, which will fail. To avoid this + # we mock the call_subprocess_raise_output function to return a valid mocked output. + def _mock_call_subprocess_raise_output( + *args, **kwargs + ): # pylint: disable=unused-argument + mocked_output = "Valid mocked output" + mocked_call_subprocess_raise_output = mock.Mock(return_value=mocked_output) + return mocked_call_subprocess_raise_output + + # Mock this function in all relevant files + mock_in_unit_test( + unit_test, + "azext_aosm.common.artifact.call_subprocess_raise_output", + _mock_call_subprocess_raise_output, + ) + + mock_in_unit_test( + unit_test, + "azext_aosm.common.registry.call_subprocess_raise_output", + _mock_call_subprocess_raise_output, + ) + + mock_in_unit_test( + unit_test, + "azext_aosm.common.utils.call_subprocess_raise_output", + _mock_call_subprocess_raise_output, + ) + + +class CnfNfdTest(ScenarioTest): + """ + Integration tests for the aosm extension for cnf definition type. + + This test uses Live Scenario Test because it depends on using the `az login` command + which does not work when playing back from the recording. + """ + + def __init__(self, method_name): + """ + This constructor initializes the class + :param method_name: The name of the test method. + :param recording_processors: The recording processors to use for the test. + These recording processors modify the recording of a test before it is saved, + helping to remove sensitive information from the recording. + :param replay_patches: The patches to apply when replaying the test. + These patches modify the test to use mocked functions instead of making live calls. + They will not be applied in live tests. + """ + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + super().__init__( + method_name, + recording_processors=[ + TokenReplacer(), + SasUriReplacer(), + BlobStoreUriReplacer(), + UsernameReplacer(), + ], + replay_patches=[patch_call_subprocess_raise_output], + ) + + @ResourceGroupPreparer(name_prefix="cli_test_cnf_nfd_", location="uksouth") + def test_cnf_nfd_build_and_publish(self, resource_group): + """ + This test builds a cnf nfd output folder and publishes it. + The resource group used is created by the ResourceGroupPreparer decorator + and is deleted at the end of the live test. + + :param resource_group: The name of the resource group to use for the test. + This is passed in by the ResourceGroupPreparer decorator. + """ + starting_directory = os.getcwd() + + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + + try: + chart_path = get_path_to_test_chart() + + nfd_input_file_path = os.path.join(test_dir, NFD_INPUT_FILE_NAME) + + update_input_file( + NFD_INPUT_TEMPLATE_NAME, + nfd_input_file_path, + params={ + "publisher_resource_group_name": resource_group, + "path_to_chart": chart_path, + }, + ) + + self.cmd( + f'az aosm nfd build -f "{nfd_input_file_path}" --definition-type cnf' + ) + + self.cmd("az aosm nfd publish -b cnf-cli-output --definition-type cnf") + finally: + os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_vnf_build_and_publish.py b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_vnf_build_and_publish.py new file mode 100644 index 00000000000..8240014d24a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/scenario_tests/test_aosm_vnf_build_and_publish.py @@ -0,0 +1,125 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +"""Integration tests for the vnf definition type commands in the aosm extension. +They test the following commands: + aosm nfd build + aosm nfd publish + aosm nsd build + aosm nsd publish +""" + +import os +import logging +import sys +import shutil +from tempfile import TemporaryDirectory + +from azure.cli.testsdk import ScenarioTest, ResourceGroupPreparer, live_only +from knack.log import get_logger +from azext_aosm.tests.latest.integration_tests.scenario_tests.recording_processors import ( + TokenReplacer, + SasUriReplacer, + BlobStoreUriReplacer, + UsernameReplacer, +) +from azext_aosm.tests.latest.integration_tests.utils import ( + update_input_file, + get_path_to_vnf_mocks, +) + +logger = get_logger(__name__) + +# We use the TEMPLATE files as a jinja2 templates to populate some input parameters at runtime. +NFD_INPUT_TEMPLATE_NAME = "vnf_input_template.jsonc" +NFD_INPUT_FILE_NAME = "vnf_input.jsonc" +NSD_INPUT_TEMPLATE_NAME = "vnf_nsd_input_template.jsonc" +NSD_INPUT_FILE_NAME = "nsd_input.jsonc" +ARM_TEMPLATE_NAME = "ubuntu_template.json" +VHD_NAME = "ubuntu.vhd" + + +class VnfNsdTest(ScenarioTest): + """This class contains the integration tests for the aosm extension for vnf definition type.""" + + def __init__(self, method_name): + """ + This constructor initializes the class + :param method_name: The name of the test method. + :param recording_processors: The recording processors to use for the test. + These recording processors modify the recording of a test before it is saved, + helping to remove sensitive information from the recording. + """ + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + super().__init__( + method_name, + recording_processors=[ + TokenReplacer(), + SasUriReplacer(), + BlobStoreUriReplacer(), + UsernameReplacer(), + ], + ) + + @ResourceGroupPreparer(name_prefix="cli_test_vnf_nsd_", location="uksouth") + @live_only() # to avoid 'CannotOverwriteExistingCassetteException' when run from recording + def test_vnf_nsd_build_and_publish(self, resource_group): + """ + This test creates a vnf nfd and nsd, publishes them. + The resource group is created by the ResourceGroupPreparer decorator + and is deleted at the end of the live test. + + :param resource_group: The name of the resource group to use for the test. + This is passed in by the ResourceGroupPreparer decorator. + """ + starting_directory = os.getcwd() + + vnf_mocks_dir = get_path_to_vnf_mocks() + + arm_template_path = os.path.join(vnf_mocks_dir, ARM_TEMPLATE_NAME) + + vhd_path = os.path.join(vnf_mocks_dir, VHD_NAME) + + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + + try: + # For ORAS push to work, we need the arm template to be in the + # current working directory. + arm_template_in_temp_dir = os.path.join(test_dir, ARM_TEMPLATE_NAME) + + shutil.copy(arm_template_path, arm_template_in_temp_dir) + + nfd_input_file_path = os.path.join(test_dir, NFD_INPUT_FILE_NAME) + + update_input_file( + NFD_INPUT_TEMPLATE_NAME, + nfd_input_file_path, + params={ + "publisher_resource_group_name": resource_group, + "arm_template_path": arm_template_in_temp_dir, + "vhd_path": vhd_path, + }, + ) + + self.cmd( + f'az aosm nfd build --config-file "{nfd_input_file_path}" --definition-type vnf' + ) + + self.cmd( + "az aosm nfd publish --build-output-folder vnf-cli-output --definition-type vnf" + ) + + nsd_input_file_path = os.path.join(test_dir, NSD_INPUT_FILE_NAME) + update_input_file( + NSD_INPUT_TEMPLATE_NAME, + nsd_input_file_path, + params={"publisher_resource_group_name": resource_group}, + ) + + self.cmd(f'az aosm nsd build -f "{nsd_input_file_path}"') + + self.cmd("az aosm nsd publish --build-output-folder nsd-cli-output") + finally: + os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/test_cnf.py b/src/aosm/azext_aosm/tests/latest/integration_tests/test_cnf.py new file mode 100644 index 00000000000..7460e2dcb02 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/test_cnf.py @@ -0,0 +1,83 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import unittest +import json +import os +from tempfile import TemporaryDirectory + +from azext_aosm.custom import ( + onboard_nfd_generate_config, + onboard_nfd_build, +) + +from azext_aosm.tests.latest.integration_tests.utils import ( + update_input_file, + get_path_to_test_chart, +) + +# We use the TEMPLATE file as a jinja2 template to populate some input parameters at runtime. +INPUT_TEMPLATE_NAME = "cnf_input_template.jsonc" +INPUT_FILE_NAME = "cnf_input.jsonc" + + +class TestCNF(unittest.TestCase): + def test_generate_config(self): + """Test generating a config file for a VNF.""" + starting_directory = os.getcwd() + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + output_file_path = os.path.join(test_dir, "cnf_input.jsonc") + + try: + onboard_nfd_generate_config( + definition_type="cnf", + output_file=os.path.join(test_dir, output_file_path), + ) + assert os.path.exists(output_file_path) + finally: + os.chdir(starting_directory) + + def test_build(self): + """Test the build command for CNFs.""" + starting_directory = os.getcwd() + + chart_path = get_path_to_test_chart() + + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + + try: + cnf_input_file_path = os.path.join(test_dir, INPUT_FILE_NAME) + + update_input_file( + INPUT_TEMPLATE_NAME, + cnf_input_file_path, + params={ + "publisher_resource_group_name": "cli_test_cnf_nfd", + "path_to_chart": chart_path, + }, + ) + + onboard_nfd_build("cnf", cnf_input_file_path) + assert os.path.exists("cnf-cli-output") + + assert os.path.exists("cnf-cli-output/all_deploy.parameters.json") + + expected_deploy_params = { + "location": "uksouth", + "publisherName": "automated-cli-test-nginx-publisher", + "publisherResourceGroupName": "cli_test_cnf_nfd", + "acrArtifactStoreName": "nginx-acr", + "acrManifestName": "nginx-acr-manifest-1-0-0", + "nfDefinitionGroup": "nginx", + "nfDefinitionVersion": "1.0.0", + } + with open( + "cnf-cli-output/all_deploy.parameters.json" + ) as allDeployParametersFile: + params = json.load(allDeployParametersFile) + assert params == expected_deploy_params + finally: + os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/test_nsd.py b/src/aosm/azext_aosm/tests/latest/integration_tests/test_nsd.py similarity index 54% rename from src/aosm/azext_aosm/tests/latest/test_nsd.py rename to src/aosm/azext_aosm/tests/latest/integration_tests/test_nsd.py index 4c689d4ab89..8094546e62a 100644 --- a/src/aosm/azext_aosm/tests/latest/test_nsd.py +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/test_nsd.py @@ -7,50 +7,61 @@ import os import shutil import subprocess +import logging +import sys from dataclasses import dataclass from distutils.dir_util import copy_tree from filecmp import dircmp from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any -from unittest.mock import Mock, patch +from typing import Any, List +from unittest.mock import patch +from azext_aosm.vendored_sdks.models import ( + VirtualNetworkFunctionDefinitionVersion, +) +from unittest import TestCase import jsonschema -import pytest -from azure.cli.core.azclierror import CLIInternalError from azure.core import exceptions as azure_exceptions from azure.mgmt.resource.features.v2015_12_01.models import ( FeatureProperties, FeatureResult, ) - +from azext_aosm.common.constants import CNF_TYPE, VNF_TYPE from azext_aosm.custom import ( - _check_features_enabled, - build_design, - generate_design_config, + onboard_nsd_build, + onboard_nsd_generate_config, ) -mock_nsd_folder = ((Path(__file__).parent) / "mock_nsd").resolve() +from azext_aosm.vendored_sdks import HybridNetworkManagementClient + +mock_input_templates_folder = ( + (Path(__file__)).parent / "integration_test_mocks/mock_input_templates" +).resolve() output_folder = ((Path(__file__).parent) / "nsd_output").resolve() +NSD_INPUT_FILE_NAME = "nsd_core_input.jsonc" CGV_DATA = { - "ubuntu-vm-nfdg": { - "deploymentParameters": { - "location": "eastus", - "subnetName": "subnet", - "virtualNetworkId": "bar", - "sshPublicKeyAdmin": "foo", - }, - "ubuntu_vm_nfdg_nfd_version": "1.0.0", + "ubuntu": { + "nfdv": "1.0.0", + "deployParameters": [ + { + "location": "eastus", + "subnetName": "subnet", + "virtualNetworkId": "bar", + "sshPublicKeyAdmin": "foo", + } + ], + "managedIdentityId": "blah", }, - "managedIdentity": "blah", } MULTIPLE_INSTANCES_CGV_DATA = { - "ubuntu-vm-nfdg": { - "deploymentParameters": [ + "ubuntu": { + "nfdv": "1.0.0", + "deployParameters": [ { "location": "eastus", "subnetName": "subnet", @@ -64,22 +75,23 @@ "sshPublicKeyAdmin": "foo2", }, ], - "ubuntu_vm_nfdg_nfd_version": "1.0.0", + "managedIdentityId": "blah", }, - "managedIdentity": "blah", } MULTIPLE_NFs_CGV_DATA = { - "managedIdentity": "managed_identity", - "nginx-nfdg": { - "customLocationId": "custom_location", - "nginx_nfdg_nfd_version": "1.0.0", - "deploymentParameters": {"service_port": 5222, "serviceAccount_create": False}, + "multi-nf": { + "nfdv": "1.0.0", + "managedIdentityId": "exampleManagedIdentityId", + "deployParameters": [ + {"service_port": 5222, "serviceAccount_create": False} + ], }, - "ubuntu-nfdg": { - "ubuntu_nfdg_nfd_version": "1.0.0", - "deploymentParameters": { + "ubuntu": { + "nfdv": "1.0.0", + "managedIdentity": "managed_identity", + "deployParameters": { "location": "eastus", "subnetName": "ubuntu-vm-subnet", "ubuntuVmName": "ubuntu-vm", @@ -102,6 +114,7 @@ }, } + nginx_deploy_parameters = { "$schema": "https://json-schema.org/draft-07/schema#", "title": "DeployParametersSchema", @@ -116,25 +129,60 @@ # We don't want to get details from a real NFD (calling out to Azure) in a UT. # Therefore we pass in a fake client to supply the deployment parameters from the "NFD". + + +@dataclass +class NetworkFunctionApplications: + name: str + depends_on_profile: list + artifact_type: str @dataclass -class NFDVProperties: +class NetworkFunctionTemplate: + nfvi_type: str + network_function_applications: List[NetworkFunctionApplications] + + +@dataclass +class NFDVProperties(VirtualNetworkFunctionDefinitionVersion): deploy_parameters: str + network_function_template: NetworkFunctionTemplate # type: ignore + network_function_type: str + @dataclass class NFDV: properties: NFDVProperties + id: str + class NFDVs: def get(self, network_function_definition_group_name, **_): + networkFunctionApplication = NetworkFunctionApplications(name="test", depends_on_profile=[],artifact_type="") + networkFunctionTemplate = NetworkFunctionTemplate(nfvi_type="AzureCore", + network_function_applications=[networkFunctionApplication] ) if "nginx" in network_function_definition_group_name: - return NFDV(NFDVProperties(json.dumps(nginx_deploy_parameters))) + return NFDV( + properties=NFDVProperties( + deploy_parameters=json.dumps(nginx_deploy_parameters), + network_function_template=networkFunctionTemplate, + network_function_type=CNF_TYPE + ), + id="/subscriptions/00000/resourceGroups/rg/providers/Microsoft.HybridNetwork/publishers/pub/networkFunctionDefinitionGroups/nginx/networkFunctionDefinitionVersions/1.0.0", + ) else: - return NFDV(NFDVProperties(json.dumps(ubuntu_deploy_parameters))) + return NFDV( + properties=NFDVProperties( + deploy_parameters=json.dumps(ubuntu_deploy_parameters), + network_function_template=networkFunctionTemplate, + network_function_type=VNF_TYPE + ), + id="/subscriptions/00000/resourceGroups/rg/providers/Microsoft.HybridNetwork/publishers/pub/networkFunctionDefinitionGroups/ubuntu/networkFunctionDefinitionVersions/1.0.0", + ) -class AOSMClient: +class AOSMClient(HybridNetworkManagementClient): def __init__(self) -> None: - self.network_function_definition_versions = NFDVs() + self.network_function_definition_versions = NFDVs() # type: ignore mock_client = AOSMClient() @@ -240,14 +288,14 @@ def build_bicep(bicep_template_path): def compare_to_expected_output(expected_folder_name: str): """ - Compares nsd-bicep-templates to the supplied folder name. + Compares nsd-cli-output to the supplied folder name. :param expected_folder_name: The name of the folder within nsd_output to compare with. """ # Check files and folders within the top level directory are the same. expected_output_path = output_folder / expected_folder_name - comparison = dircmp("nsd-bicep-templates", expected_output_path) + comparison = dircmp("nsd-cli-output", expected_output_path) try: assert len(comparison.diff_files) == 0 @@ -260,7 +308,7 @@ def compare_to_expected_output(expected_folder_name: str): assert len(subdir.left_only) == 0 assert len(subdir.right_only) == 0 except: - copy_tree("nsd-bicep-templates", str(expected_output_path)) + copy_tree("nsd-cli-output", str(expected_output_path)) print( f"Output has changed in {expected_output_path}, use git diff to check if " f"you are happy with those changes." @@ -268,126 +316,117 @@ def compare_to_expected_output(expected_folder_name: str): raise -class TestNSDGenerator: +class TestNSDGenerator(TestCase): + def setUp(self): + # Prints out info logs in console if test fails + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + def test_generate_config(self): """Test generating a config file for a VNF.""" starting_directory = os.getcwd() with TemporaryDirectory() as test_dir: os.chdir(test_dir) + output_file_path = os.path.join(test_dir, "nsd-input.jsonc") + try: - generate_design_config() - assert os.path.exists("input.json") + onboard_nsd_generate_config(output_file_path) + assert os.path.exists(output_file_path) finally: os.chdir(starting_directory) - @patch("azext_aosm.custom.cf_resources") - def test_build(self, cf_resources): + @patch( + "azext_aosm.common.command_context.get_mgmt_service_client", + return_value=mock_client, + ) + def test_build(self, mock_get_mgmt_service_client): """Test building the NSD bicep templates.""" starting_directory = os.getcwd() with TemporaryDirectory() as test_dir: os.chdir(test_dir) + nsd_input_file_path = os.path.join( + mock_input_templates_folder, NSD_INPUT_FILE_NAME + ) + try: - build_design( - mock_cmd, - client=mock_client, - config_file=str(mock_nsd_folder / "input.json"), + onboard_nsd_build( + cmd=mock_cmd, + config_file=nsd_input_file_path, ) - assert os.path.exists("nsd-bicep-templates") + assert os.path.exists("nsd-cli-output") + validate_json_against_schema( CGV_DATA, - "nsd-bicep-templates/schemas/ubuntu_ConfigGroupSchema.json", + "nsd-cli-output/nsdDefinition/config-group-schema.json", ) compare_to_expected_output("test_build") finally: os.chdir(starting_directory) - @patch("azext_aosm.custom.cf_resources") - def test_build_multiple_instances(self, cf_resources): + @patch( + "azext_aosm.common.command_context.get_mgmt_service_client", + return_value=mock_client, + ) + def test_build_multiple_instances(self, mock_get_mgmt_service_client): + # Parameter is not used but we need to include it for the patch to work + # TODO: this test passes but it doesn't actually test anything because + # This functionality is currently broken """Test building the NSD bicep templates with multiple NFs allowed.""" starting_directory = os.getcwd() with TemporaryDirectory() as test_dir: os.chdir(test_dir) try: - build_design( - mock_cmd, - client=mock_client, - config_file=str(mock_nsd_folder / "input_multiple_instances.json"), + onboard_nsd_build( + cmd=mock_cmd, + config_file=str( + mock_input_templates_folder / "input_multiple_instances.jsonc" + ), ) - assert os.path.exists("nsd-bicep-templates") + assert os.path.exists("nsd-cli-output") validate_json_against_schema( MULTIPLE_INSTANCES_CGV_DATA, - "nsd-bicep-templates/schemas/ubuntu_ConfigGroupSchema.json", + "nsd-cli-output/nsdDefinition/config-group-schema.json", ) compare_to_expected_output("test_build_multiple_instances") finally: os.chdir(starting_directory) - @patch("azext_aosm.custom.cf_resources") - def test_build_multiple_nfs(self, cf_resources): - """Test building the NSD bicep templates with multiple NFs allowed.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_design( - mock_cmd, - client=mock_client, - config_file=str(mock_nsd_folder / "input_multi_nf_nsd.json"), - ) - - assert os.path.exists("nsd-bicep-templates") - validate_json_against_schema( - MULTIPLE_NFs_CGV_DATA, - "nsd-bicep-templates/schemas/multinf_ConfigGroupSchema.json", - ) - - # The bicep checks take a while, so we would only do them here and not - # on the other tests. However, they are disabled until we can look at - # them further, as the version of Bicep used ends up in the built file, - # and we don't control what version of bicep is used in the pipeline or - # on the user's machine. - # build_bicep("nsd-bicep-templates/nginx-nfdg_nf.bicep") - # build_bicep("nsd-bicep-templates/ubuntu-nfdg_nf.bicep") - # build_bicep("nsd-bicep-templates/nsd_definition.bicep") - # build_bicep("nsd-bicep-templates/artifact_manifest.bicep") - - compare_to_expected_output("test_build_multiple_nfs") - finally: - os.chdir(starting_directory) - - def test_check_features(self, caplog): - """ - Test the _check_features_enabled function. - - Does not test the actual feature check, just that the function logs and raises - exceptions appropriately. - """ - mock_features_client = FeaturesClient() - mock_missing_features_client = MissingFeaturesClient() - caplog.set_level("DEBUG") - with patch("azext_aosm.custom.cf_features", return_value=mock_features_client): - mock_features_client.features.mock_state = "NotRegistered" - - with pytest.raises(CLIInternalError): - _check_features_enabled(mock_cmd) - assert "is not registered on the subscription" in caplog.text - mock_features_client.features.mock_state = "Registered" - _check_features_enabled(mock_cmd) - - with patch( - "azext_aosm.custom.cf_features", return_value=mock_missing_features_client - ): - with pytest.raises(CLIInternalError): - _check_features_enabled(mock_cmd) - assert ( - "CLI encountered an error checking that your " - "subscription has been onboarded to AOSM." in caplog.text - ) + # TODO: creating NSDs with multiple NFs is broken at the moment. + # Fix this test after the bug there is fixed. + # @patch( + # "azext_aosm.common.command_context.get_mgmt_service_client", + # return_value=mock_client, + # ) + # def test_build_multiple_nfs(self, mock_get_mgmt_service_client): + # """Test building the NSD bicep templates with multiple NFs allowed.""" + # starting_directory = os.getcwd() + # with TemporaryDirectory() as test_dir: + # os.chdir(test_dir) + + # try: + # onboard_nsd_build( + # cmd=mock_cmd, + # config_file=str(mock_input_templates_folder / "input_multi_nf_nsd.jsonc"), + # ) + + # assert os.path.exists("nsd-cli-output") + + # # The bicep checks take a while, so we would only do them here and not + # # on the other tests. However, they are disabled until we can look at + # # them further, as the version of Bicep used ends up in the built file, + # # and we don't control what version of bicep is used in the pipeline or + # # on the user's machine. + # # build_bicep("nsd-bicep-templates/nginx-nfdg_nf.bicep") + # # build_bicep("nsd-bicep-templates/ubuntu-nfdg_nf.bicep") + # # build_bicep("nsd-bicep-templates/nsd_definition.bicep") + # # build_bicep("nsd-bicep-templates/artifact_manifest.bicep") + + # compare_to_expected_output("test_build_multiple_nfs") + # finally: + # os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/test_vnf.py b/src/aosm/azext_aosm/tests/latest/integration_tests/test_vnf.py new file mode 100644 index 00000000000..1972affc4f3 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/test_vnf.py @@ -0,0 +1,123 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import os +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory + +from azext_aosm.custom import onboard_nfd_build, onboard_nfd_generate_config +from azext_aosm.tests.latest.integration_tests.utils import update_input_file +from azext_aosm.common.constants import VNF_OUTPUT_FOLDER_FILENAME + +mock_vnf_folder = ((Path(__file__).parent.parent) / "mock_vnf").resolve() + +INPUT_WITH_SAS_VHD_PARAMS = { + "imageDiskSizeGB": 30, + "imageHyperVGeneration": "V1", + "apiVersion": "2023-03-01", + "imageName": "ubuntu-vmImage", +} + +# We use the TEMPLATE files as a jinja2 templates to populate some input parameters at runtime. +NFD_INPUT_TEMPLATE_NAME = "vnf_input_template.jsonc" +NFD_INPUT_FILE_NAME = "vnf_input.jsonc" +VNF_INPUT_WITH_SAS_TOKEN_TEMPLATE_NAME = "vnf_input_with_sas_token_template.jsonc" +ARM_TEMPLATE_NAME = "ubuntu_template.json" +VHD_NAME = "ubuntu.vhd" + + +def get_path_to_vnf_mocks(): + """Get the path to the vnf mocks directory.""" + code_dir = os.path.dirname(__file__) + vnf_mocks_dir = os.path.join(code_dir, "integration_test_mocks", "vnf_mocks") + + return vnf_mocks_dir + + +def validate_vhd_parameters(expected_params, vhd_params_file_path): + """Validate that the expected parameters are in the actual parameters.""" + assert os.path.exists(vhd_params_file_path) + with open(vhd_params_file_path) as f: + actual_params = json.load(f) + assert expected_params == actual_params + + +class TestVNF(unittest.TestCase): + def test_generate_config(self): + """Test generating a config file for a VNF.""" + starting_directory = os.getcwd() + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + output_file_path = os.path.join(test_dir, "vnf_input.jsonc") + + try: + onboard_nfd_generate_config( + definition_type="vnf", + output_file=os.path.join(test_dir, output_file_path), + ) + assert os.path.exists(output_file_path) + finally: + os.chdir(starting_directory) + + def test_build_with_filepath(self): + """Test building an NFDV for a VNF using a filepath.""" + starting_directory = os.getcwd() + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + + try: + vnf_mocks_dir = get_path_to_vnf_mocks() + + arm_template_path = os.path.join(vnf_mocks_dir, ARM_TEMPLATE_NAME) + vhd_path = os.path.join(vnf_mocks_dir, VHD_NAME) + + nfd_input_file_path = os.path.join(test_dir, NFD_INPUT_FILE_NAME) + + update_input_file( + NFD_INPUT_TEMPLATE_NAME, + nfd_input_file_path, + params={ + "publisher_resource_group_name": "resource_group", + "arm_template_path": arm_template_path, + "vhd_path": vhd_path, + }, + ) + + onboard_nfd_build("vnf", nfd_input_file_path) + assert os.path.exists(VNF_OUTPUT_FOLDER_FILENAME) + finally: + os.chdir(starting_directory) + + def test_build_with_sas_token(self): + """Test building an NFDV for a VNF using a filepath.""" + starting_directory = os.getcwd() + with TemporaryDirectory() as test_dir: + os.chdir(test_dir) + + try: + vnf_mocks_dir = get_path_to_vnf_mocks() + + arm_template_path = os.path.join(vnf_mocks_dir, ARM_TEMPLATE_NAME) + + nfd_input_file_path = os.path.join(test_dir, NFD_INPUT_FILE_NAME) + update_input_file( + VNF_INPUT_WITH_SAS_TOKEN_TEMPLATE_NAME, + nfd_input_file_path, + params={ + "publisher_resource_group_name": "resource_group", + "arm_template_path": arm_template_path, + }, + ) + + onboard_nfd_build("vnf", nfd_input_file_path) + assert os.path.exists(VNF_OUTPUT_FOLDER_FILENAME) + validate_vhd_parameters( + INPUT_WITH_SAS_VHD_PARAMS, + f"{VNF_OUTPUT_FOLDER_FILENAME}/nfDefinition/vhdParameters.json", + ) + finally: + os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/integration_tests/utils.py b/src/aosm/azext_aosm/tests/latest/integration_tests/utils.py new file mode 100644 index 00000000000..bdd20aaddf2 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/integration_tests/utils.py @@ -0,0 +1,59 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +""" This module provides utility functions for integration tests. """ +import os +import unittest +from typing import Dict +from jinja2 import Template + +from azure.cli.core.azclierror import CLIInternalError + +CHART_NAME = "nginxdemo-0.1.0.tgz" + + +def mock_in_unit_test(unit_test, target, replacement): + """Mock a function in a unit test.""" + + if not isinstance(unit_test, unittest.TestCase): + raise CLIInternalError("Patches can be only called from a unit test") + + patcher = unittest.mock.patch(target, replacement) + patcher.__enter__() + unit_test.addCleanup(patcher.__exit__, None, None, None) + + +def update_input_file(input_template_name, output_file_path, params: Dict[str, str]): + """Update the input file with the given parameters and return the path to the updated file.""" + code_dir = os.path.dirname(__file__) + templates_dir = os.path.join( + code_dir, "integration_test_mocks", "mock_input_templates" + ) + input_template_path = os.path.join(templates_dir, input_template_name) + + with open(input_template_path, "r", encoding="utf-8") as file: + contents = file.read() + + jinja_template = Template(contents) + + rendered_template = jinja_template.render(**params) + + with open(output_file_path, "w", encoding="utf-8") as file: + file.write(rendered_template) + + +def get_path_to_test_chart(): + """Get the path to the chart used in the tests.""" + code_dir = os.path.dirname(__file__) + templates_dir = os.path.join(code_dir, "integration_test_mocks", "cnf_mocks") + chart_path = os.path.join(templates_dir, CHART_NAME) + return chart_path + + +def get_path_to_vnf_mocks(): + """Get the path to the vnf mocks directory.""" + code_dir = os.path.dirname(__file__) + vnf_mocks_dir = os.path.join(code_dir, "integration_test_mocks", "vnf_mocks") + + return vnf_mocks_dir diff --git a/src/aosm/azext_aosm/tests/latest/mock_arm_templates/no-params-template.json b/src/aosm/azext_aosm/tests/latest/mock_arm_templates/no-params-template.json new file mode 100644 index 00000000000..c61fdffc7af --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_arm_templates/no-params-template.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.15.31.15270", + "templateHash": "1656082395923655778" + } + }, + "variables": { + "imageResourceGroup": "[resourceGroup().name]", + "subscriptionId": "[subscription().subscriptionId]", + "vmSizeSku": "Standard_D2s_v3" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2021-05-01", + "name": "[format('{0}_nic', parameters('ubuntuVmName'))]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[format('{0}/subnets/{1}', parameters('virtualNetworkId'), parameters('subnetName'))]" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2021-07-01", + "name": "[parameters('ubuntuVmName')]", + "location": "[parameters('location')]", + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSizeSku')]" + }, + "storageProfile": { + "imageReference": { + "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('subscriptionId'), variables('imageResourceGroup')), 'Microsoft.Compute/images', parameters('imageName'))]" + }, + "osDisk": { + "osType": "Linux", + "name": "[format('{0}_disk', parameters('ubuntuVmName'))]", + "createOption": "FromImage", + "caching": "ReadWrite", + "writeAcceleratorEnabled": false, + "managedDisk": "[json('{\"storageAccountType\": \"Premium_LRS\"}')]", + "deleteOption": "Delete", + "diskSizeGB": 30 + } + }, + "osProfile": { + "computerName": "[parameters('ubuntuVmName')]", + "adminUsername": "azureuser", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "/home/azureuser/.ssh/authorized_keys", + "keyData": "[parameters('sshPublicKeyAdmin')]" + } + ] + }, + "provisionVMAgent": true, + "patchSettings": { + "patchMode": "ImageDefault", + "assessmentMode": "ImageDefault" + } + }, + "secrets": [], + "allowExtensionOperations": true + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + ] + } + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_arm_templates/simple-template.json b/src/aosm/azext_aosm/tests/latest/mock_arm_templates/simple-template.json new file mode 100644 index 00000000000..28e37a1909c --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_arm_templates/simple-template.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.15.31.15270", + "templateHash": "1656082395923655778" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "uksouth" + } + }, + "variables": { + "imageResourceGroup": "[resourceGroup().name]", + "subscriptionId": "[subscription().subscriptionId]", + "vmSizeSku": "Standard_D2s_v3" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2021-05-01", + "name": "[format('{0}_nic', parameters('ubuntuVmName'))]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[format('{0}/subnets/{1}', parameters('virtualNetworkId'), parameters('subnetName'))]" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2021-07-01", + "name": "[parameters('ubuntuVmName')]", + "location": "[parameters('location')]", + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSizeSku')]" + }, + "storageProfile": { + "imageReference": { + "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('subscriptionId'), variables('imageResourceGroup')), 'Microsoft.Compute/images', parameters('imageName'))]" + }, + "osDisk": { + "osType": "Linux", + "name": "[format('{0}_disk', parameters('ubuntuVmName'))]", + "createOption": "FromImage", + "caching": "ReadWrite", + "writeAcceleratorEnabled": false, + "managedDisk": "[json('{\"storageAccountType\": \"Premium_LRS\"}')]", + "deleteOption": "Delete", + "diskSizeGB": 30 + } + }, + "osProfile": { + "computerName": "[parameters('ubuntuVmName')]", + "adminUsername": "azureuser", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "/home/azureuser/.ssh/authorized_keys", + "keyData": "[parameters('sshPublicKeyAdmin')]" + } + ] + }, + "provisionVMAgent": true, + "patchSettings": { + "patchMode": "ImageDefault", + "assessmentMode": "ImageDefault" + } + }, + "secrets": [], + "allowExtensionOperations": true + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + ] + } + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_vnf/ubuntu-template.json b/src/aosm/azext_aosm/tests/latest/mock_arm_templates/ubuntu-template.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/mock_vnf/ubuntu-template.json rename to src/aosm/azext_aosm/tests/latest/mock_arm_templates/ubuntu-template.json diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/.helmignore b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/.helmignore new file mode 100644 index 00000000000..0e8a0eb36f4 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/Chart.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/Chart.yaml new file mode 100644 index 00000000000..275ab10e2e2 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: nf-agent-cnf +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/NOTES.txt b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/NOTES.txt new file mode 100644 index 00000000000..238eb9aa2f6 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nf-agent-cnf.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nf-agent-cnf.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nf-agent-cnf.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nf-agent-cnf.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/_helpers.tpl b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/_helpers.tpl new file mode 100644 index 00000000000..1a8d7653757 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "nf-agent-cnf.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nf-agent-cnf.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nf-agent-cnf.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nf-agent-cnf.labels" -}} +helm.sh/chart: {{ include "nf-agent-cnf.chart" . }} +{{ include "nf-agent-cnf.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nf-agent-cnf.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nf-agent-cnf.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "nf-agent-cnf.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "nf-agent-cnf.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/deployment.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/deployment.yaml new file mode 100644 index 00000000000..90bae35f548 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nf-agent-cnf.fullname" . }} + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "nf-agent-cnf.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nf-agent-cnf.selectorLabels" . | nindent 8 }} + #aadpodidbinding: {{ .Values.nfagent.podIdentity }} - not using podidentity any more + spec: + # Copied imagePullSecrets from how afosas-aosm repo does it + imagePullSecrets: {{ mustToPrettyJson (ternary (list ) .Values.imagePullSecrets (kindIs "invalid" .Values.imagePullSecrets)) }} + serviceAccountName: {{ include "nf-agent-cnf.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + # Edited the image to point to the nf-agent image in the Artifact Store ACR + image: "{{ .Values.image.repository }}/pez-nfagent:879624" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + # Commented out otherwise kubernetes keeps restarting the pod thinking the probes have failed + # livenessProbe: + # httpGet: + # path: / + # port: http + # readinessProbe: + # httpGet: + # path: / + # port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + # Gets the NF Agent config from the configMap - see nf-agent-config-map.yaml + volumeMounts: + - name: nfagent-config-volume + mountPath: /etc/nf-agent/config.yaml + subPath: config.yaml + volumes: + - name: nfagent-config-volume + configMap: + name: nfagent-config + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/hpa.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/hpa.yaml new file mode 100644 index 00000000000..ae24cfc1704 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "nf-agent-cnf.fullname" . }} + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "nf-agent-cnf.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/ingress.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/ingress.yaml new file mode 100644 index 00000000000..9fc4d315aed --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "nf-agent-cnf.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/nf-agent-config-map.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/nf-agent-config-map.yaml new file mode 100644 index 00000000000..8c2f5e6f823 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/nf-agent-config-map.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nfagent-config +data: + config.yaml: | + # Example NF Agent config file. + # Config is read from /etc/nf-agent/config.yaml. + log_level: debug + service_bus: + # Using a namespace and Managed Identity (specified by client ID) for auth. + namespace: {{ .Values.nfagent.namespace }} + # Helm uses sprig for templating, so we can use sprig functions to find just the UID from the full Managed Identity ID path. + identity: {{ (splitList "/" .Values.nfagent.identity) | last }} + # Alternatively can use a connstring instead of namespace + managed identity: + # connstring: "Endpoint=sb://contoso.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=TopSecretSASTokenGoesHere=" + subscriptions: + - topic: {{ .Values.nfagent.topic }} + subscription: {{ .Values.nfagent.topic }}-subscription + # Handler-specific config + handler_config: + simpl: + # The endpoint is constructed from the namespace and service name of the receiving thing. + # We couldn't get AOSM to install the service to listen on anything but port 80 + # Doh - that was because we changed values.yaml in the chart but didn't change values.mappings.yaml in the NFDV + # Changing values.mappings.yaml should make this work on port 5222 as expected. + endpoint: http://nfconfigchart.nfconfigchart.svc.cluster.local:80 # DevSkim: ignore DS162092 diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/service.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/service.yaml new file mode 100644 index 00000000000..ed537a4e61c --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nf-agent-cnf.fullname" . }} + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "nf-agent-cnf.selectorLabels" . | nindent 4 }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/serviceaccount.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/serviceaccount.yaml new file mode 100644 index 00000000000..e19c3c09fbc --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nf-agent-cnf.serviceAccountName" . }} + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/tests/test-connection.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/tests/test-connection.yaml new file mode 100644 index 00000000000..309ea5078a2 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "nf-agent-cnf.fullname" . }}-test-connection" + labels: + {{- include "nf-agent-cnf.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "nf-agent-cnf.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.mappings.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.mappings.yaml new file mode 100644 index 00000000000..eef4e074f8b --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.mappings.yaml @@ -0,0 +1,89 @@ +# Default values for nf-agent-cnf. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: sunnyclipubsunnynfagentacre00abc1832.azurecr.io + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "879624" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +nfagent: + namespace: "{deployParameters.nfAgentServiceBusNamespace}" + identity: "{deployParameters.nfAgentUserAssignedIdentityResourceId}" + topic: "{deployParameters.nfagent_topic}" # ??? This wasn't made available to simpl + # name of pod identity - not using this any more + # podIdentity: mypeapod diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.schema.json b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.schema.json new file mode 100644 index 00000000000..5207b19a8df --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/helm-charts/nf-agent-cnf-invalid/values.schema.json @@ -0,0 +1,193 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "additionalProperties": true, + "properties": { + "affinity": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "autoscaling": { + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "maxReplicas": { + "type": "integer" + }, + "minReplicas": { + "type": "integer" + }, + "targetCPUUtilizationPercentage": { + "type": "integer" + } + }, + "type": "object" + }, + "fullnameOverride": { + "type": "string" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "imagePullSecrets": { + "items": { + "anyOf": [] + }, + "type": "array" + }, + "ingress": { + "additionalProperties": false, + "properties": { + "annotations": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + } + ] + }, + "type": "array" + } + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "tls": { + "items": { + "anyOf": [] + }, + "type": "array" + } + }, + "type": "object" + }, + "nameOverride": { + "type": "string" + }, + "nfagent": { + "additionalProperties": false, + "properties": { + "identity": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "topic": { + "type": "string" + } + }, + "type": "object" + }, + "nodeSelector": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "podAnnotations": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "podSecurityContext": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "securityContext": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "service": { + "additionalProperties": false, + "properties": { + "port": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "tolerations": { + "items": { + "anyOf": [] + }, + "type": "array" + } + }, + "type": "object" +} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf-template.jsonc b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf-template.jsonc new file mode 100644 index 00000000000..712bacbb322 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf-template.jsonc @@ -0,0 +1,18 @@ +{ + "location": "uksouth", + "publisher_name": "sunnyclipub", + "publisher_resource_group_name": "sunny-uksouth", + "nf_name": "nf-agent-cnf", + "version": "0.1.0", + "acr_artifact_store_name": "sunny-nfagent-acr-2", + "images": { + "source_registry": "--this was copied here and renamed from https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/4a0479c0-b795-4d0f-96fd-c7edd2a2928f/resourceGroups/pez-nfagent-pipelines/providers/Microsoft.ContainerRegistry/registries/peznfagenttemp/overview new one was /subscriptions/c7bd9d96-70dd-4f61-af56-6e0abd8d80b5/resourceGroups/sunny-nfagent-acr-HostedResources-4CDE264A/providers/Microsoft.ContainerRegistry/registries/SunnyclipubSunnyNfagentAcre00abc1832" + }, + "helm_packages": [ + { + "name": "nf-agent-cnf", + "path_to_chart": "{{tests_directory}}/latest/mock_cnf/helm-charts/nf-agent-cnf", + "default_values": "" + } + ] +} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf.json b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf.json deleted file mode 100644 index 33d35093cfc..00000000000 --- a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nf-agent-cnf.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "publisher_name": "sunnyclipub", - "publisher_resource_group_name": "sunny-uksouth", - "nf_name": "nf-agent-cnf", - "version": "0.1.0", - "acr_artifact_store_name": "sunny-nfagent-acr-2", - "location": "uksouth", - "images": { - "source_registry": "--this was copied here and renamed from https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/4a0479c0-b795-4d0f-96fd-c7edd2a2928f/resourceGroups/pez-nfagent-pipelines/providers/Microsoft.ContainerRegistry/registries/peznfagenttemp/overview new one was /subscriptions/c7bd9d96-70dd-4f61-af56-6e0abd8d80b5/resourceGroups/sunny-nfagent-acr-HostedResources-4CDE264A/providers/Microsoft.ContainerRegistry/registries/SunnyclipubSunnyNfagentAcre00abc1832" - }, - "helm_packages": [ - { - "name": "nf-agent-cnf", - "path_to_chart": "helm-charts/nf-agent-cnf-0.1.0.tgz", - "path_to_mappings": "", - "depends_on": [] - } - ] -} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.json b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.json deleted file mode 100644 index f6a97949291..00000000000 --- a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "publisher_name": "sunnyclipub", - "publisher_resource_group_name": "sunny-uksouth", - "nf_name": "nginx-basic-test", - "version": "0.1.0", - "acr_artifact_store_name": "sunny-nfagent-acr-2", - "location": "uksouth", - "images": - { - "source_registry": "this was nonsense just put something in to stop CLI complaining. The image was manually uploaded. /subscriptions/c7bd9d96-70dd-4f61-af56-6e0abd8d80b5/resourceGroups/SIMPLVM-team-CI/providers/Microsoft.ContainerRegistry/registries/a4oSIMPL" - }, - "helm_packages": [ - { - "name": "nfconfigchart", - "path_to_chart": "helm-charts/nfconfigchart-0.1.0.tgz", - "path_to_mappings": "helm-charts/nfconfigchart/nfconfigchartvalues.mappings.yaml", - "depends_on": [ - ] - } - ] -} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.jsonc b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.jsonc new file mode 100644 index 00000000000..1da910afbe5 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/input-nfconfigchart.jsonc @@ -0,0 +1,29 @@ +{ + // Azure location to use when creating resources. + "location": "uksouth", + // Name of the Publisher resource you want your definition published to. + // Will be created if it does not exist. + "publisher_name": "cli-tests-nginx-publisher", + // Optional. Resource group for the Publisher resource. + // Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "cli_test_cnf_nfd", + // Optional. Name of the ACR Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "nginx-acr", + // Name of NF definition. + "nf_name": "nginx", + // Version of the NF definition in A.B.C format. + "version": "1.0.0", + // List of registries from which to pull the image(s). + // For example [sourceacr.azurecr.io/test, myacr2.azurecr.io]. + "image_sources": ["docker.io"], + + // List of Helm packages to be included in the CNF. + "helm_packages": [ + { + "name": "nfconfigchart", + "path_to_chart": "helm-charts/nfconfigchart-0.1.0.tgz", + "default_values": "" + } + ] +} diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_config_file.json b/src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_config_file.json index 6f2372284bc..4c91d8e75c7 100644 --- a/src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_config_file.json +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_config_file.json @@ -9,8 +9,7 @@ { "name": "invalid-package", "path_to_chart": "src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_chart.yaml", - "path_to_mappings":"src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_mappings.yaml", - "depends_on": [] + "path_to_mappings":"src/aosm/azext_aosm/tests/latest/mock_cnf/invalid_mappings.yaml" } ] } \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_cnf/nf-agent-cnf-helm_template_output.yaml b/src/aosm/azext_aosm/tests/latest/mock_cnf/nf-agent-cnf-helm_template_output.yaml new file mode 100644 index 00000000000..fd82bf9bc4c --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_cnf/nf-agent-cnf-helm_template_output.yaml @@ -0,0 +1,145 @@ +--- +# Source: nf-agent-cnf/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nf-agent-cnf + labels: + helm.sh/chart: nf-agent-cnf-0.1.0 + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +--- +# Source: nf-agent-cnf/templates/nf-agent-config-map.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nfagent-config +data: + config.yaml: | + # Example NF Agent config file. + # Config is read from /etc/nf-agent/config.yaml. + log_level: debug + service_bus: + # Using a namespace and Managed Identity (specified by client ID) for auth. + namespace: sb-uowvjfivpyuow + # Helm uses sprig for templating, so we can use sprig functions to find just the UID from the full Managed Identity ID path. + identity: 041db2eb-36e0-42cd-ac13-03ae8e997cd1 + # Alternatively can use a connstring instead of namespace + managed identity: + # connstring: "Endpoint=sb://contoso.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=TopSecretSASTokenGoesHere=" + subscriptions: + - topic: simpl + subscription: simpl-subscription + # Handler-specific config + handler_config: + simpl: + # The endpoint is constructed from the namespace and service name of the receiving thing. + # We couldn't get AOSM to install the service to listen on anything but port 80 + # Doh - that was because we changed values.yaml in the chart but didn't change values.mappings.yaml in the NFDV + # Changing values.mappings.yaml should make this work on port 5222 as expected. + endpoint: http://nfconfigchart.nfconfigchart.svc.cluster.local:80 # DevSkim: ignore DS162092 +--- +# Source: nf-agent-cnf/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: nf-agent-cnf + labels: + helm.sh/chart: nf-agent-cnf-0.1.0 + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - port: 8123 + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf +--- +# Source: nf-agent-cnf/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nf-agent-cnf + labels: + helm.sh/chart: nf-agent-cnf-0.1.0 + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + template: + metadata: + labels: + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + #aadpodidbinding: - not using podidentity any more + spec: + # Copied imagePullSecrets from how afosas-aosm repo does it + imagePullSecrets: [] + serviceAccountName: nf-agent-cnf + securityContext: + {} + containers: + - name: nf-agent-cnf + securityContext: + {} + # Edited the image to point to the nf-agent image in the Artifact Store ACR + image: "sunnyclipubsunnynfagentacr2dd56aed266.azurecr.io/pez-nfagent:879624" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8123 + protocol: TCP + # Commented out otherwise kubernetes keeps restarting the pod thinking the probes have failed + # livenessProbe: + # httpGet: + # path: / + # port: http + # readinessProbe: + # httpGet: + # path: / + # port: http + resources: + {} + # Gets the NF Agent config from the configMap - see nf-agent-config-map.yaml + volumeMounts: + - name: nfagent-config-volume + mountPath: /etc/nf-agent/config.yaml + subPath: config.yaml + volumes: + - name: nfagent-config-volume + configMap: + name: nfagent-config +--- +# Source: nf-agent-cnf/templates/tests/test-connection.yaml +apiVersion: v1 +kind: Pod +metadata: + name: "nf-agent-cnf-test-connection" + labels: + helm.sh/chart: nf-agent-cnf-0.1.0 + app.kubernetes.io/name: nf-agent-cnf + app.kubernetes.io/instance: nf-agent-cnf + app.kubernetes.io/version: "1.16.0" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['nf-agent-cnf:8123'] + restartPolicy: Never diff --git a/src/aosm/azext_aosm/tests/latest/mock_vnf/input_with_fp.json b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath copy.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/mock_vnf/input_with_fp.json rename to src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath copy.json diff --git a/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath.jsonc b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath.jsonc new file mode 100644 index 00000000000..d9eb7e128e4 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_filepath.jsonc @@ -0,0 +1,56 @@ +{ + // Azure location to use when creating resources. + "location": "eastus", + // Name of the Publisher resource you want your definition published to. + // Will be created if it does not exist. + "publisher_name": "jamie-mobile-publisher", + // Optional. Resource group for the Publisher resource. + // Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "Jamie-publisher", + // Optional. Name of the ACR Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "ubuntu-acr", + // Name of NF definition. + "nf_name": "ubuntu-vm", + // Version of the NF definition in A.B.C format. + "version": "1.0.0", + // Optional. Name of the storage account Artifact Store resource. + // Will be created if it does not exist (with a default name if none is supplied). + "blob_artifact_store_name": "ubuntu-blob-store", + // The parameter name in the VM ARM template which specifies the name of the image to use for the VM. + "image_name_parameter": "imageName", + // ARM template configuration. + "arm_templates": [ + { + // Name of the artifact. + "artifact_name": "test-art", + // Version of the artifact in A.B.C format. + "version": "1.0.0", + // File path of the artifact you wish to upload from your local disk. + // Relative paths are relative to the configuration file. On Windows escape any backslash with another backslash. + "file_path": "ubuntu-template.json" + } + ], + // VHD image configuration. + "vhd": { + // Optional. Name of the artifact. + "artifact_name": "", + // Version of the artifact in A-B-C format. + "version": "1-0-0", + // 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. + "file_path": "livecd.ubuntu-cpc.azure.vhd", + // Optional. SAS URL of the blob artifact you wish to copy to your Artifact Store. + // Delete if not required. On Windows escape any backslash with another backslash. + "blob_sas_url": "", + // Optional. Specifies the size of empty data disks in gigabytes. + // This value cannot be larger than 1023 GB. Delete if not required. + "image_disk_size_GB": "", + // Optional. Specifies the HyperVGenerationType of the VirtualMachine created from the image. + // Valid values are V1 and V2. V1 is the default if not specified. Delete if not required. + "image_hyper_v_generation": "", + // Optional. The ARM API version used to create the Microsoft.Compute/images resource. + // Delete if not required. + "image_api_version": "" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_fp.json b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_fp.json new file mode 100644 index 00000000000..a527f0061a2 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_fp.json @@ -0,0 +1,18 @@ +{ + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", + "nf_name": "ubuntu-vm", + "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", + "blob_artifact_store_name": "ubuntu-blob-store", + "image_name_parameter": "imageName", + "arm_template": { + "file_path": "ubuntu-template.json", + "version": "1.0.0" + }, + "vhd": { + "file_path": "livecd.ubuntu-cpc.azure.vhd", + "version": "1-0-0" + } +} diff --git a/src/aosm/azext_aosm/tests/latest/mock_vnf/input_with_sas.json b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas.json similarity index 100% rename from src/aosm/azext_aosm/tests/latest/mock_vnf/input_with_sas.json rename to src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas.json diff --git a/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas_token.json b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas_token.json new file mode 100644 index 00000000000..5222d940186 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/input_with_sas_token.json @@ -0,0 +1,21 @@ +{ + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", + "nf_name": "ubuntu-vm", + "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", + "blob_artifact_store_name": "ubuntu-blob-store", + "image_name_parameter": "imageName", + "arm_template": { + "file_path": "ubuntu-template.json", + "version": "1.0.0" + }, + "vhd": { + "blob_sas_url": "https://a/dummy/sas-url", + "version": "1-0-0", + "image_disk_size_GB": 30, + "image_hyper_v_generation": "V1", + "image_api_version": "2023-03-01" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_core_vnf/ubuntu-template.json b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/ubuntu-template.json new file mode 100644 index 00000000000..378927a3fe5 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_core_vnf/ubuntu-template.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.15.31.15270", + "templateHash": "1656082395923655778" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "subnetName": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string", + "defaultValue": "ubuntu-vm" + }, + "virtualNetworkId": { + "type": "string" + }, + "sshPublicKeyAdmin": { + "type": "string" + }, + "imageName": { + "type": "string" + } + }, + "variables": { + "imageResourceGroup": "[resourceGroup().name]", + "subscriptionId": "[subscription().subscriptionId]", + "vmSizeSku": "Standard_D2s_v3" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2021-05-01", + "name": "[format('{0}_nic', parameters('ubuntuVmName'))]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[format('{0}/subnets/{1}', parameters('virtualNetworkId'), parameters('subnetName'))]" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2021-07-01", + "name": "[parameters('ubuntuVmName')]", + "location": "[parameters('location')]", + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSizeSku')]" + }, + "storageProfile": { + "imageReference": { + "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('subscriptionId'), variables('imageResourceGroup')), 'Microsoft.Compute/images', parameters('imageName'))]" + }, + "osDisk": { + "osType": "Linux", + "name": "[format('{0}_disk', parameters('ubuntuVmName'))]", + "createOption": "FromImage", + "caching": "ReadWrite", + "writeAcceleratorEnabled": false, + "managedDisk": "[json('{\"storageAccountType\": \"Premium_LRS\"}')]", + "deleteOption": "Delete", + "diskSizeGB": 30 + } + }, + "osProfile": { + "computerName": "[parameters('ubuntuVmName')]", + "adminUsername": "azureuser", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "/home/azureuser/.ssh/authorized_keys", + "keyData": "[parameters('sshPublicKeyAdmin')]" + } + ] + }, + "provisionVMAgent": true, + "patchSettings": { + "patchMode": "ImageDefault", + "assessmentMode": "ImageDefault" + } + }, + "secrets": [], + "allowExtensionOperations": true + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + ] + } + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input.json b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_filepath.json similarity index 53% rename from src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input.json rename to src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_filepath.json index fa4e8dd303d..b3a3b991a1d 100644 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_filepath.json @@ -1,18 +1,18 @@ { - "publisher_name": "automated-tests-ubuntuPublisher", - "publisher_resource_group_name": "cli_test_vnf_nsd_000001", - "acr_artifact_store_name": "ubuntu-acr", - "location": "uaenorth", + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", "nf_name": "ubuntu-vm", "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", "blob_artifact_store_name": "ubuntu-blob-store", "image_name_parameter": "imageName", "arm_template": { - "file_path": "../vnf_mocks/ubuntu_template.json", + "file_path": "ubuntu-template.json", "version": "1.0.0" }, "vhd": { - "file_path": "../vnf_mocks/ubuntu.vhd", + "file_path": "livecd.ubuntu-cpc.azure.vhd", "version": "1-0-0" } -} \ No newline at end of file +} diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input_template.json b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_fp.json similarity index 52% rename from src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input_template.json rename to src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_fp.json index f4ded903cb3..b3a3b991a1d 100644 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_input_template.json +++ b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_fp.json @@ -1,18 +1,18 @@ { - "publisher_name": "automated-cli-tests-ubuntu-publisher", - "publisher_resource_group_name": "{{publisher_resource_group_name}}", - "acr_artifact_store_name": "ubuntu-acr", - "location": "uaenorth", + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", "nf_name": "ubuntu-vm", "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", "blob_artifact_store_name": "ubuntu-blob-store", "image_name_parameter": "imageName", "arm_template": { - "file_path": "../vnf_mocks/ubuntu_template.json", + "file_path": "ubuntu-template.json", "version": "1.0.0" }, "vhd": { - "file_path": "../vnf_mocks/ubuntu.vhd", + "file_path": "livecd.ubuntu-cpc.azure.vhd", "version": "1-0-0" } -} \ No newline at end of file +} diff --git a/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas.json b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas.json new file mode 100644 index 00000000000..5222d940186 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas.json @@ -0,0 +1,21 @@ +{ + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", + "nf_name": "ubuntu-vm", + "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", + "blob_artifact_store_name": "ubuntu-blob-store", + "image_name_parameter": "imageName", + "arm_template": { + "file_path": "ubuntu-template.json", + "version": "1.0.0" + }, + "vhd": { + "blob_sas_url": "https://a/dummy/sas-url", + "version": "1-0-0", + "image_disk_size_GB": 30, + "image_hyper_v_generation": "V1", + "image_api_version": "2023-03-01" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas_token.json b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas_token.json new file mode 100644 index 00000000000..5222d940186 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/input_with_sas_token.json @@ -0,0 +1,21 @@ +{ + "publisher_name": "jamie-mobile-publisher", + "publisher_resource_group_name": "Jamie-publisher", + "nf_name": "ubuntu-vm", + "version": "1.0.0", + "acr_artifact_store_name": "ubuntu-acr", + "location": "eastus", + "blob_artifact_store_name": "ubuntu-blob-store", + "image_name_parameter": "imageName", + "arm_template": { + "file_path": "ubuntu-template.json", + "version": "1.0.0" + }, + "vhd": { + "blob_sas_url": "https://a/dummy/sas-url", + "version": "1-0-0", + "image_disk_size_GB": 30, + "image_hyper_v_generation": "V1", + "image_api_version": "2023-03-01" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/ubuntu-template.json b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/ubuntu-template.json new file mode 100644 index 00000000000..378927a3fe5 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/mock_nexus_vnf/ubuntu-template.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.15.31.15270", + "templateHash": "1656082395923655778" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "subnetName": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string", + "defaultValue": "ubuntu-vm" + }, + "virtualNetworkId": { + "type": "string" + }, + "sshPublicKeyAdmin": { + "type": "string" + }, + "imageName": { + "type": "string" + } + }, + "variables": { + "imageResourceGroup": "[resourceGroup().name]", + "subscriptionId": "[subscription().subscriptionId]", + "vmSizeSku": "Standard_D2s_v3" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2021-05-01", + "name": "[format('{0}_nic', parameters('ubuntuVmName'))]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[format('{0}/subnets/{1}', parameters('virtualNetworkId'), parameters('subnetName'))]" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2021-07-01", + "name": "[parameters('ubuntuVmName')]", + "location": "[parameters('location')]", + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSizeSku')]" + }, + "storageProfile": { + "imageReference": { + "id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('subscriptionId'), variables('imageResourceGroup')), 'Microsoft.Compute/images', parameters('imageName'))]" + }, + "osDisk": { + "osType": "Linux", + "name": "[format('{0}_disk', parameters('ubuntuVmName'))]", + "createOption": "FromImage", + "caching": "ReadWrite", + "writeAcceleratorEnabled": false, + "managedDisk": "[json('{\"storageAccountType\": \"Premium_LRS\"}')]", + "deleteOption": "Delete", + "diskSizeGB": 30 + } + }, + "osProfile": { + "computerName": "[parameters('ubuntuVmName')]", + "adminUsername": "azureuser", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "/home/azureuser/.ssh/authorized_keys", + "keyData": "[parameters('sshPublicKeyAdmin')]" + } + ] + }, + "provisionVMAgent": true, + "patchSettings": { + "patchMode": "ImageDefault", + "assessmentMode": "ImageDefault" + } + }, + "secrets": [], + "allowExtensionOperations": true + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}_nic', parameters('ubuntuVmName')))]" + ] + } + ] +} \ No newline at end of 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 deleted file mode 100644 index c609de96eae..00000000000 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "location": "eastus", - "publisher_name": "jamie-mobile-publisher", - "publisher_resource_group_name": "Jamie-publisher", - "acr_artifact_store_name": "ubuntu-acr", - "network_functions": [ - { - "name": "ubuntu-vm-nfdg", - "version": "1.0.0", - "publisher_offering_location": "eastus", - "type": "vnf", - "multiple_instances": false, - "publisher": "jamie-mobile-publisher", - "publisher_resource_group": "Jamie-publisher" - } - ], - "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 deleted file mode 100644 index 5820cb367ba..00000000000 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multi_nf_nsd.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "publisher_name": "jamie-publisher", - "publisher_resource_group_name": "Jamie-multi-NF", - "acr_artifact_store_name": "acr", - "location": "eastus", - "network_functions": [ - { - "publisher": "reference-publisher", - "publisher_resource_group": "Reference-publisher", - "name": "nginx-nfdg", - "version": "1.0.0", - "publisher_offering_location": "eastus", - "type": "cnf", - "multiple_instances": "False" - }, - { - "publisher": "reference-publisher", - "publisher_resource_group": "Reference-publisher", - "name": "ubuntu-nfdg", - "version": "1.0.0", - "publisher_offering_location": "eastus", - "type": "vnf", - "multiple_instances": false - } - ], - "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 deleted file mode 100644 index 0d8049734c4..00000000000 --- a/src/aosm/azext_aosm/tests/latest/mock_nsd/input_multiple_instances.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "location": "eastus", - "publisher_name": "jamie-mobile-publisher", - "publisher_resource_group_name": "Jamie-publisher", - "acr_artifact_store_name": "ubuntu-acr", - "network_functions": [ - { - "name": "ubuntu-vm-nfdg", - "version": "1.0.0", - "publisher_offering_location": "eastus", - "type": "vnf", - "multiple_instances": "True", - "publisher": "jamie-mobile-publisher", - "publisher_resource_group": "Jamie-publisher" - } - ], - "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/nsd_output/test_build/configMappings/ubuntu-vm-nfdg_config_mapping.json b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/configMappings/ubuntu-vm-nfdg_config_mapping.json deleted file mode 100644 index 1d6fcf2b8f5..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/configMappings/ubuntu-vm-nfdg_config_mapping.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "deploymentParametersObject": { - "deploymentParameters": [ - "{configurationparameters('ubuntu_ConfigGroupSchema').ubuntu-vm-nfdg.deploymentParameters}" - ] - }, - "ubuntu_vm_nfdg_nfd_version": "{configurationparameters('ubuntu_ConfigGroupSchema').ubuntu-vm-nfdg.ubuntu_vm_nfdg_nfd_version}", - "managedIdentity": "{configurationparameters('ubuntu_ConfigGroupSchema').managedIdentity}" -} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/schemas/ubuntu_ConfigGroupSchema.json b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/schemas/ubuntu_ConfigGroupSchema.json deleted file mode 100644 index 287fa0a6106..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/schemas/ubuntu_ConfigGroupSchema.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "title": "ubuntu_ConfigGroupSchema", - "type": "object", - "properties": { - "ubuntu-vm-nfdg": { - "type": "object", - "properties": { - "deploymentParameters": { - "type": "object", - "properties": { - "location": { - "type": "string" - }, - "subnetName": { - "type": "string" - }, - "virtualNetworkId": { - "type": "string" - }, - "sshPublicKeyAdmin": { - "type": "string" - } - } - }, - "ubuntu_vm_nfdg_nfd_version": { - "type": "string", - "description": "The version of the ubuntu-vm-nfdg NFD to use. This version must be compatible with (have the same parameters exposed as) ubuntu-vm-nfdg." - } - }, - "required": [ - "deploymentParameters", - "ubuntu_vm_nfdg_nfd_version" - ] - }, - "managedIdentity": { - "type": "string", - "description": "The managed identity to use to deploy NFs within this SNS. This should be of the form '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}. If you wish to use a system assigned identity, set this to a blank string." - } - }, - "required": [ - "ubuntu-vm-nfdg", - "managedIdentity" - ] -} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/ubuntu-vm-nfdg_nf.bicep b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/ubuntu-vm-nfdg_nf.bicep deleted file mode 100644 index 4158f8c6ccb..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build/ubuntu-vm-nfdg_nf.bicep +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Highly Confidential Material -// -// The template that the NSD invokes to create the Network Function from a published NFDV. - -@description('Publisher where the NFD is published') -param publisherName string = 'jamie-mobile-publisher' - -@description('Resource group where the NFD publisher exists') -param publisherResourceGroup string = 'Jamie-publisher' - -@description('NFD Group name for the Network Function') -param networkFunctionDefinitionGroupName string = 'ubuntu-vm-nfdg' - -@description('NFD version') -param ubuntu_vm_nfdg_nfd_version string - -@description('The managed identity that should be used to create the NF.') -param managedIdentity string - -param location string = 'eastus' - -param nfviType string = 'AzureCore' - -param resourceGroupId string = resourceGroup().id - -@secure() -param deploymentParametersObject object - -var deploymentParameters = deploymentParametersObject.deploymentParameters - -var identityObject = (managedIdentity == '') ? { - type: 'SystemAssigned' -} : { - type: 'UserAssigned' - userAssignedIdentities: { - '${managedIdentity}': {} - } -} - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup(publisherResourceGroup) -} - -resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { - parent: publisher - name: networkFunctionDefinitionGroupName -} - -resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { - parent: nfdg - name: ubuntu_vm_nfdg_nfd_version - -} - -resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deploymentParameters: { - name: 'ubuntu-vm-nfdg${i}' - location: location - identity: identityObject - properties: { - networkFunctionDefinitionVersionResourceReference: { - id: nfdv.id - idType: 'Open' - } - nfviType: nfviType - nfviId: resourceGroupId - allowSoftwareUpdate: true - configurationType: 'Secret' - secretDeploymentValues: string(values) - } -}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/artifact_manifest.bicep b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/artifact_manifest.bicep deleted file mode 100644 index 1dddde744eb..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/artifact_manifest.bicep +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. - -// This file creates an Artifact Manifest for a NSD -param location string -@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') -param publisherName string -@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') -param acrArtifactStoreName string -@description('Name of the manifest to deploy for the ACR-backed Artifact Store') -param acrManifestNames array -@description('The name under which to store the ARM template') -param armTemplateNames array -@description('The version that you want to name the NFM template artifact, in format A.B.C. e.g. 6.13.0. If testing for development, you can use any numbers you like.') -param armTemplateVersion string - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup() -} - -resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { - parent: publisher - name: acrArtifactStoreName -} - -resource acrArtifactManifests 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = [for (values, i) in armTemplateNames: { - parent: acrArtifactStore - name: acrManifestNames[i] - location: location - properties: { - artifacts: [ - { - artifactName: armTemplateNames[i] - artifactType: 'ArmTemplate' - artifactVersion: armTemplateVersion - } - ] - } -}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/configMappings/ubuntu-vm-nfdg_config_mapping.json b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/configMappings/ubuntu-vm-nfdg_config_mapping.json deleted file mode 100644 index c903aa85a35..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/configMappings/ubuntu-vm-nfdg_config_mapping.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "deploymentParametersObject": { - "deploymentParameters": "{configurationparameters('ubuntu_ConfigGroupSchema').ubuntu-vm-nfdg.deploymentParameters}" - }, - "ubuntu_vm_nfdg_nfd_version": "{configurationparameters('ubuntu_ConfigGroupSchema').ubuntu-vm-nfdg.ubuntu_vm_nfdg_nfd_version}", - "managedIdentity": "{configurationparameters('ubuntu_ConfigGroupSchema').managedIdentity}" -} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/ubuntu-vm-nfdg_nf.bicep b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/ubuntu-vm-nfdg_nf.bicep deleted file mode 100644 index 4158f8c6ccb..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_instances/ubuntu-vm-nfdg_nf.bicep +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Highly Confidential Material -// -// The template that the NSD invokes to create the Network Function from a published NFDV. - -@description('Publisher where the NFD is published') -param publisherName string = 'jamie-mobile-publisher' - -@description('Resource group where the NFD publisher exists') -param publisherResourceGroup string = 'Jamie-publisher' - -@description('NFD Group name for the Network Function') -param networkFunctionDefinitionGroupName string = 'ubuntu-vm-nfdg' - -@description('NFD version') -param ubuntu_vm_nfdg_nfd_version string - -@description('The managed identity that should be used to create the NF.') -param managedIdentity string - -param location string = 'eastus' - -param nfviType string = 'AzureCore' - -param resourceGroupId string = resourceGroup().id - -@secure() -param deploymentParametersObject object - -var deploymentParameters = deploymentParametersObject.deploymentParameters - -var identityObject = (managedIdentity == '') ? { - type: 'SystemAssigned' -} : { - type: 'UserAssigned' - userAssignedIdentities: { - '${managedIdentity}': {} - } -} - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup(publisherResourceGroup) -} - -resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { - parent: publisher - name: networkFunctionDefinitionGroupName -} - -resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' existing = { - parent: nfdg - name: ubuntu_vm_nfdg_nfd_version - -} - -resource nf_resource 'Microsoft.HybridNetwork/networkFunctions@2023-09-01' = [for (values, i) in deploymentParameters: { - name: 'ubuntu-vm-nfdg${i}' - location: location - identity: identityObject - properties: { - networkFunctionDefinitionVersionResourceReference: { - id: nfdv.id - idType: 'Open' - } - nfviType: nfviType - nfviId: resourceGroupId - allowSoftwareUpdate: true - configurationType: 'Secret' - secretDeploymentValues: string(values) - } -}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep b/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep deleted file mode 100644 index 1dddde744eb..00000000000 --- a/src/aosm/azext_aosm/tests/latest/nsd_output/test_build_multiple_nfs/artifact_manifest.bicep +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. - -// This file creates an Artifact Manifest for a NSD -param location string -@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') -param publisherName string -@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') -param acrArtifactStoreName string -@description('Name of the manifest to deploy for the ACR-backed Artifact Store') -param acrManifestNames array -@description('The name under which to store the ARM template') -param armTemplateNames array -@description('The version that you want to name the NFM template artifact, in format A.B.C. e.g. 6.13.0. If testing for development, you can use any numbers you like.') -param armTemplateVersion string - -resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { - name: publisherName - scope: resourceGroup() -} - -resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { - parent: publisher - name: acrArtifactStoreName -} - -resource acrArtifactManifests 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = [for (values, i) in armTemplateNames: { - parent: acrArtifactStore - name: acrManifestNames[i] - location: location - properties: { - artifacts: [ - { - artifactName: armTemplateNames[i] - artifactType: 'ArmTemplate' - artifactVersion: armTemplateVersion - } - ] - } -}] \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_input_template.json b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_input_template.json deleted file mode 100644 index f3ec1f24aa3..00000000000 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_input_template.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "publisher_name": "automated-cli-tests-nginx-publisher", - "publisher_resource_group_name": "{{publisher_resource_group_name}}", - "nf_name": "nginx", - "version": "1.0.0", - "acr_artifact_store_name": "nginx-acr", - "location": "uaenorth", - "images":{ - "source_registry": "not-used-in-test-but-cannot-be-blank", - "source_registry_namespace": "" - }, - "helm_packages": [ - { - "name": "nginxdemo", - "path_to_chart": "{{path_to_chart}}", - "path_to_mappings": "", - "depends_on": [] - } - ] -} 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 deleted file mode 100644 index 81e42a38501..00000000000 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/cnf_nsd_input_template.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "location": "uaenorth", - "publisher_name": "automated-cli-tests-nginx-publisher", - "publisher_resource_group_name": "{{publisher_resource_group_name}}", - "acr_artifact_store_name": "nginx-acr", - "network_functions": [ - { - "name": "nginx-nfdg", - "version": "1.0.0", - "publisher_offering_location": "uaenorth", - "type": "cnf", - "multiple_instances": false, - "publisher": "automated-cli-tests-nginx-publisher", - "publisher_resource_group": "{{publisher_resource_group_name}}" - } - ], - "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/nsd_input.json b/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/nsd_input.json deleted file mode 100644 index 1e40b229d7a..00000000000 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/nsd_input.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "location": "uaenorth", - "publisher_name": "automated-tests-ubuntuPublisher", - "publisher_resource_group_name": "cli_test_vnf_nsd_000001", - "acr_artifact_store_name": "ubuntu-acr", - "network_functions": [ - { - "name": "ubuntu-vm-nfdg", - "version": "1.0.0", - "publisher_offering_location": "uaenorth", - "type": "vnf", - "multiple_instances": false, - "publisher": "automated-tests-ubuntuPublisher", - "publisher_resource_group": "cli_test_vnf_nsd_000001" - } - ], - "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/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 deleted file mode 100644 index 0545d8595d6..00000000000 --- a/src/aosm/azext_aosm/tests/latest/scenario_test_mocks/mock_input_templates/vnf_nsd_input_template.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "location": "uaenorth", - "publisher_name": "automated-cli-tests-ubuntu-publisher", - "publisher_resource_group_name": "{{publisher_resource_group_name}}", - "acr_artifact_store_name": "ubuntu-acr", - "network_functions": [ - { - "name": "ubuntu-vm-nfdg", - "version": "1.0.0", - "publisher_offering_location": "uaenorth", - "type": "vnf", - "multiple_instances": false, - "publisher": "automated-cli-tests-ubuntu-publisher", - "publisher_resource_group": "{{publisher_resource_group_name}}" - } - ], - "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/schemas/schema-1-values.yaml b/src/aosm/azext_aosm/tests/latest/schemas/schema-1-values.yaml new file mode 100644 index 00000000000..6b6af7e4063 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/schemas/schema-1-values.yaml @@ -0,0 +1,37 @@ +level-1_a_string: "I'm a string" +level-1_b_integer: 42 +level-1_c_boolean: true +level-1_d_null: null +level-1_e_object: + level-2_a_object: + level-3_a_string: "I'm a string at level 3" + level-3_b_integer: 342 + level-3_c_boolean: true + level-3_d_null: null + level-3_e_null_not-required: null + level-3_f_object_no-properties: + Level-3_object_key: Level-3_object_value + level-3_g_array: + - array_item_1 + - 333 + - lv3_object_in_array_key: lv3_object_in_array_value + level-2_b_string: + "I'm a string at level 2" + level-2_c_integer: + 242 + level-2_d_null: + null + level-2_e_string_not-required: "I'm not a required string" +level-1_f_object: + level-2_a_boolean: false + level-2_a_null: null +level-1_g_object_not-required_nested-required: + level-2_a_boolean: true +level-1_h_object_not-required_nested-not-required: + level-2_a_boolean: false +level-1_i_object_no-properties: + Level-1_object_key: Level-1_object_value +level-1_j_array: + - array_item_a + - 111 + - lv1_object_in_array_key: lv1_object_in_array_value \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/schemas/schema-1.json b/src/aosm/azext_aosm/tests/latest/schemas/schema-1.json new file mode 100644 index 00000000000..0a2849b8d27 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/schemas/schema-1.json @@ -0,0 +1,136 @@ +{ + "type": "object", + "properties": { + "level-1_a_string": { + "type": "string" + }, + "level-1_b_integer": { + "type": "integer" + }, + "level-1_c_boolean": { + "type": "boolean" + }, + "level-1_d_null": { + "type": "null" + }, + "level-1_e_object": { + "type": "object", + "properties": { + "level-2_a_object": { + "type": "object", + "properties": { + "level-3_a_string": { + "type": "string" + }, + "level-3_b_integer": { + "type": "integer" + }, + "level-3_c_boolean": { + "type": "boolean" + }, + "level-3_d_null": { + "type": "null" + }, + "level-3_e_null_not-required": { + "type": "null" + }, + "level-3_f_object_no-properties": { + "type": "object" + }, + "level-3_g_array": { + "type": "array" + } + }, + "required": [ + "level-3_a_string", + "level-3_b_integer", + "level-3_c_boolean", + "level-3_d_null", + "level-3_f_object_no-properties", + "level-3_g_array" + ] + }, + "level-2_b_string": { + "type": "string" + }, + "level-2_c_integer": { + "type": "integer" + }, + "level-2_d_null": { + "type": "null" + }, + "level-2_e_string_not-required": { + "type": "string" + } + }, + "required": [ + "level-2_a_object", + "level-2_b_string", + "level-2_c_integer", + "level-2_d_null" + ] + }, + "level-1_f_object": { + "type": "object", + "properties": { + "level-2_a_boolean": { + "type": "boolean" + }, + "level-2_a_null": { + "type": "null" + } + }, + "required": [ + "level-2_a_boolean", + "level-2_a_null" + ] + }, + "level-1_g_object_not-required_nested-required": { + "type": "object", + "properties": { + "level-2_a_boolean": { + "type": "boolean" + } + }, + "required": [ + "level-2_a_boolean" + ] + }, + "level-1_h_object_not-required_nested-not-required": { + "type": "object", + "properties": { + "level-2_a_boolean": { + "type": "boolean" + } + } + }, + "level-1_i_object_no-properties": { + "type": "object" + }, + "level-1_j_array": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "object" + } + ], + "additionalItems": false + } + }, + "required": [ + "level-1_a_string", + "level-1_b_integer", + "level-1_c_boolean", + "level-1_d_null", + "level-1_e_object", + "level-1_f_object", + "level-1_i_object_no-properties", + "level-1_j_array" + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/test_aosm_cnf_publish_and_delete.py b/src/aosm/azext_aosm/tests/latest/test_aosm_cnf_publish_and_delete.py deleted file mode 100644 index 21935070f30..00000000000 --- a/src/aosm/azext_aosm/tests/latest/test_aosm_cnf_publish_and_delete.py +++ /dev/null @@ -1,116 +0,0 @@ -# # -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -# Integration tests for the aosm extension. They test the following commands for the -# cnf definition type: -# aosm nfd build -# aosm nfd publish -# aosm nfd delete -# aosm nsd build -# aosm nsd publish -# aosm nsd delete -# -# -------------------------------------------------------------------------------------------- - -import os -from typing import Dict -from azure.cli.testsdk import LiveScenarioTest, ResourceGroupPreparer -from knack.log import get_logger -from jinja2 import Template - -logger = get_logger(__name__) - -NFD_INPUT_TEMPLATE_NAME = "cnf_input_template.json" -NFD_INPUT_FILE_NAME = "cnf_input.json" -NSD_INPUT_TEMPLATE_NAME = "cnf_nsd_input_template.json" -NSD_INPUT_FILE_NAME = "nsd_cnf_input.json" -CHART_NAME = "nginxdemo-0.1.0.tgz" - - -def get_path_to_chart(): - code_dir = os.path.dirname(__file__) - templates_dir = os.path.join(code_dir, "scenario_test_mocks", "cnf_mocks") - chart_path = os.path.join(templates_dir, CHART_NAME) - return chart_path - - -def update_input_file(input_template_name, output_file_name, params: Dict[str, str]): - code_dir = os.path.dirname(__file__) - templates_dir = os.path.join( - code_dir, "scenario_test_mocks", "mock_input_templates" - ) - input_template_path = os.path.join(templates_dir, input_template_name) - - with open(input_template_path, "r", encoding="utf-8") as file: - contents = file.read() - - jinja_template = Template(contents) - - rendered_template = jinja_template.render(**params) - - output_path = os.path.join(templates_dir, output_file_name) - - with open(output_path, "w", encoding="utf-8") as file: - file.write(rendered_template) - - return output_path - - -class CnfNsdTest(LiveScenarioTest): - """ - Integration tests for the aosm extension for cnf definition type. - - This test uses Live Scenario Test because it depends on using the `az login` command - which does not work when playing back from the recording. - """ - - @ResourceGroupPreparer(name_prefix="cli_test_cnf_nsd_", location="uaenorth") - def test_cnf_nsd_publish_and_delete(self, resource_group): - """ - This test creates a cnf nfd and nsd, publishes them, and then deletes them. - - :param resource_group: The name of the resource group to use for the test. - This is passed in by the ResourceGroupPreparer decorator. - """ - - chart_path = get_path_to_chart() - - nfd_input_file_path = update_input_file( - NFD_INPUT_TEMPLATE_NAME, - NFD_INPUT_FILE_NAME, - params={ - "publisher_resource_group_name": resource_group, - "path_to_chart": chart_path, - }, - ) - - self.cmd( - f'az aosm nfd build -f "{nfd_input_file_path}" --definition-type cnf --force' - ) - - try: - self.cmd( - f'az aosm nfd publish -f "{nfd_input_file_path}" --definition-type cnf --debug --skip image-upload' - ) - except Exception: - self.cmd( - f'az aosm nfd delete --definition-type cnf -f "{nfd_input_file_path}" --debug --force' - ) - raise - - nsd_input_file_path = update_input_file( - NSD_INPUT_TEMPLATE_NAME, - NSD_INPUT_FILE_NAME, - params={"publisher_resource_group_name": resource_group}, - ) - - self.cmd(f'az aosm nsd build -f "{nsd_input_file_path}" --debug --force') - - try: - self.cmd(f'az aosm nsd publish -f "{nsd_input_file_path}" --debug') - finally: - self.cmd(f'az aosm nsd delete -f "{nsd_input_file_path}" --debug --force') - self.cmd( - f'az aosm nfd delete --definition-type cnf -f "{nfd_input_file_path}" --debug --force' - ) diff --git a/src/aosm/azext_aosm/tests/latest/test_aosm_scenario.py b/src/aosm/azext_aosm/tests/latest/test_aosm_scenario.py deleted file mode 100644 index fe10ce0d1de..00000000000 --- a/src/aosm/azext_aosm/tests/latest/test_aosm_scenario.py +++ /dev/null @@ -1,39 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -import unittest - -# from azure_devtools.scenario_tests import AllowLargeResponse -from azure.cli.testsdk import ResourceGroupPreparer, ScenarioTest - -TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "..")) - -## Note: keeping this only as an example for what we could do -# class AosmScenarioTest(ScenarioTest): -# @ResourceGroupPreparer(name_prefix="cli_test_aosm") -# def test_aosm(self, resource_group): -# self.kwargs.update({"name": "test1"}) - -# self.cmd( -# "aosm create -g {rg} -n {name} --tags foo=doo", -# checks=[self.check("tags.foo", "doo"), self.check("name", "{name}")], -# ) -# self.cmd( -# "aosm update -g {rg} -n {name} --tags foo=boo", -# checks=[self.check("tags.foo", "boo")], -# ) -# count = len(self.cmd("aosm list").get_output_in_json()) -# self.cmd( -# "aosm show - {rg} -n {name}", -# checks=[ -# self.check("name", "{name}"), -# self.check("resourceGroup", "{rg}"), -# self.check("tags.foo", "boo"), -# ], -# ) -# self.cmd("aosm delete -g {rg} -n {name}") -# final_count = len(self.cmd("aosm list").get_output_in_json()) -# self.assertTrue(final_count, count - 1) diff --git a/src/aosm/azext_aosm/tests/latest/test_aosm_vnf_publish_and_delete.py b/src/aosm/azext_aosm/tests/latest/test_aosm_vnf_publish_and_delete.py deleted file mode 100644 index 39e099f7918..00000000000 --- a/src/aosm/azext_aosm/tests/latest/test_aosm_vnf_publish_and_delete.py +++ /dev/null @@ -1,116 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -# This is an integration tests for the aosm extension. It tests the following commands for the -# vnf definition type: -# aosm nfd build -# aosm nfd publish -# aosm nfd delete -# aosm nsd build -# aosm nsd publish -# aosm nsd delete -# -------------------------------------------------------------------------------------------- - -import os -from azure.cli.testsdk import LiveScenarioTest, ResourceGroupPreparer -from knack.log import get_logger -from jinja2 import Template - - -logger = get_logger(__name__) - -NFD_INPUT_TEMPLATE_NAME = "vnf_input_template.json" -NFD_INPUT_FILE_NAME = "vnf_input.json" -NSD_INPUT_TEMPLATE_NAME = "vnf_nsd_input_template.json" -NSD_INPUT_FILE_NAME = "nsd_input.json" -ARM_TEMPLATE_RELATIVE_PATH = ( - "scenario_test_mocks/vnf_mocks/ubuntu_template.json" -) - - -def update_resource_group_in_input_file( - input_template_name: str, output_file_name: str, resource_group: str -) -> str: - """ - This function updates the resource group name in the input template file and returns the - path to the updated file. - - :param input_template_name: The name of the input template file. - :param output_file_name: The name of the output file. - :param resource_group: The name of the resource group to update the input template with. - :return: The path to the updated input template file. - """ - code_dir = os.path.dirname(__file__) - templates_dir = os.path.join( - code_dir, "scenario_test_mocks", "mock_input_templates" - ) - input_template_path = os.path.join(templates_dir, input_template_name) - - with open(input_template_path, "r", encoding="utf-8") as file: - contents = file.read() - - jinja_template = Template(contents) - - rendered_template = jinja_template.render( - publisher_resource_group_name=resource_group - ) - - output_path = os.path.join(templates_dir, output_file_name) - - with open(output_path, "w", encoding="utf-8") as file: - file.write(rendered_template) - - return output_path - - -class VnfNsdTest(LiveScenarioTest): - """This class contains the integration tests for the aosm extension for vnf definition type.""" - - @ResourceGroupPreparer( - name_prefix="cli_test_vnf_nsd_", location="uaenorth" - ) - def test_vnf_nsd_publish_and_delete(self, resource_group): - """ - This test creates a vnf nfd and nsd, publishes them, and then deletes them. - - :param resource_group: The name of the resource group to use for the test. - This is passed in by the ResourceGroupPreparer decorator. - """ - nfd_input_file_path = update_resource_group_in_input_file( - NFD_INPUT_TEMPLATE_NAME, NFD_INPUT_FILE_NAME, resource_group - ) - - self.cmd( - f'az aosm nfd build -f "{nfd_input_file_path}" --definition-type vnf --force' - ) - - try: - self.cmd( - f'az aosm nfd publish -f "{nfd_input_file_path}" --definition-type vnf' - ) - except Exception: - # If the command fails, then the test should fail. - # We still need to clean up the resources, so we run the delete command. - self.cmd( - f'az aosm nfd delete --definition-type vnf -f "{nfd_input_file_path}" --clean --force' - ) - raise - - nsd_input_file_path = update_resource_group_in_input_file( - NSD_INPUT_TEMPLATE_NAME, NSD_INPUT_FILE_NAME, resource_group - ) - - self.cmd(f'az aosm nsd build -f "{nsd_input_file_path}" --force') - - try: - self.cmd(f'az aosm nsd publish -f "{nsd_input_file_path}"') - finally: - # If the command fails, then the test should fail. - # We still need to clean up the resources, so we run the delete command. - self.cmd( - f'az aosm nsd delete -f "{nsd_input_file_path}" --clean --force' - ) - self.cmd( - f'az aosm nfd delete --definition-type vnf -f "{nfd_input_file_path}" --clean --force' - ) diff --git a/src/aosm/azext_aosm/tests/latest/test_cnf.py b/src/aosm/azext_aosm/tests/latest/test_cnf.py deleted file mode 100644 index 0eaa1173a7c..00000000000 --- a/src/aosm/azext_aosm/tests/latest/test_cnf.py +++ /dev/null @@ -1,67 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -import unittest -import json -import os -from pathlib import Path -from tempfile import TemporaryDirectory - -from azext_aosm.custom import build_definition, generate_definition_config - - -mock_cnf_folder = ((Path(__file__).parent) / "mock_cnf").resolve() - - -class TestCNF(unittest.TestCase): - def test_generate_config(self): - """Test generating a config file for a VNF.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - generate_definition_config("cnf") - assert os.path.exists("input.json") - finally: - os.chdir(starting_directory) - - def test_build(self): - """Test the build command for CNFs.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_definition( - "cnf", str(mock_cnf_folder / "input-nfconfigchart.json") - ) - assert os.path.exists("nfd-bicep-nginx-basic-test") - # Confirm that the generated schema file correctly handles array deployment params. - assert os.path.exists("nfd-bicep-nginx-basic-test/schemas/deploymentParameters.json") - with open("nfd-bicep-nginx-basic-test/schemas/deploymentParameters.json") as f: - schema = json.load(f) - assert schema["properties"]["imagePullSecrets_0"]["type"] == "string" - finally: - os.chdir(starting_directory) - - def test_build_no_mapping(self): - """ - Test the build command for CNFs where no mapping file is supplied. - - Also reorder the parameters. - """ - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_definition( - "cnf", - str(mock_cnf_folder / "input-nf-agent-cnf.json"), - order_params=True, - ) - assert os.path.exists("nfd-bicep-nf-agent-cnf") - finally: - os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/test_vnf.py b/src/aosm/azext_aosm/tests/latest/test_vnf.py deleted file mode 100644 index d6d178f56d0..00000000000 --- a/src/aosm/azext_aosm/tests/latest/test_vnf.py +++ /dev/null @@ -1,89 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import json -import os -import unittest -from pathlib import Path -from tempfile import TemporaryDirectory - -from azext_aosm.custom import build_definition, generate_definition_config - -mock_vnf_folder = ((Path(__file__).parent) / "mock_vnf").resolve() - -INPUT_WITH_SAS_VHD_PARAMS = { - "imageName": "ubuntu-vmImage", - "imageDiskSizeGB": 30, - "imageHyperVGeneration": "V1", - "imageApiVersion": "2023-03-01", -} - - -def validate_vhd_parameters(expected_params, vhd_params_file_path): - """Validate that the expected parameters are in the actual parameters.""" - assert os.path.exists(vhd_params_file_path) - with open(vhd_params_file_path) as f: - actual_params = json.load(f) - assert expected_params == actual_params - - -class TestVNF(unittest.TestCase): - def test_generate_config(self): - """Test generating a config file for a VNF.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - generate_definition_config("vnf") - assert os.path.exists("input.json") - finally: - os.chdir(starting_directory) - - def test_build(self): - """Test building an NFDV for a VNF.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_definition( - "vnf", str(mock_vnf_folder / "input_with_fp.json") - ) - assert os.path.exists("nfd-bicep-ubuntu-template") - finally: - os.chdir(starting_directory) - - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_definition( - "vnf", str(mock_vnf_folder / "input_with_sas.json") - ) - assert os.path.exists("nfd-bicep-ubuntu-template") - print(os.listdir("nfd-bicep-ubuntu-template")) - validate_vhd_parameters( - INPUT_WITH_SAS_VHD_PARAMS, - "nfd-bicep-ubuntu-template/configMappings/vhdParameters.json", - ) - finally: - os.chdir(starting_directory) - - def test_build_with_ordered_params(self): - """Test building an NFDV for a VNF.""" - starting_directory = os.getcwd() - with TemporaryDirectory() as test_dir: - os.chdir(test_dir) - - try: - build_definition( - "vnf", - str(mock_vnf_folder / "input_with_fp.json"), - order_params=True, - ) - assert os.path.exists("nfd-bicep-ubuntu-template") - finally: - os.chdir(starting_directory) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/input_files/nsd-input.jsonc b/src/aosm/azext_aosm/tests/latest/unit_test/input_files/nsd-input.jsonc new file mode 100644 index 00000000000..c3e1db97862 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/input_files/nsd-input.jsonc @@ -0,0 +1,50 @@ +{ + // Azure location to use when creating resources. + "location": "", + // Name of the Publisher resource you want your definition published to. Will be created if it does not exist. + "publisher_name": "", + // Optional. Resource group for the Publisher resource. Will be created if it does not exist (with a default name if none is supplied). + "publisher_resource_group_name": "", + // Optional. Name of the ACR Artifact Store resource. Will be created if it does not exist (with a default name if none is supplied). + "acr_artifact_store_name": "", + // Network Service Design (NSD) name. This is the collection of Network Service Design Versions. Will be created if it does not exist. + "nsd_name": "", + // Version of the NSD to be created. This should be in the format A.B.C + "nsd_version": "", + // Description of the Network Service Design Version (NSDV). + "nsdv_description": "", + // List of Resource Element Templates. + "resource_element_templates": [ + { + // Type of Resource Element. Either NF or ArmTemplate + "resource_element_type": "", + "properties": { + // The name of the existing publisher for the NSD. + "publisher": "", + // The resource group that the publisher is hosted in. + "publisher_resource_group": "", + // The name of the existing Network Function Definition Group to deploy using this NSD. + "name": "", + // 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. + "version": "", + // The region that the NFDV is published to. + "publisher_offering_location": "", + "type": "" + } + }, + { + // Type of Resource Element. Either NF or ArmTemplate + "resource_element_type": "", + // Properties of the Resource Element. + "properties": { + // Optional. Name of the artifact. + "artifact_name": "", + // Version of the artifact in A.B.C format. + "version": "", + // File path of the artifact you wish to upload from your local disk. Relative paths are relative to the configuration file. On Windows escape any backslash with another backslash. + "file_path": "" + } + } + ] +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_artifact_builder.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_artifact_builder.py new file mode 100644 index 00000000000..0526f4eb548 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_artifact_builder.py @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from azext_aosm.definition_folder.builder.artifact_builder import ( + ArtifactDefinitionElementBuilder, +) + + +class TestArtifactDefinitionElementBuilder(TestCase): + """Test the artifact definition element builder.""" + + @patch("pathlib.Path.write_text") + @patch("pathlib.Path.mkdir") + def test_write(self, mock_mkdir, mock_write_text): + """Test writing the definition element to disk.""" + + # Create some mocks to act as artifacts. + artifact_1 = MagicMock() + artifact_1.to_dict.return_value = {"abc": "def"} + artifact_2 = MagicMock() + artifact_2.to_dict.return_value = {"ghi": "jkl"} + + # Create a Artifact definition element builder. + artifact_definition_element_builder = ArtifactDefinitionElementBuilder( + Path("/some/folder"), [artifact_1, artifact_2] + ) + + # Write the definition element to disk. + artifact_definition_element_builder.write() + + # Check that the definition element was written to disk. + mock_mkdir.assert_called_once() + artifact_1.to_dict.assert_called_once() + artifact_2.to_dict.assert_called_once() + expected_params = [{"abc": "def"}, {"ghi": "jkl"}] + mock_write_text.assert_called_once_with(json.dumps(expected_params, indent=4)) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_bicep_builder.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_bicep_builder.py new file mode 100644 index 00000000000..11b9c699575 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_bicep_builder.py @@ -0,0 +1,62 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from azext_aosm.definition_folder.builder.bicep_builder import ( + BicepDefinitionElementBuilder, +) + + +class TestBicepDefinitionElementBuilder(TestCase): + """Test the Bicep definition element builder.""" + + @patch("pathlib.Path.write_text") + @patch("pathlib.Path.mkdir") + def test_write(self, mock_mkdir, mock_write_text): + """Test writing the definition element to disk.""" + + # Create a Bicep definition element builder. + bicep_definition_element_builder = BicepDefinitionElementBuilder( + Path("/some/folder"), "some bicep content" + ) + + # Write the definition element to disk. + bicep_definition_element_builder.write() + + # Check that the definition element was written to disk. + mock_mkdir.assert_called_once() + mock_write_text.assert_called_once_with("some bicep content") + + @patch("pathlib.Path.write_text") + @patch("pathlib.Path.mkdir") + def test_write_supporting_files(self, mock_mkdir, mock_write_text): + """Test writing the definition element to disk with supporting files.""" + + # Create a Bicep definition element builder. + bicep_definition_element_builder = BicepDefinitionElementBuilder( + Path("/some/folder"), "some bicep content" + ) + + # Create some mocks to act as supporting files. + supporting_file_1 = MagicMock() + supporting_file_2 = MagicMock() + + # Add the supporting files to the definition element builder. + bicep_definition_element_builder.add_supporting_file(supporting_file_1) + bicep_definition_element_builder.add_supporting_file(supporting_file_2) + + # Write the definition element to disk. + bicep_definition_element_builder.write() + + # Check that the definition element was written to disk. + mock_mkdir.assert_called_once() + mock_write_text.assert_called_once_with("some bicep content") + + # Check that the supporting files were written to disk. + supporting_file_1.write.assert_called_once() + supporting_file_2.write.assert_called_once() diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_definition_folder_builder.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_definition_folder_builder.py new file mode 100644 index 00000000000..801d86c7304 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_definiton_folder_builder/test_definition_folder_builder.py @@ -0,0 +1,52 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import json +from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from azext_aosm.definition_folder.builder.definition_folder_builder import DefinitionFolderBuilder + + +class TestDefinitionFolderBuilder(TestCase): + """Test the definition folder builder.""" + + @patch("pathlib.Path.write_text") + @patch("pathlib.Path.mkdir") + def test_add_elements_and_write(self, mock_mkdir, mock_write_text): + """Test adding elements and writing the definition folder.""" + + # Create some mocks to act as definition elements. + element_1 = MagicMock() + element_1.path = Path("/some/folder/element_1") + element_1.only_delete_on_clean = False + element_2 = MagicMock() + element_2.path = Path("/some/folder/element_2") + element_2.only_delete_on_clean = True + + # Create a definition folder builder. + definition_folder_builder = DefinitionFolderBuilder(Path("/some/folder")) + + # Add the elements to the definition folder builder. + definition_folder_builder.add_element(element_1) + definition_folder_builder.add_element(element_2) + + # Write the definition folder. + definition_folder_builder.write() + + # Check that the elements were written to disk. + mock_mkdir.assert_called_once() + element_1.write.assert_called_once() + element_2.write.assert_called_once() + + # Check that the index.json file was written to disk. + expected_params = [{"name": "element_1", + "type": "artifact", + "only_delete_on_clean": False }, + {"name": "element_2", + "type": "artifact", + "only_delete_on_clean": True}] + mock_write_text.assert_called_once_with(json.dumps(expected_params, indent=4)) + diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_cnf_generate_config.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_cnf_generate_config.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_core_vnf_generate_config.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_core_vnf_generate_config.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nexus_vnf_generate_config.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nexus_vnf_generate_config.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nsd_generate_config.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_generate_config/test_nsd_generate_config.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_arm_template_input.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_arm_template_input.py new file mode 100644 index 00000000000..1a1cbdc4e4d --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_arm_template_input.py @@ -0,0 +1,171 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging +import os +import sys +from unittest import TestCase +from unittest.mock import mock_open, patch + +from azext_aosm.inputs.arm_template_input import ArmTemplateInput + +code_directory = os.path.dirname(__file__) +parent_directory = os.path.abspath(os.path.join(code_directory, "../..")) +arm_template_path = os.path.join(parent_directory, "mock_arm_templates", "simple-template.json") +no_params_template_path = os.path.join(parent_directory, "mock_arm_templates", "no-params-template.json") + + +class TestARMTemplateInput(TestCase): + """Test the ARMTempalteInput class.""" + + def setUp(self): + # Prints out info logs in console if fails + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + self.arm_input = ArmTemplateInput( + artifact_name="test-artifact-name", + artifact_version="1.1.1", + template_path=arm_template_path, + default_config=None + ) + + def test_get_defaults_is_none(self): + """Test ARM template input when default config is None""" + arm_template_input = self.arm_input + + # Test when default_config is None + arm_template_input.default_config = None + defaults = arm_template_input.get_defaults() + self.assertEqual(defaults, {}) + + def test_get_defaults_is_empty_dict(self): + """Test ARM template input when default config is {}""" + arm_template_input = self.arm_input + # Test when default_config is an empty dictionary + arm_template_input.default_config = {} + defaults = arm_template_input.get_defaults() + self.assertEqual(defaults, {}) + + def test_get_defaults_with_config(self): + """Test ARM template input when default config provided""" + arm_template_input = self.arm_input + # Test when default_config has some values + arm_template_input.default_config = { + "param1": "value1", + "param2": "value2" + } + defaults = arm_template_input.get_defaults() + self.assertEqual(defaults, { + "param1": "value1", + "param2": "value2" + }) + + def test_get_schema_with_params(self): + """Test getting the schema for the ARM template input.""" + schema = self.arm_input.get_schema() + expected_schema = { + '$schema': 'https://json-schema.org/draft-07/schema#', + 'properties': {'location': {'type': 'string', 'default': 'uksouth'}}, + 'required': [], + 'type': 'object' + } + print("SCHEMA", schema) + self.assertEqual(schema, expected_schema) + + @patch("builtins.open", mock_open( + read_data='{"$schema": "#", "resources": { } }')) + def test_get_schema_no_parameters(self): + """Test getting the schema for the ARM template input when no parameters are found.""" + + no_params_arm_input = ArmTemplateInput( + artifact_name="test-artifact-name", + artifact_version="1.1.1", + template_path=no_params_template_path, + default_config=None + ) + # Assert logger warning when no parameters in file + with self.assertLogs(level='WARNING'): + schema = no_params_arm_input.get_schema() + expected_schema = { + '$schema': 'https://json-schema.org/draft-07/schema#', + 'properties': {}, + 'required': [], + 'type': 'object' + } + # Assert outputted schema is base schema with empty properties + self.assertEqual(schema, expected_schema) + + def test_generate_schema_from_params_with_default_values(self): + """ Test _generate_schema_from_arm_params for ARM template input. + With default values, which mean no required params + """ + schema = { + "properties": {}, + "required": [] + } + data = { + "test": { + "type": "string", + "defaultValue": "test" + } + } + # pylint: disable=protected-access + self.arm_input._generate_schema_from_arm_params(schema, data) + + expected_schema = { + 'properties': + {'test': {'type': 'string', 'default': 'test'}}, + 'required': []} + + self.assertEqual(schema, expected_schema) + + def test_generate_schema_from_params_with_no_default_values(self): + """ Test _generate_schema_from_params for ARM template input. + Without default values, so they should be added to required properties + """ + schema = { + "properties": {}, + "required": [] + } + data = { + "test": { + "type": "string" + } + } + # pylint: disable=protected-access + self.arm_input._generate_schema_from_arm_params(schema, data) + expected_schema = {'properties': {'test': {'type': 'string'}}, 'required': ['test']} + self.assertEqual(schema, expected_schema) + + def test_generate_schema_from_params_nested_properties(self): + """ Test _generate_schema_from_arm_params for ARM template input. + With an object in the template, so we expect this to be called recursively + """ + schema = { + "properties": {}, + "required": [] + } + data = { + "test": { + "type": "string" + }, + "vmImageRepositoryCredentials": { + "type": "object", + "metadata": { + "description": "Credentials used to login to the image repository." + } + } + } + # pylint: disable=protected-access + self.arm_input._generate_schema_from_arm_params(schema, data) + + # We expect vmImageRepositoryCredentials to be required and to have a nested schema + expected_schema = {'properties': + {'test': {'type': 'string'}, + 'vmImageRepositoryCredentials': { + "type": "object", + "properties": {}, + "required": []} + }, + 'required': ['test', 'vmImageRepositoryCredentials']} + self.assertEqual(schema, expected_schema) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_helm_chart_input.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_helm_chart_input.py new file mode 100644 index 00000000000..0f45654d28b --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_inputs/test_helm_chart_input.py @@ -0,0 +1,152 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging +import os +import sys +from pathlib import Path +from unittest import TestCase +import tempfile +import json +from azext_aosm.common.exceptions import ( + DefaultValuesNotFoundError, + TemplateValidationError, + SchemaGetOrGenerateError +) +from azext_aosm.inputs.helm_chart_input import HelmChartInput + +code_directory = os.path.dirname(__file__) +parent_directory = os.path.abspath(os.path.join(code_directory, "../..")) +helm_charts_directory = os.path.join(parent_directory, "mock_cnf", "helm-charts") + +VALID_CHART_NAME = "nf-agent-cnf" +INVALID_CHART_NAME = "nf-agent-cnf-invalid" + + +class TestHelmChartInput(TestCase): + """Test the HelmChartInput class.""" + + def setUp(self): + # Prints out info logs in console if fails + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + + def test_validate_template_valid_chart(self): + """Test validating a valid Helm chart using helm template.""" + + helm_chart_input = HelmChartInput( + artifact_name="test-valid", + artifact_version="1.0.0", + chart_path=Path( + os.path.join( + helm_charts_directory, + VALID_CHART_NAME, + ) + ), + ) + + # A valid template does not raise exceptions or return anything. + helm_chart_input.validate_template() + + def test_validate_template_invalid_chart(self): + """Test validating an invalid Helm chart using helm template.""" + + helm_chart_input = HelmChartInput( + artifact_name="test-invalid", + artifact_version="1.0.0", + chart_path=Path( + os.path.join( + helm_charts_directory, + INVALID_CHART_NAME, + ) + ), + ) + + self.assertRaises(TemplateValidationError, helm_chart_input.validate_template) + + def test_validate_values(self): + """Test validating whether values exist in a helm chart.""" + + helm_chart_input = HelmChartInput( + artifact_name="test-invalid", + artifact_version="1.0.0", + chart_path=Path( + os.path.join( + helm_charts_directory, + VALID_CHART_NAME, + ) + ), + ) + + helm_chart_input.validate_values() + + # Use an Invalid chart (because it does not have values.yaml in it), + # but provide a default config parameter which will override those values. + helm_chart_input_with_default_values = HelmChartInput( + artifact_name="test-default-values", + artifact_version="1.0.0", + chart_path=Path( + os.path.join( + helm_charts_directory, + INVALID_CHART_NAME, + ) + ), + default_config={"test": "test"}, + ) + + helm_chart_input_with_default_values.validate_values() + + def test_validate_invalid_values(self): + """Test validating a helm chart with values.yaml missing.""" + helm_chart_input = HelmChartInput( + artifact_name="test-invalid", + artifact_version="1.0.0", + chart_path=Path( + os.path.join( + helm_charts_directory, + INVALID_CHART_NAME, + ) + ), + ) + + with self.assertRaises(DefaultValuesNotFoundError): + helm_chart_input.validate_values() + + def test_get_schema(self): + """Test retrieving the schema for the Helm chart.""" + self.temp_dir = tempfile.TemporaryDirectory() + self.chart_path = Path(self.temp_dir.name) + + with open(self.chart_path / "Chart.yaml", "w") as f: + f.write("name: test-chart\nversion: 1.0.0") + + helm_chart_input = HelmChartInput( + artifact_name="test-schema", + artifact_version="1.0.0", + chart_path=self.chart_path, + ) + # # NOTE: temporarly commented out as we have commented out the code for using the values.schema.json + # # Test case when values.schema.json exists in the chart. + # with open(self.chart_path / "values.schema.json", "w") as f: + # json.dump({"key": "value"}, f) + # schema = helm_chart_input.get_schema() + # self.assertEqual(schema, {"key": "value"}) + + # # Test case when values.schema.json does not exist in the chart. + # os.remove(self.chart_path / "values.schema.json") + with open(self.chart_path / "values.yaml", "w") as f: + f.write("key: value") + schema = helm_chart_input.get_schema() + expected_schema = { + "type": "object", + "properties": {"key": {"type": "string"}}, + "required": ["key"], + } + self.assertEqual(schema, expected_schema) + + # Test case when neither values.schema.json nor values.yaml exist in the chart. + os.remove(self.chart_path / "values.yaml") + with self.assertRaises(SchemaGetOrGenerateError): + helm_chart_input.get_schema() + + self.temp_dir.cleanup() diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_core_arm_processor.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_core_arm_processor.py new file mode 100644 index 00000000000..d602e60361b --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_core_arm_processor.py @@ -0,0 +1,177 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import os +import logging +import sys +import json +from unittest import TestCase +from unittest.mock import MagicMock +from azext_aosm.build_processors.arm_processor import AzureCoreArmBuildProcessor +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.vendored_sdks.models import ( + AzureCoreNetworkFunctionArmTemplateApplication, + ApplicationEnablement, ArmResourceDefinitionResourceElementTemplate, + ArmResourceDefinitionResourceElementTemplateDetails, + ArmTemplateArtifactProfile, + ArmTemplateMappingRuleProfile, + AzureCoreArmTemplateArtifactProfile, + NSDArtifactProfile, + TemplateType, + AzureCoreArmTemplateDeployMappingRuleProfile, + DependsOnProfile, + ManifestArtifactFormat, + ReferencedResource, +) +from azext_aosm.common.constants import TEMPLATE_PARAMETERS_FILENAME +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.common.artifact import LocalFileACRArtifact + +code_directory = os.path.dirname(__file__) +parent_directory = os.path.abspath(os.path.join(code_directory, "../..")) +mock_vnf_directory = os.path.join(parent_directory, "mock_core_vnf") + + +class AzureCoreArmProcessorTest(TestCase): + """Class to test AzureCore ARM Processor functionality""" + + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + mock_arm_template_path = os.path.join(mock_vnf_directory, "ubuntu-template.json") + self.core_arm_input = ArmTemplateInput( + artifact_name="test-artifact-name", + artifact_version="1.1.1", + template_path=mock_arm_template_path, + default_config=None + ) + self.processor = AzureCoreArmBuildProcessor("test-name", self.core_arm_input, expose_all_params=False) + + def test_get_artifact_manifest_list(self): + """Test get artifact manifest list for Azure Core arm processor.""" + manifest_list = self.processor.get_artifact_manifest_list() + mock_manifest_artifact_format = ManifestArtifactFormat( + artifact_name="test-artifact-name", + artifact_type="ArmTemplate", + artifact_version="1.1.1", + ) + self.assertEqual(len(manifest_list), 1) + self.assertIsInstance(manifest_list[0], ManifestArtifactFormat) + self.assertEqual(manifest_list[0], mock_manifest_artifact_format) + + def test_artifact_details(self): + """Test get artifact details for Azure Core arm processor.""" + artifact_details = self.processor.get_artifact_details() + mock_arm_template_path = os.path.join(mock_vnf_directory, "ubuntu-template.json") + mock_artifact = [LocalFileACRArtifact( + artifact_name="test-artifact-name", + artifact_type="ArmTemplate", + artifact_version="1.1.1", + file_path=mock_arm_template_path, + )] + + # Testing each individial part of artifact are equal, + # as two artifacts objects are never equal, even if they contain the same content + self.assertEqual(artifact_details[0][0].artifact_name, mock_artifact[0].artifact_name) + self.assertEqual(artifact_details[0][0].artifact_version, mock_artifact[0].artifact_version) + self.assertEqual(artifact_details[0][0].artifact_type, mock_artifact[0].artifact_type) + self.assertEqual(artifact_details[0][0].file_path, mock_artifact[0].file_path) + # Ensure no list of LocalFileBuilders is returned, as this is only for NSDs + self.assertEqual(artifact_details[1], []) + assert True + + def test_generate_nf_application(self): + """Test generate nf application for Azure Core ARM Processor.""" + # Check type is correct, other functionality is tested in appropriate functions + # (such as test_generate_artifact_profile) + nf_application = self.processor.generate_nf_application() + self.assertIsInstance(nf_application, AzureCoreNetworkFunctionArmTemplateApplication) + + def test_generate_resource_element_template(self): + """Test generate RET for Azure Core ARM Processor""" + result = self.processor.generate_resource_element_template() + + # Assert the expected output + expected_template = ArmResourceDefinitionResourceElementTemplateDetails( + name="test-name", + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + configuration=ArmResourceDefinitionResourceElementTemplate( + template_type=TemplateType.ARM_TEMPLATE.value, + parameter_values=json.dumps({}), + artifact_profile=NSDArtifactProfile( + artifact_store_reference=ReferencedResource(id=""), + artifact_name=self.processor.input_artifact.artifact_name, + artifact_version=self.processor.input_artifact.artifact_version, + ), + ), + ) + # Assert each parameter equal + self.assertEqual(result.name, expected_template.name) + self.assertEqual(result.depends_on_profile, expected_template.depends_on_profile) + self.assertEqual(result.configuration.artifact_profile, + expected_template.configuration.artifact_profile) + + def test_generate_parameters_file(self): + """ Test generate parameters file for Azure Core ARM Processor""" + # Mock private function + # (generate mapping rule profile is tested elsewhere) + mapping_rule_profile = MagicMock() + mock_params = '{"param1": "value1", "param2": "value2"}' + mapping_rule_profile.template_mapping_rule_profile.template_parameters = mock_params + self.processor._generate_mapping_rule_profile = MagicMock(return_value=mapping_rule_profile) + + parameters_file = self.processor.generate_parameters_file() + + # Assert the expected behaviour + expected_params = { + "param1": "value1", + "param2": "value2" + } + expected_json = json.dumps(expected_params, indent=4) + + # Assert the contents + self.assertEqual(parameters_file.file_content, expected_json) + # Assert name of the file includes + # (We want to know that in the instance of Azure Core ARM Templates, + # we are creating template parameters) + assert TEMPLATE_PARAMETERS_FILENAME in str(parameters_file.path) + + # Assert the type is LocalFileBuilder + self.assertIsInstance(parameters_file, LocalFileBuilder) + # Assert that the necessary methods were called + self.processor._generate_mapping_rule_profile.assert_called_once() + + def test_generate_mapping_rule_profile(self): + """ Test generate artifact profile returned correctly with generate_nf_application.""" + nf_application = self.processor.generate_nf_application() + mock_template_params = json.dumps({ + "subnetName": "{deployParameters.test-name.subnetName}", + "virtualNetworkId": "{deployParameters.test-name.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.test-name.sshPublicKeyAdmin}", + "imageName": "{deployParameters.test-name.imageName}" + }) + expected_arm_mapping_profile = ArmTemplateMappingRuleProfile( + template_parameters=mock_template_params) + expected_nexus_arm_mapping_profile = AzureCoreArmTemplateDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + template_mapping_rule_profile=expected_arm_mapping_profile, + ) + self.assertEqual(nf_application.deploy_parameters_mapping_rule_profile, + expected_nexus_arm_mapping_profile) + self.assertIsInstance(nf_application.deploy_parameters_mapping_rule_profile, + AzureCoreArmTemplateDeployMappingRuleProfile) + + def test_generate_artifact_profile(self): + """ Test generate artifact profile returned correctly with generate_nf_application.""" + + nf_application = self.processor.generate_nf_application() + expected_arm_artifact_profile = ArmTemplateArtifactProfile( + template_name="test-artifact-name", template_version="1.1.1") + expected_nexus_arm_artifact_profile = AzureCoreArmTemplateArtifactProfile( + artifact_store=ReferencedResource(id=""), + template_artifact_profile=expected_arm_artifact_profile + ) + self.assertEqual(nf_application.artifact_profile, expected_nexus_arm_artifact_profile) + self.assertIsInstance(nf_application.artifact_profile, AzureCoreArmTemplateArtifactProfile) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_helm_chart_processor.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_helm_chart_processor.py new file mode 100644 index 00000000000..f4a17fd247c --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_helm_chart_processor.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import os +from unittest import TestCase +from unittest.mock import Mock + +from azext_aosm.build_processors.helm_chart_processor import HelmChartProcessor + +code_directory = os.path.dirname(__file__) +parent_directory = os.path.abspath(os.path.join(code_directory, "../..")) +mock_cnf_directory = os.path.join(parent_directory, "mock_cnf") + +HELM_TEMPLATE_MOCK_OUTPUT_FILE = "nf-agent-cnf-helm_template_output.yaml" + + +class TestHelmChartProcessor(TestCase): + def setUp(self): + self.helm_chart_processor = HelmChartProcessor( + name="test-nf-agent-cnf", + input_artifact=Mock(), + registry_handler=None, + expose_all_params=False + ) + + def test_find_chart_images(self): + with open( + os.path.join(mock_cnf_directory, HELM_TEMPLATE_MOCK_OUTPUT_FILE), + "r", + encoding="utf-8", + ) as file: + helm_template_contents = file.read() + + self.helm_chart_processor.input_artifact.helm_template = helm_template_contents + self.helm_chart_processor.input_artifact.artifact_name = "test-nf-agent-cnf" + + # We want to test a specific private method so disable the pylint warning + # pylint: disable=protected-access + collected_images = self.helm_chart_processor._find_chart_images() + + # Assert the expected images are returned + expected_images = {("pez-nfagent", "879624")} + self.assertEqual(collected_images, expected_images) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_arm_processor.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_arm_processor.py new file mode 100644 index 00000000000..7792b7d0ff9 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_arm_processor.py @@ -0,0 +1,177 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import os +import logging +import sys +import json +from unittest import TestCase +from unittest.mock import MagicMock +from azext_aosm.build_processors.arm_processor import NexusArmBuildProcessor +from azext_aosm.inputs.arm_template_input import ArmTemplateInput +from azext_aosm.vendored_sdks.models import ( + AzureOperatorNexusNetworkFunctionArmTemplateApplication, + ApplicationEnablement, ArmResourceDefinitionResourceElementTemplate, + ArmResourceDefinitionResourceElementTemplateDetails, + ArmTemplateArtifactProfile, + ArmTemplateMappingRuleProfile, + NSDArtifactProfile, + TemplateType, + AzureOperatorNexusArmTemplateDeployMappingRuleProfile, + AzureOperatorNexusArmTemplateArtifactProfile, + DependsOnProfile, + ManifestArtifactFormat, + ReferencedResource, +) +from azext_aosm.definition_folder.builder.local_file_builder import LocalFileBuilder +from azext_aosm.common.artifact import LocalFileACRArtifact +from azext_aosm.common.constants import TEMPLATE_PARAMETERS_FILENAME + +code_directory = os.path.dirname(__file__) +parent_directory = os.path.abspath(os.path.join(code_directory, "../..")) +mock_vnf_directory = os.path.join(parent_directory, "mock_nexus_vnf") + + +class NexusArmProcessorTest(TestCase): + """Class to test Nexus ARM Processor functionality""" + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + mock_arm_template_path = os.path.join(mock_vnf_directory, "ubuntu-template.json") + self.nexus_arm_input = ArmTemplateInput( + artifact_name="test-artifact-name", + artifact_version="1.1.1", + template_path=mock_arm_template_path, + default_config=None + ) + self.processor = NexusArmBuildProcessor("test-name", self.nexus_arm_input, expose_all_params=False) + + def test_get_artifact_manifest_list(self): + """Test get artifact manifest list for nexus arm processor.""" + manifest_list = self.processor.get_artifact_manifest_list() + mock_manifest_artifact_format = ManifestArtifactFormat( + artifact_name="test-artifact-name", + artifact_type="ArmTemplate", + artifact_version="1.1.1", + ) + self.assertEqual(len(manifest_list), 1) + self.assertIsInstance(manifest_list[0], ManifestArtifactFormat) + self.assertEqual(manifest_list[0], mock_manifest_artifact_format) + assert True + + def test_artifact_details(self): + """Test get artifact details for nexus arm processor.""" + artifact_details = self.processor.get_artifact_details() + mock_arm_template_path = os.path.join(mock_vnf_directory, "ubuntu-template.json") + mock_artifact = [LocalFileACRArtifact( + artifact_name="test-artifact-name", + artifact_type="ArmTemplate", + artifact_version="1.1.1", + file_path=mock_arm_template_path, + )] + + # Ensure no list of LocalFileBuilders is returned, as this is only for NSDs + self.assertEqual(artifact_details[0][0].artifact_name, mock_artifact[0].artifact_name) + self.assertEqual(artifact_details[0][0].artifact_version, mock_artifact[0].artifact_version) + self.assertEqual(artifact_details[0][0].artifact_type, mock_artifact[0].artifact_type) + self.assertEqual(artifact_details[0][0].file_path, mock_artifact[0].file_path) + self.assertEqual(artifact_details[1], []) + assert True + + def test_generate_nf_application(self): + """Test generate nf application for nexus arm processor.""" + # Check type is correct, other functionality is tested in appropriate functions + # (such as test_generate_artifact_profile) + nf_application = self.processor.generate_nf_application() + self.assertIsInstance(nf_application, + AzureOperatorNexusNetworkFunctionArmTemplateApplication) + + def test_generate_resource_element_template(self): + """Test generate RET for Nexus ARM Processor""" + result = self.processor.generate_resource_element_template() + + # Assert the expected output + expected_template = ArmResourceDefinitionResourceElementTemplateDetails( + name="test-name", + depends_on_profile=DependsOnProfile( + install_depends_on=[], uninstall_depends_on=[], update_depends_on=[] + ), + configuration=ArmResourceDefinitionResourceElementTemplate( + template_type=TemplateType.ARM_TEMPLATE.value, + parameter_values=json.dumps({}), + artifact_profile=NSDArtifactProfile( + artifact_store_reference=ReferencedResource(id=""), + artifact_name=self.processor.input_artifact.artifact_name, + artifact_version=self.processor.input_artifact.artifact_version, + ), + ), + ) + # Assert each parameter equal + self.assertEqual(result.name, expected_template.name) + self.assertEqual(result.depends_on_profile, expected_template.depends_on_profile) + self.assertEqual(result.configuration.artifact_profile, + expected_template.configuration.artifact_profile) + + def test_generate_parameters_file(self): + """ Test generate parameters file for Nexus ARM Processor""" + + # Mock private function + # (generate mapping rule profile is tested elsewhere) + mapping_rule_profile = MagicMock() + mock_params = '{"param1": "value1", "param2": "value2"}' + mapping_rule_profile.template_mapping_rule_profile.template_parameters = mock_params + self.processor._generate_mapping_rule_profile = MagicMock(return_value=mapping_rule_profile) + + parameters_file = self.processor.generate_parameters_file() + + # Assert the expected behaviour + expected_params = { + "param1": "value1", + "param2": "value2" + } + expected_json = json.dumps(expected_params, indent=4) + + # Assert the contents + self.assertEqual(parameters_file.file_content, expected_json) + # Assert name of the file includes templateParameters + # (We want to know that in the instance of Nexus ARM Templates, + # we are creating template parameters) + assert TEMPLATE_PARAMETERS_FILENAME in str(parameters_file.path) + # Assert the type is LocalFileBuilder + self.assertIsInstance(parameters_file, LocalFileBuilder) + # Assert that the necessary methods were called + self.processor._generate_mapping_rule_profile.assert_called_once() + + def test_generate_mapping_rule_profile(self): + """ Test generate artifact profile returned correctly with generate_nf_application.""" + nf_application = self.processor.generate_nf_application() + mock_template_params = json.dumps({ + "subnetName": "{deployParameters.test-name.subnetName}", + "virtualNetworkId": "{deployParameters.test-name.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.test-name.sshPublicKeyAdmin}", + "imageName": "{deployParameters.test-name.imageName}" + }) + expected_arm_mapping_profile = ArmTemplateMappingRuleProfile( + template_parameters=mock_template_params) + expected_nexus_arm_mapping_profile = AzureOperatorNexusArmTemplateDeployMappingRuleProfile( + application_enablement=ApplicationEnablement.ENABLED, + template_mapping_rule_profile=expected_arm_mapping_profile, + ) + self.assertEqual(nf_application.deploy_parameters_mapping_rule_profile, + expected_nexus_arm_mapping_profile) + self.assertIsInstance(nf_application.deploy_parameters_mapping_rule_profile, + AzureOperatorNexusArmTemplateDeployMappingRuleProfile) + + def test_generate_artifact_profile(self): + """ Test generate artifact profile returned correctly with generate_nf_application.""" + + nf_application = self.processor.generate_nf_application() + expected_arm_artifact_profile = ArmTemplateArtifactProfile( + template_name="test-artifact-name", template_version="1.1.1") + expected_nexus_arm_artifact_profile = AzureOperatorNexusArmTemplateArtifactProfile( + artifact_store=ReferencedResource(id=""), + template_artifact_profile=expected_arm_artifact_profile + ) + self.assertEqual(nf_application.artifact_profile, expected_nexus_arm_artifact_profile) + self.assertIsInstance(nf_application.artifact_profile, + AzureOperatorNexusArmTemplateArtifactProfile) diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_image_processor.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_nexus_image_processor.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_vhd_processor.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_processors/test_vhd_processor.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_registry.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_registry.py new file mode 100644 index 00000000000..e017e66c998 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/unit_test/test_registry.py @@ -0,0 +1,227 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from unittest import TestCase +from unittest.mock import Mock, patch, mock_open +import logging +import sys +import json + +from azext_aosm.common.registry import ( + ContainerRegistry, + UniversalRegistry, + AzureContainerRegistry, + ContainerRegistryHandler, + REGISTRY_CLASS_TO_TYPE, +) +from knack.util import CLIError +from azure.cli.core.azclierror import ClientRequestError + + +class TestRegistry(TestCase): + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + self.registry = ContainerRegistry(registry_name="registry.example.com") + + def test_to_dict(self): + """Test the to_dict method.""" + registry_name = "registry.example.com" + registry = AzureContainerRegistry(registry_name=registry_name) + expected_output = { + "type": REGISTRY_CLASS_TO_TYPE[type(registry)], + "registry_name": registry_name, + } + output_dict = registry.to_dict() + self.assertEqual(output_dict, expected_output) + + def test_from_dict(self): + """Test the from_dict method.""" + + registry_dict = { + "type": "AzureContainerRegistry", + "registry_name": "registry.azurecr.io", + } + + registry = ContainerRegistry.from_dict(registry_dict) + + self.assertIsInstance(registry, AzureContainerRegistry) + self.assertEqual(registry.registry_name, "registry.azurecr.io") + + registry_dict = { + "type": "UniversalRegistry", + "registry_name": "registry.example.com", + } + + registry = ContainerRegistry.from_dict(registry_dict) + + self.assertIsInstance(registry, UniversalRegistry) + self.assertEqual(registry.registry_name, "registry.example.com") + + def test_from_dict_missing_field(self): + registry_dict = { + "type": "AzureContainerRegistry" + # Missing "registry_name" field + } + + with self.assertRaises(ValueError): + ContainerRegistry.from_dict(registry_dict) + + def test_add_namespace(self): + """Test the add_namespace method.""" + + namespace_to_add = "Test_namespace" + + self.registry.add_namespace(namespace_to_add) + + assert namespace_to_add in self.registry.registry_namespaces + + +class TestUniversalRegistry(TestCase): + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + self.registry = UniversalRegistry(registry_name="ghcr.io") + self.registry.add_namespace("") + + def test_find_image_existing_image(self): + image = "myimage" + version = "1.0.0" + + # Mock the call_subprocess_raise_output function + mocked_output = "some output" + mocked_call_subprocess_raise_output = Mock(return_value=mocked_output) + + with patch( + "azext_aosm.common.registry.call_subprocess_raise_output", + mocked_call_subprocess_raise_output, + ): + result = self.registry.find_image(image, version) + + self.assertEqual(result, (self.registry, "")) + + def test_find_image_cli_error(self): + image = "myimage" + version = "1.0.0" + + # Mock the call_subprocess_raise_output function to raise a CLIError + mocked_error = CLIError() + mocked_call_subprocess_raise_output = Mock(side_effect=mocked_error) + + with patch( + "azext_aosm.common.registry.call_subprocess_raise_output", + mocked_call_subprocess_raise_output, + ): + result = self.registry.find_image(image, version) + self.assertEqual(result, (None, None)) + + +class TestAzureContainerRegistry(TestCase): + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + self.registry = AzureContainerRegistry(registry_name="registry.azurecr.io") + self.registry.add_namespace("") + + def test_find_image_existing_image(self): + image = "myimage" + version = "1.0.0" + + # Mock the call_subprocess_raise_output function + mocked_output = "some output" + mocked_call_subprocess_raise_output = Mock(return_value=mocked_output) + + with patch( + "azext_aosm.common.registry.call_subprocess_raise_output", + mocked_call_subprocess_raise_output, + ): + result = self.registry.find_image(image, version) + + self.assertEqual(result, (self.registry, "")) + + def test_find_image_cli_error(self): + image = "myimage" + version = "1.0.0" + + # Mock the call_subprocess_raise_output function to raise a CLIError + mocked_error = CLIError() + mocked_call_subprocess_raise_output = Mock(side_effect=mocked_error) + + with patch( + "azext_aosm.common.registry.call_subprocess_raise_output", + mocked_call_subprocess_raise_output, + ): + result = self.registry.find_image(image, version) + self.assertEqual(result, (None, None)) + + +class TestRegistryHandler(TestCase): + def setUp(self): + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + + self.registry_name_1 = "registry.azurecr.io" + self.registry_name_2 = "registry.example.com/sample" + self.registry_name_3 = "registry.example.com" + + self.registry_handler = ContainerRegistryHandler( + image_sources=[ + self.registry_name_1, + self.registry_name_2, + self.registry_name_3, + ] + ) + + def test_create_registry_list(self): + registry_list = self.registry_handler.registry_list + + # There are two unique registries (registry.example.com and registry.azurecr.io) + self.assertEqual(len(registry_list), 2) + + registry_count = 0 + acr_registry_count = 0 + + for registry in registry_list: + self.assertIn(registry.registry_name, self.registry_handler.image_sources) + + if isinstance(registry, AzureContainerRegistry): + acr_registry_count += 1 + elif isinstance(registry, ContainerRegistry): + registry_count += 1 + else: + self.fail("Unexpected registry type") + + self.assertEqual(registry_count, 1) + self.assertEqual(acr_registry_count, 1) + + def test_find_registry_for_image(self): + # Create a mock object to replace the find_image method + mock_find_image_ACR = Mock() + mock_find_image_ACR.return_value = ( + AzureContainerRegistry(self.registry_name_1), + "", + ) + mock_find_image_universal_registry = Mock() + mock_find_image_universal_registry.return_value = ( + UniversalRegistry(self.registry_name_2), + "sample", + ) + with patch( + "azext_aosm.common.registry.AzureContainerRegistry.find_image", + mock_find_image_ACR, + ): + registry_1, namespace = self.registry_handler.find_registry_for_image( + "image1", "1.0.0" + ) + + self.assertEqual(registry_1.registry_name, self.registry_name_1) + self.assertEqual(namespace, "") + + with patch( + "azext_aosm.common.registry.UniversalRegistry.find_image", + mock_find_image_universal_registry, + ): + + registry_2, namespace = self.registry_handler.find_registry_for_image( + "image2", "1.0.0" + ) + + self.assertEqual(registry_2.registry_name, self.registry_name_2) + self.assertEqual(namespace, "sample") diff --git a/src/aosm/azext_aosm/tests/latest/unit_test/test_utils.py b/src/aosm/azext_aosm/tests/latest/unit_test/test_utils.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/templateParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/templateParameters.json new file mode 100644 index 00000000000..35398722d4a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/templateParameters.json @@ -0,0 +1,8 @@ +{ + "location": "{deployParameters.location}", + "subnetName": "{deployParameters.subnetName}", + "ubuntuVmName": "{deployParameters.ubuntuVmName}", + "virtualNetworkId": "{deployParameters.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.sshPublicKeyAdmin}", + "imageName": "ubuntu-vmImage" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json new file mode 100644 index 00000000000..27c48c5e970 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json @@ -0,0 +1,3 @@ +{ + "imageName": "ubuntu-vmImage" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/deployParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/deployParameters.json new file mode 100644 index 00000000000..0a9a1aecaf6 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/deployParameters.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string" + }, + "virtualNetworkId": { + "type": "string" + }, + "sshPublicKeyAdmin": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt new file mode 100644 index 00000000000..38f13eac5d5 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt @@ -0,0 +1,13 @@ +# The following parameters are optional as they have default values. +# If you do not wish to expose them in the NFD, find and remove them from both +# deploymentParameters.json and templateParameters.json (and vhdParameters.json if +they are there) + +{ + "location": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/generate_nfd/templates/vnfartifactmanifests.bicep b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep similarity index 100% rename from src/aosm/azext_aosm/generate_nfd/templates/vnfartifactmanifests.bicep rename to src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep diff --git a/src/aosm/azext_aosm/generate_nfd/templates/vnfdefinition.bicep b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/vnfdefinition.bicep similarity index 97% rename from src/aosm/azext_aosm/generate_nfd/templates/vnfdefinition.bicep rename to src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/vnfdefinition.bicep index 9deaeffd182..188183918df 100644 --- a/src/aosm/azext_aosm/generate_nfd/templates/vnfdefinition.bicep +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_filepath/nfd-bicep-ubuntu-template/vnfdefinition.bicep @@ -50,7 +50,7 @@ resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroup properties: { // versionState should be changed to 'Active' once it is finalized. versionState: 'Preview' - deployParameters: string(loadJsonContent('schemas/deploymentParameters.json')) + deployParameters: string(loadJsonContent('schemas/deployParameters.json')) networkFunctionType: 'VirtualNetworkFunction' networkFunctionTemplate: { nfviType: 'AzureCore' diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/templateParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/templateParameters.json new file mode 100644 index 00000000000..35398722d4a --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/templateParameters.json @@ -0,0 +1,8 @@ +{ + "location": "{deployParameters.location}", + "subnetName": "{deployParameters.subnetName}", + "ubuntuVmName": "{deployParameters.ubuntuVmName}", + "virtualNetworkId": "{deployParameters.virtualNetworkId}", + "sshPublicKeyAdmin": "{deployParameters.sshPublicKeyAdmin}", + "imageName": "ubuntu-vmImage" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json new file mode 100644 index 00000000000..c633de114ad --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/configMappings/vhdParameters.json @@ -0,0 +1,6 @@ +{ + "imageName": "ubuntu-vmImage", + "imageDiskSizeGB": 30, + "imageHyperVGeneration": "V1", + "imageApiVersion": "2023-03-01" +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/deployParameters.json b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/deployParameters.json new file mode 100644 index 00000000000..0a9a1aecaf6 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/deployParameters.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "DeployParametersSchema", + "type": "object", + "properties": { + "location": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string" + }, + "virtualNetworkId": { + "type": "string" + }, + "sshPublicKeyAdmin": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt new file mode 100644 index 00000000000..38f13eac5d5 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/schemas/optionalDeploymentParameters.txt @@ -0,0 +1,13 @@ +# The following parameters are optional as they have default values. +# If you do not wish to expose them in the NFD, find and remove them from both +# deploymentParameters.json and templateParameters.json (and vhdParameters.json if +they are there) + +{ + "location": { + "type": "string" + }, + "ubuntuVmName": { + "type": "string" + } +} \ No newline at end of file diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep new file mode 100644 index 00000000000..109cca9c766 --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfartifactmanifests.bicep @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an NF definition for a VNF +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of an existing Storage Account-backed Artifact Store, deployed under the publisher.') +param saArtifactStoreName string +@description('Name of the manifest to deploy for the ACR-backed Artifact Store') +param acrManifestName string +@description('Name of the manifest to deploy for the Storage Account-backed Artifact Store') +param saManifestName string +@description('Name of Network Function. Used predominantly as a prefix for other variable names') +param nfName string +@description('The version that you want to name the NFM VHD artifact, in format A-B-C. e.g. 6-13-0') +param vhdVersion string +@description('The name under which to store the ARM template') +param armTemplateVersion string + +// Created by the az aosm definition publish command before the template is deployed +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName + scope: resourceGroup() +} + +// Created by the az aosm definition publish command before the template is deployed +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +// Created by the az aosm definition publish command before the template is deployed +resource saArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: saArtifactStoreName +} + +resource saArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: saArtifactStore + name: saManifestName + location: location + properties: { + artifacts: [ + { + artifactName: '${nfName}-vhd' + artifactType: 'VhdImageFile' + artifactVersion: vhdVersion + } + ] + } +} + +resource acrArtifactManifest 'Microsoft.Hybridnetwork/publishers/artifactStores/artifactManifests@2023-09-01' = { + parent: acrArtifactStore + name: acrManifestName + location: location + properties: { + artifacts: [ + { + artifactName: '${nfName}-arm-template' + artifactType: 'ArmTemplate' + artifactVersion: armTemplateVersion + } + ] + } +} diff --git a/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfdefinition.bicep b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfdefinition.bicep new file mode 100644 index 00000000000..188183918df --- /dev/null +++ b/src/aosm/azext_aosm/tests/latest/vnf_output/ubuntu_with_sas_token/nfd-bicep-ubuntu-template/vnfdefinition.bicep @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. + +// This file creates an NF definition for a VNF +param location string +@description('Name of an existing publisher, expected to be in the resource group where you deploy the template') +param publisherName string +@description('Name of an existing ACR-backed Artifact Store, deployed under the publisher.') +param acrArtifactStoreName string +@description('Name of an existing Storage Account-backed Artifact Store, deployed under the publisher.') +param saArtifactStoreName string +@description('Name of Network Function. Used predominantly as a prefix for other variable names') +param nfName string +@description('Name of an existing Network Function Definition Group') +param nfDefinitionGroup string +@description('The version of the NFDV you want to deploy, in format A.B.C') +param nfDefinitionVersion string +@description('The version that you want to name the NFM VHD artifact, in format A-B-C. e.g. 6-13-0') +param vhdVersion string +@description('The version that you want to name the NFM template artifact, in format A.B.C. e.g. 6.13.0. If testing for development, you can use any numbers you like.') +param armTemplateVersion string + +// Created by the az aosm definition publish command before the template is deployed +resource publisher 'Microsoft.HybridNetwork/publishers@2023-09-01' existing = { + name: publisherName + scope: resourceGroup() +} + +// Created by the az aosm definition publish command before the template is deployed +resource acrArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: acrArtifactStoreName +} + +// Created by the az aosm definition publish command before the template is deployed +resource saArtifactStore 'Microsoft.HybridNetwork/publishers/artifactStores@2023-09-01' existing = { + parent: publisher + name: saArtifactStoreName +} + +// Created by the az aosm definition publish command before the template is deployed +resource nfdg 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups@2023-09-01' existing = { + parent: publisher + name: nfDefinitionGroup +} + +resource nfdv 'Microsoft.Hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions@2023-09-01' = { + parent: nfdg + name: nfDefinitionVersion + location: location + properties: { + // versionState should be changed to 'Active' once it is finalized. + versionState: 'Preview' + deployParameters: string(loadJsonContent('schemas/deployParameters.json')) + networkFunctionType: 'VirtualNetworkFunction' + networkFunctionTemplate: { + nfviType: 'AzureCore' + networkFunctionApplications: [ + { + artifactType: 'VhdImageFile' + name: '${nfName}Image' + dependsOnProfile: null + artifactProfile: { + vhdArtifactProfile: { + vhdName: '${nfName}-vhd' + vhdVersion: vhdVersion + } + artifactStore: { + id: saArtifactStore.id + } + } + // mapping deploy param vals to vals required by this network function application object + deployParametersMappingRuleProfile: { + vhdImageMappingRuleProfile: { + userConfiguration: string(loadJsonContent('configMappings/vhdParameters.json')) + } + // ?? + applicationEnablement: 'Unknown' + } + } + { + artifactType: 'ArmTemplate' + name: nfName + dependsOnProfile: null + artifactProfile: { + templateArtifactProfile: { + templateName: '${nfName}-arm-template' + templateVersion: armTemplateVersion + } + artifactStore: { + id: acrArtifactStore.id + } + } + deployParametersMappingRuleProfile: { + templateMappingRuleProfile: { + templateParameters: string(loadJsonContent('configMappings/templateParameters.json')) + } + applicationEnablement: 'Unknown' + } + } + ] + } + } +} diff --git a/src/aosm/azext_aosm/util/management_clients.py b/src/aosm/azext_aosm/util/management_clients.py deleted file mode 100644 index 936e0d16ec7..00000000000 --- a/src/aosm/azext_aosm/util/management_clients.py +++ /dev/null @@ -1,22 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Clients for the python SDK along with useful caches.""" - -from dataclasses import dataclass - -from azure.mgmt.resource import ResourceManagementClient -from knack.log import get_logger - -from azext_aosm.vendored_sdks import HybridNetworkManagementClient - -logger = get_logger(__name__) - - -@dataclass -class ApiClients: - """A class for API Clients needed throughout.""" - - aosm_client: HybridNetworkManagementClient - resource_client: ResourceManagementClient diff --git a/src/aosm/azext_aosm/util/utils.py b/src/aosm/azext_aosm/util/utils.py deleted file mode 100644 index 91d144a764c..00000000000 --- a/src/aosm/azext_aosm/util/utils.py +++ /dev/null @@ -1,25 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -"""Utility functions.""" - - -def input_ack(ack: str, request_to_user: str) -> bool: - """ - Overarching function to request, sanitise and return True if input is specified ack. - - This prints the question string and asks for user input. which is santised by - removing all whitespaces in the string, and made lowercase. True is returned if the - user input is equal to supplied acknowledgement string and False if anything else - """ - unsanitised_ans = input(request_to_user) - return str(unsanitised_ans.strip().replace(" ", "").lower()) == ack - - -def snake_case_to_camel_case(text): - """Converts snake case to camel case.""" - components = text.split("_") - return components[0] + "".join( - x[0].upper() + x[1:] for x in components[1:] - ) diff --git a/src/aosm/setup.py b/src/aosm/setup.py index 62b8716f65b..306cde47f6f 100644 --- a/src/aosm/setup.py +++ b/src/aosm/setup.py @@ -5,6 +5,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from codecs import open + from setuptools import find_packages, setup try: @@ -16,7 +17,7 @@ # Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = "1.0.0b4" +VERSION = "2.0.0b1" # The full list of classifiers is available at @@ -32,7 +33,12 @@ "License :: OSI Approved :: MIT License", ] -DEPENDENCIES = ["oras~=0.1.19", "jinja2>=3.1.2"] +DEPENDENCIES = [ + "oras~=0.1.19", + "jinja2>=3.1.4", + "genson>=1.2.2", + "ruamel.yaml>=0.17.4", +] with open("README.md", "r", encoding="utf-8") as f: README = f.read() @@ -54,8 +60,7 @@ package_data={ "azext_aosm": [ "azext_metadata.json", - "generate_nfd/templates/*", - "generate_nsd/templates/*", + "common/templates/**", ] }, )