azureml-registry-tools 0.1.0a6__py3-none-any.whl → 0.1.0a8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- azureml/registry/_cli/registry_syndication_cli.py +84 -0
- azureml/registry/data/__init__.py +4 -0
- azureml/registry/data/asset.yaml.template +4 -0
- azureml/registry/data/description.md.template +3 -0
- azureml/registry/data/evaluation.md.template +3 -0
- azureml/registry/data/model-variant.schema.json +61 -0
- azureml/registry/data/model.schema.json +597 -0
- azureml/registry/data/model.yaml.template +8 -0
- azureml/registry/data/notes.md.template +3 -0
- azureml/registry/data/validate_model_schema.py +119 -0
- azureml/registry/data/validate_model_variant_schema.py +85 -0
- azureml/registry/mgmt/asset_management.py +260 -0
- azureml/registry/mgmt/create_asset_template.py +87 -0
- azureml/registry/mgmt/create_model_spec.py +239 -0
- azureml/registry/mgmt/registry_config.py +147 -0
- {azureml_registry_tools-0.1.0a6.dist-info → azureml_registry_tools-0.1.0a8.dist-info}/METADATA +1 -1
- azureml_registry_tools-0.1.0a8.dist-info/RECORD +37 -0
- azureml_registry_tools-0.1.0a6.dist-info/RECORD +0 -23
- {azureml_registry_tools-0.1.0a6.dist-info → azureml_registry_tools-0.1.0a8.dist-info}/WHEEL +0 -0
- {azureml_registry_tools-0.1.0a6.dist-info → azureml_registry_tools-0.1.0a8.dist-info}/entry_points.txt +0 -0
- {azureml_registry_tools-0.1.0a6.dist-info → azureml_registry_tools-0.1.0a8.dist-info}/licenses/LICENSE.txt +0 -0
- {azureml_registry_tools-0.1.0a6.dist-info → azureml_registry_tools-0.1.0a8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
# ---------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
3
|
+
# ---------------------------------------------------------
|
4
|
+
|
5
|
+
"""Validate model variant schema."""
|
6
|
+
|
7
|
+
import argparse
|
8
|
+
import yaml
|
9
|
+
import sys
|
10
|
+
import jsonschema
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import List
|
13
|
+
|
14
|
+
import azureml.assets as assets
|
15
|
+
import azureml.assets.util as util
|
16
|
+
from azureml.assets.util import logger
|
17
|
+
|
18
|
+
|
19
|
+
def validate_model_schema(input_dirs: List[Path],
|
20
|
+
schema_file: Path,
|
21
|
+
asset_config_filename: str) -> bool:
|
22
|
+
"""Validate model variant schema.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
input_dirs (List[Path]): Directories containing assets.
|
26
|
+
schema_file (Path): File containing model variant schema.
|
27
|
+
asset_config_filename (str): Asset config filename to search for.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
bool: True on success.
|
31
|
+
"""
|
32
|
+
# Load model schema from file
|
33
|
+
loaded_schema = {}
|
34
|
+
with open(schema_file, 'r') as file:
|
35
|
+
loaded_schema = yaml.safe_load(file)
|
36
|
+
|
37
|
+
# Create validator instance for collecting all errors
|
38
|
+
validator = jsonschema.Draft7Validator(loaded_schema)
|
39
|
+
|
40
|
+
asset_count = 0
|
41
|
+
model_count = 0
|
42
|
+
error_count = 0
|
43
|
+
for input_dir in input_dirs:
|
44
|
+
# Recursively find all files with the name matching asset_config_filename
|
45
|
+
for asset_config in util.find_assets(input_dir, asset_config_filename):
|
46
|
+
asset_count += 1
|
47
|
+
file_path = asset_config.spec_with_path
|
48
|
+
if asset_config.type == assets.AssetType.MODEL:
|
49
|
+
model_count += 1
|
50
|
+
# Validate the file against the schema
|
51
|
+
try:
|
52
|
+
with open(file_path, "r") as f:
|
53
|
+
spec_config = yaml.safe_load(f)
|
54
|
+
|
55
|
+
# Collect all validation errors
|
56
|
+
errors = list(validator.iter_errors(spec_config))
|
57
|
+
|
58
|
+
if not errors:
|
59
|
+
logger.print(f"{file_path} is valid.")
|
60
|
+
else:
|
61
|
+
logger.log_error(f"\n‼️{file_path} has {len(errors)} validation error(s):")
|
62
|
+
|
63
|
+
for e in errors:
|
64
|
+
# Get detailed error information for each error
|
65
|
+
error_path = '.'.join(str(p) for p in e.path) if e.path else "root"
|
66
|
+
line_info = ""
|
67
|
+
|
68
|
+
# Get line number from jsonschema error if available
|
69
|
+
if hasattr(e, 'lineno') and e.lineno is not None:
|
70
|
+
line_info = f" at line {e.lineno}"
|
71
|
+
else:
|
72
|
+
# Try to find line number by looking at the path and instance
|
73
|
+
try:
|
74
|
+
with open(file_path, "r") as f:
|
75
|
+
yaml_content = f.readlines()
|
76
|
+
yaml_lines = []
|
77
|
+
for idx, line in enumerate(yaml_content):
|
78
|
+
if error_path in line:
|
79
|
+
yaml_lines.append(f"line {idx+1}: {line.strip()}")
|
80
|
+
if yaml_lines:
|
81
|
+
line_info = "\nPossible location(s):\n " + "\n ".join(yaml_lines)
|
82
|
+
except Exception:
|
83
|
+
pass
|
84
|
+
|
85
|
+
schema_path = '.'.join(str(p) for p in e.schema_path)
|
86
|
+
logger.print(f"⚠️ {file_path} is invalid at path '{error_path}'{line_info}:")
|
87
|
+
logger.print(f" Error: {e.message}")
|
88
|
+
logger.print(f" Instance: {e.instance}")
|
89
|
+
logger.print(f" Schema path: {schema_path}")
|
90
|
+
error_count += 1
|
91
|
+
except Exception as e:
|
92
|
+
logger.log_error(f"Error processing {file_path}: {str(e)}")
|
93
|
+
error_count += 1
|
94
|
+
|
95
|
+
logger.print(f"Found {asset_count} total asset(s).")
|
96
|
+
logger.print(f"Found {error_count} model(s) with error(s) out of {model_count} total model(s)")
|
97
|
+
return error_count == 0
|
98
|
+
|
99
|
+
|
100
|
+
if __name__ == "__main__":
|
101
|
+
# Handle command-line args
|
102
|
+
parser = argparse.ArgumentParser()
|
103
|
+
parser.add_argument("-i", "--input-dirs", required=True,
|
104
|
+
help="Comma-separated list of directories containing assets")
|
105
|
+
parser.add_argument("-m", "--schema-file", default=Path(__file__).parent / "model.schema.json", type=Path, help="Model Schema file")
|
106
|
+
parser.add_argument("-a", "--asset-config-filename", default=assets.DEFAULT_ASSET_FILENAME,
|
107
|
+
help="Asset config file name to search for")
|
108
|
+
args = parser.parse_args()
|
109
|
+
|
110
|
+
# Convert comma-separated values to lists
|
111
|
+
input_dirs = [Path(d) for d in args.input_dirs.split(",")]
|
112
|
+
|
113
|
+
# Validate against model schema
|
114
|
+
success = validate_model_schema(input_dirs=input_dirs,
|
115
|
+
schema_file=args.schema_file,
|
116
|
+
asset_config_filename=args.asset_config_filename)
|
117
|
+
|
118
|
+
if not success:
|
119
|
+
sys.exit(1)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# ---------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
3
|
+
# ---------------------------------------------------------
|
4
|
+
|
5
|
+
"""Validate model variant schema."""
|
6
|
+
|
7
|
+
import argparse
|
8
|
+
import yaml
|
9
|
+
import sys
|
10
|
+
import jsonschema
|
11
|
+
from jsonschema import validate
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import List
|
14
|
+
|
15
|
+
import azureml.assets as assets
|
16
|
+
import azureml.assets.util as util
|
17
|
+
from azureml.assets.util import logger
|
18
|
+
|
19
|
+
|
20
|
+
def validate_model_variant_schema(input_dirs: List[Path],
|
21
|
+
model_variant_schema_file: Path,
|
22
|
+
asset_config_filename: str) -> bool:
|
23
|
+
"""Validate model variant schema.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
input_dirs (List[Path]): Directories containing assets.
|
27
|
+
model_variant_schema_file (Path): File containing model variant schema.
|
28
|
+
asset_config_filename (str): Asset config filename to search for.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
bool: True on success.
|
32
|
+
"""
|
33
|
+
# Load variantInfo schema from file
|
34
|
+
model_variant_info_schema = {}
|
35
|
+
with open(model_variant_schema_file, 'r') as file:
|
36
|
+
model_variant_info_schema = yaml.safe_load(file)
|
37
|
+
|
38
|
+
asset_count = 0
|
39
|
+
model_count = 0
|
40
|
+
error_count = 0
|
41
|
+
for input_dir in input_dirs:
|
42
|
+
for asset_config in util.find_assets(input_dir, asset_config_filename):
|
43
|
+
asset_count += 1
|
44
|
+
if asset_config.type == assets.AssetType.MODEL:
|
45
|
+
model_count += 1
|
46
|
+
# Extract model variant info from spec
|
47
|
+
variant_info = None
|
48
|
+
with open(asset_config.spec_with_path, "r") as f:
|
49
|
+
spec_config = yaml.safe_load(f)
|
50
|
+
variant_info = spec_config.get("variantInfo")
|
51
|
+
|
52
|
+
if variant_info is not None:
|
53
|
+
logger.print(f"Found variantInfo in spec {asset_config.spec_with_path}. "
|
54
|
+
f"Validating variantInfo against schema: {variant_info}")
|
55
|
+
# Validate data
|
56
|
+
try:
|
57
|
+
validate(instance=variant_info, schema=model_variant_info_schema)
|
58
|
+
logger.print("variantInfo is valid.")
|
59
|
+
except jsonschema.exceptions.ValidationError as e:
|
60
|
+
logger.log_error(f"variantInfo is invalid for {asset_config.spec_with_path}: {e.message}")
|
61
|
+
error_count += 1
|
62
|
+
|
63
|
+
logger.print(f"Found {asset_count} total asset(s).")
|
64
|
+
logger.print(f"Found {error_count} model(s) with error(s) out of {model_count} total model(s)")
|
65
|
+
return error_count == 0
|
66
|
+
|
67
|
+
|
68
|
+
if __name__ == "__main__":
|
69
|
+
# Handle command-line args
|
70
|
+
parser = argparse.ArgumentParser()
|
71
|
+
parser.add_argument("-i", "--input-dirs", required=True, help="Comma-separated list of directories containing assets")
|
72
|
+
parser.add_argument("-m", "--model-variant-schema-file", default=Path(__file__).parent / "model-variant.schema.json", type=Path, help="Model Variant Schema file")
|
73
|
+
parser.add_argument("-a", "--asset-config-filename", default=assets.DEFAULT_ASSET_FILENAME, help="Asset config file name to search for")
|
74
|
+
args = parser.parse_args()
|
75
|
+
|
76
|
+
# Convert comma-separated values to lists
|
77
|
+
input_dirs = [Path(d) for d in args.input_dirs.split(",")]
|
78
|
+
|
79
|
+
# Validate variantInfo against model variant schema
|
80
|
+
success = validate_model_variant_schema(input_dirs=input_dirs,
|
81
|
+
model_variant_schema_file=args.model_variant_schema_file,
|
82
|
+
asset_config_filename=args.asset_config_filename)
|
83
|
+
|
84
|
+
if not success:
|
85
|
+
sys.exit(1)
|
@@ -0,0 +1,260 @@
|
|
1
|
+
# ---------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
3
|
+
# ---------------------------------------------------------
|
4
|
+
"""Asset management commands for registry-mgmt CLI."""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import shutil
|
8
|
+
import tempfile
|
9
|
+
import yaml
|
10
|
+
from datetime import datetime, timedelta
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import List
|
13
|
+
|
14
|
+
from azure.ai.ml import MLClient
|
15
|
+
from azure.identity import DefaultAzureCredential
|
16
|
+
|
17
|
+
from azureml.registry.data.validate_model_schema import validate_model_schema
|
18
|
+
from azureml.registry.data.validate_model_variant_schema import validate_model_variant_schema
|
19
|
+
from azureml.registry.mgmt.registry_config import RegistryConfig
|
20
|
+
|
21
|
+
# Windows compatibility patch - must be applied before importing azureml.assets
|
22
|
+
from subprocess import run
|
23
|
+
|
24
|
+
|
25
|
+
def patched_run_command(cmd: List[str]):
|
26
|
+
"""Run command with shell=True for Windows compatibility."""
|
27
|
+
result = run(cmd, capture_output=True, encoding=sys.stdout.encoding, errors="ignore", shell=True)
|
28
|
+
return result
|
29
|
+
|
30
|
+
|
31
|
+
# Apply patch before importing azureml.assets
|
32
|
+
import azureml.assets.publish_utils as publish_utils # noqa: E402
|
33
|
+
publish_utils.run_command = patched_run_command
|
34
|
+
|
35
|
+
import azureml.assets as assets # noqa: E402
|
36
|
+
import azureml.assets.util as util # noqa: E402
|
37
|
+
from azureml.assets.config import AssetConfig, AssetType, AzureBlobstoreAssetPath # noqa: E402
|
38
|
+
from azureml.assets.publish_utils import create_asset # noqa: E402
|
39
|
+
from azureml.assets.validate_assets import validate_assets # noqa: E402
|
40
|
+
|
41
|
+
|
42
|
+
def validate_model(asset_path: Path) -> bool:
|
43
|
+
"""Validate model.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
asset_path (Path): Path to the asset folder to validate
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
bool: True if validation passes, False otherwise
|
50
|
+
"""
|
51
|
+
errors = 0
|
52
|
+
|
53
|
+
print("⚙️ [VALIDATION #1]: Validate assets...")
|
54
|
+
if not validate_assets(asset_path, assets.DEFAULT_ASSET_FILENAME):
|
55
|
+
print("❌ [FAILED] Validation #1: validate_assets\n\n")
|
56
|
+
errors += 1
|
57
|
+
else:
|
58
|
+
print("✅ [PASSED] Validation #1: validate_assets passed\n")
|
59
|
+
|
60
|
+
# Model variant schema validation
|
61
|
+
model_variant_schema_file = Path(__file__).parent.parent / "data" / "model-variant.schema.json"
|
62
|
+
|
63
|
+
print("⚙️ [VALIDATION #2]: Validating model variant schema...")
|
64
|
+
if not validate_model_variant_schema(input_dirs=[asset_path], model_variant_schema_file=model_variant_schema_file,
|
65
|
+
asset_config_filename=assets.DEFAULT_ASSET_FILENAME):
|
66
|
+
print("❌ [FAILED] Validation #2: validate_model_variant_schema\n")
|
67
|
+
errors += 1
|
68
|
+
else:
|
69
|
+
print("✅ [PASSED] Validation #2: validate_model_variant_schema passed\n")
|
70
|
+
|
71
|
+
# Model schema validation
|
72
|
+
model_schema_file = Path(__file__).parent.parent / "data" / "model.schema.json"
|
73
|
+
|
74
|
+
print("⚙️ [VALIDATION #3]: Validating model schema...")
|
75
|
+
if not validate_model_schema(input_dirs=[asset_path], schema_file=model_schema_file,
|
76
|
+
asset_config_filename=assets.DEFAULT_ASSET_FILENAME):
|
77
|
+
print("❌ [FAILED] Validation #3: validate_model_schema\n")
|
78
|
+
errors += 1
|
79
|
+
else:
|
80
|
+
print("✅ [PASSED] Validation #3: validate_model_schema passed\n")
|
81
|
+
|
82
|
+
if errors != 0:
|
83
|
+
return False
|
84
|
+
|
85
|
+
print("🎉 [VALIDATION COMPLETE] All validations passed!\n")
|
86
|
+
return True
|
87
|
+
|
88
|
+
|
89
|
+
def set_storage_and_sas(asset: AssetConfig, storage_config: dict):
|
90
|
+
"""Use storage configuration and generate/set SAS token.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
asset (AssetConfig): Asset configuration object to modify
|
94
|
+
storage_config (dict): Storage configuration dictionary
|
95
|
+
"""
|
96
|
+
if not storage_config:
|
97
|
+
# No storage overrides provided, skip storage configuration
|
98
|
+
return
|
99
|
+
|
100
|
+
print("Overriding storage configuration with provided values...")
|
101
|
+
extra_config = asset.extra_config_as_object()
|
102
|
+
extra_config._path = AzureBlobstoreAssetPath(
|
103
|
+
storage_name=storage_config["storage_name"],
|
104
|
+
container_name=storage_config["container_name"],
|
105
|
+
container_path=storage_config["container_path"]
|
106
|
+
)
|
107
|
+
_ = extra_config.path.get_uri(token_expiration=timedelta(hours=1))
|
108
|
+
|
109
|
+
|
110
|
+
def build_mutable_asset(base_asset: AssetConfig, mutable_asset_dir: str) -> AssetConfig:
|
111
|
+
"""Build a mutable copy of the asset in a temporary directory.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
base_asset (AssetConfig): Base asset configuration to copy
|
115
|
+
mutable_asset_dir (str): Directory path for the mutable asset copy
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
AssetConfig: Mutable asset configuration object
|
119
|
+
"""
|
120
|
+
common_dir, _ = util.find_common_directory(base_asset.release_paths)
|
121
|
+
|
122
|
+
# Convert string paths to Path objects and ensure they're absolute
|
123
|
+
common_dir = Path(common_dir).resolve()
|
124
|
+
mutable_asset_dir = Path(mutable_asset_dir).resolve()
|
125
|
+
base_asset_file = base_asset.file_name_with_path.resolve()
|
126
|
+
base_spec_file = base_asset.spec_with_path.resolve()
|
127
|
+
|
128
|
+
shutil.copytree(common_dir, mutable_asset_dir, dirs_exist_ok=True)
|
129
|
+
|
130
|
+
# Reference asset files in mutable directory
|
131
|
+
asset_config_file = mutable_asset_dir / base_asset_file.relative_to(common_dir)
|
132
|
+
spec_config_file = mutable_asset_dir / base_spec_file.relative_to(common_dir)
|
133
|
+
|
134
|
+
# Autoincrement version for mutable asset
|
135
|
+
with open(spec_config_file, "r") as f:
|
136
|
+
spec_config = yaml.safe_load(f)
|
137
|
+
spec_config["version"] = datetime.now().strftime("%Y%m%d%H%M%S")
|
138
|
+
|
139
|
+
with open(spec_config_file, "w") as f:
|
140
|
+
yaml.dump(spec_config, f)
|
141
|
+
|
142
|
+
mutable_asset = AssetConfig(asset_config_file)
|
143
|
+
|
144
|
+
return mutable_asset
|
145
|
+
|
146
|
+
|
147
|
+
def create_or_update_asset(readonly_asset: AssetConfig, config: RegistryConfig):
|
148
|
+
"""Create or update an asset in the AzureML registry.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
readonly_asset (AssetConfig): Asset configuration to create or update
|
152
|
+
config (RegistryConfig): Registry configuration settings
|
153
|
+
"""
|
154
|
+
print("[CREATING/UPDATING ASSET]")
|
155
|
+
print(f"Using registry configuration from: {config.config_path}")
|
156
|
+
# Create ML client
|
157
|
+
ml_client = MLClient(
|
158
|
+
subscription_id=config.subscription_id,
|
159
|
+
resource_group_name=config.resource_group,
|
160
|
+
registry_name=config.registry_name,
|
161
|
+
credential=DefaultAzureCredential(),
|
162
|
+
)
|
163
|
+
|
164
|
+
with tempfile.TemporaryDirectory() as mutable_asset_dir:
|
165
|
+
mutable_asset = build_mutable_asset(base_asset=readonly_asset, mutable_asset_dir=mutable_asset_dir)
|
166
|
+
# autoincrement version
|
167
|
+
try:
|
168
|
+
set_storage_and_sas(mutable_asset, config.storage_config)
|
169
|
+
success = create_asset(mutable_asset, config.registry_name, ml_client)
|
170
|
+
except Exception as e:
|
171
|
+
print(f"Failed to create/update asset: {e}")
|
172
|
+
raise
|
173
|
+
|
174
|
+
if not success:
|
175
|
+
print(f"Failed to create/update asset: create_asset 'success' returned {success}")
|
176
|
+
raise
|
177
|
+
|
178
|
+
print("\n[VALIDATE YOUR ASSET IN THE UI HERE]")
|
179
|
+
print(f" - Model Catalog link: https://ai.azure.com/explore/models/{mutable_asset.name}/version/{mutable_asset.version}/registry/{config.registry_name}?tid={config.tenant_id}")
|
180
|
+
print(f" - Azure Portal link: https://ml.azure.com/registries/{config.registry_name}/models/{mutable_asset.name}/version/{mutable_asset.version}?tid={config.tenant_id}")
|
181
|
+
|
182
|
+
|
183
|
+
def asset_validate(asset_path: Path, dry_run: bool = False) -> bool:
|
184
|
+
"""Validate an asset at the specified path.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
asset_path (Path): Path to the asset folder to validate
|
188
|
+
dry_run (bool): If True, perform a dry run without side effects
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
bool: True if validation passes, False otherwise
|
192
|
+
"""
|
193
|
+
if dry_run:
|
194
|
+
print(f"[DRY RUN] Would validate asset at: {asset_path}")
|
195
|
+
return True
|
196
|
+
|
197
|
+
asset_path = asset_path.resolve()
|
198
|
+
print(f"[VALIDATION] Begin validating for asset at: {asset_path}...")
|
199
|
+
|
200
|
+
# Check if asset path exists
|
201
|
+
if not asset_path.exists():
|
202
|
+
print(f"❌ [ERROR]: Asset path {asset_path} does not exist")
|
203
|
+
return False
|
204
|
+
|
205
|
+
# Check for exactly one asset
|
206
|
+
asset_count = len(util.find_assets([asset_path], assets.DEFAULT_ASSET_FILENAME))
|
207
|
+
if asset_count != 1:
|
208
|
+
print(f"❌ [ERROR]: Expected exactly one asset in {asset_path}, found {asset_count}")
|
209
|
+
return False
|
210
|
+
|
211
|
+
# Load asset configuration
|
212
|
+
readonly_asset = assets.AssetConfig(asset_path / assets.DEFAULT_ASSET_FILENAME)
|
213
|
+
|
214
|
+
# Check asset type
|
215
|
+
if readonly_asset.type != AssetType.MODEL:
|
216
|
+
print(f"❌ [ERROR]: Asset type {readonly_asset.type} is not supported for validation. "
|
217
|
+
f"Only models are currently supported.")
|
218
|
+
return False
|
219
|
+
|
220
|
+
# Perform validation
|
221
|
+
return validate_model(readonly_asset.file_path)
|
222
|
+
|
223
|
+
|
224
|
+
def asset_deploy(asset_path: Path, config_path: Path, dry_run: bool = False) -> bool:
|
225
|
+
"""Deploy an asset using configuration file.
|
226
|
+
|
227
|
+
Args:
|
228
|
+
asset_path (Path): Path to the asset folder to deploy
|
229
|
+
config_path (Path): Path to configuration file
|
230
|
+
dry_run (bool): If True, perform a dry run without deploying
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
bool: True if deployment succeeds, False otherwise
|
234
|
+
"""
|
235
|
+
try:
|
236
|
+
config = RegistryConfig(config_path)
|
237
|
+
except Exception as e:
|
238
|
+
print(f"❌ [ERROR]: Configuration validation failed: {e}")
|
239
|
+
return False
|
240
|
+
|
241
|
+
if dry_run:
|
242
|
+
print(f"[DRY RUN] Would deploy asset at {asset_path} to registry {config.registry_name}")
|
243
|
+
return True
|
244
|
+
|
245
|
+
asset_path = asset_path.resolve()
|
246
|
+
|
247
|
+
# Validate asset before deployment
|
248
|
+
if not asset_validate(asset_path, dry_run=False):
|
249
|
+
print("❌ [ERROR]: Asset validation failed. Asset deployment aborted.")
|
250
|
+
return False
|
251
|
+
|
252
|
+
# Load asset configuration
|
253
|
+
readonly_asset = assets.AssetConfig(asset_path / assets.DEFAULT_ASSET_FILENAME)
|
254
|
+
|
255
|
+
try:
|
256
|
+
create_or_update_asset(readonly_asset, config)
|
257
|
+
return True
|
258
|
+
except Exception as e:
|
259
|
+
print(f"❌ [ERROR]: Failed to deploy asset: {e}")
|
260
|
+
return False
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# ---------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
3
|
+
# ---------------------------------------------------------
|
4
|
+
"""Asset template creation commands for registry-mgmt CLI."""
|
5
|
+
|
6
|
+
from pathlib import Path
|
7
|
+
from .create_model_spec import generate_model_spec_content
|
8
|
+
|
9
|
+
|
10
|
+
def asset_template(folder_path: Path, dry_run: bool = False) -> bool:
|
11
|
+
"""Create asset template files in the specified folder.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
folder_path (Path): Path to the folder where template files will be created
|
15
|
+
dry_run (bool): If True, perform a dry run without creating files
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
bool: True if template creation succeeds, False otherwise
|
19
|
+
"""
|
20
|
+
if dry_run:
|
21
|
+
print(f"[DRY RUN] Would create asset template files in: {folder_path}")
|
22
|
+
return True
|
23
|
+
|
24
|
+
folder_path = folder_path.resolve()
|
25
|
+
print(f"Creating asset template files in {folder_path} ...")
|
26
|
+
|
27
|
+
# Check if folder path exists, create if it doesn't
|
28
|
+
if not folder_path.exists():
|
29
|
+
try:
|
30
|
+
print(f"Folder path does not exist, creating directory {folder_path} ...")
|
31
|
+
folder_path.mkdir(parents=True, exist_ok=True)
|
32
|
+
print(f"Created directory: {folder_path}")
|
33
|
+
except Exception as e:
|
34
|
+
print(f"[ERROR] Failed to create directory {folder_path}: {e}")
|
35
|
+
return False
|
36
|
+
|
37
|
+
# Get the data directory path (relative to this module)
|
38
|
+
data_dir = Path(__file__).parent.parent / "data"
|
39
|
+
|
40
|
+
# List of template files to create
|
41
|
+
template_files = ["asset.yaml", "spec.yaml", "model.yaml", "notes.md", "evaluation.md", "description.md"]
|
42
|
+
|
43
|
+
# Create each template file
|
44
|
+
try:
|
45
|
+
for output_name in template_files:
|
46
|
+
output_path = folder_path / output_name
|
47
|
+
|
48
|
+
# Special handling for spec.yaml (generate from schema)
|
49
|
+
if output_name == "spec.yaml":
|
50
|
+
schema_path = data_dir / "model.schema.json"
|
51
|
+
if not schema_path.exists():
|
52
|
+
print(f"[ERROR] Schema file not found: {schema_path}")
|
53
|
+
return False
|
54
|
+
|
55
|
+
try:
|
56
|
+
spec_content = generate_model_spec_content(schema_path)
|
57
|
+
with open(output_path, "w", encoding="utf-8") as output_file:
|
58
|
+
output_file.write(spec_content)
|
59
|
+
print(f"Created {output_path}")
|
60
|
+
except Exception as e:
|
61
|
+
print(f"[ERROR] Failed to generate spec.yaml: {e}")
|
62
|
+
return False
|
63
|
+
else:
|
64
|
+
# Handle other template files from data/ folder
|
65
|
+
template_name = f"{output_name}.template"
|
66
|
+
template_path = data_dir / template_name
|
67
|
+
|
68
|
+
# Check if template file exists
|
69
|
+
if not template_path.exists():
|
70
|
+
print(f"[ERROR] Template file not found: {template_path}")
|
71
|
+
return False
|
72
|
+
|
73
|
+
# Read template content and write to output file
|
74
|
+
with open(template_path, "r", encoding="utf-8") as template_file:
|
75
|
+
content = template_file.read()
|
76
|
+
|
77
|
+
with open(output_path, "w", encoding="utf-8") as output_file:
|
78
|
+
output_file.write(content)
|
79
|
+
|
80
|
+
print(f"Created {output_path}")
|
81
|
+
|
82
|
+
print(f"Created {len(template_files)} template files in {folder_path}")
|
83
|
+
return True
|
84
|
+
|
85
|
+
except Exception as e:
|
86
|
+
print(f"[ERROR] Failed to create template files: {e}")
|
87
|
+
return False
|