scripts/azureml-assets/azureml/assets/asset_utils.py (62 lines of code) (raw):

# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. """Tools to maintain asset source files.""" import argparse import shutil from pathlib import Path import azureml.assets as assets import azureml.assets.util as util from azureml.assets.util import logger ASSET_COUNT = "asset_count" DELETED_COUNT = "deleted_count" def list_assets(args: argparse.Namespace): """List assets. Args: args (argparse.Namespace): Args from argparse. """ # Find assets under input dir asset_list = [] for asset_config in util.find_assets(args.input_dirs, args.asset_config_filename): asset_list.append(asset_config.partial_name) asset_list.sort() # Write to file or stdout if args.output_file: with open(args.output_file, "w") as f: f.write("\n".join(asset_list)) else: print("\n".join(asset_list)) # Set variables logger.set_output(ASSET_COUNT, len(asset_list)) def delete_assets(args: argparse.Namespace): """Delete assets that are not in the retention file. NOTE: This is generally meant to be run against the release branch, where there's no risk of deleting shared files like component source code. Args: args (argparse.Namespace): Args from argparse. """ # Read retention file with open(args.retention_file) as f: retention_list = f.read().splitlines() print(f"Read {len(retention_list)} asset(s) from retention file") # Find assets under input dir asset_count = 0 deleted_count = 0 for asset_config in util.find_assets(args.input_dirs, args.asset_config_filename): asset_count += 1 if asset_config.partial_name not in retention_list: # Get common directory, in case asset config file isn't at the root common_dir, _ = util.find_common_directory(asset_config.release_paths) # Delete asset if args.dry_run: logger.print(f"Would delete {asset_config.partial_name} from {common_dir}") else: try: logger.print(f"Deleting {asset_config.partial_name} from {common_dir}") shutil.rmtree(common_dir) deleted_count += 1 except Exception as e: logger.log_warning(f"Failed to delete {common_dir}: {e}") # Set variables logger.set_output(ASSET_COUNT, asset_count) logger.set_output(DELETED_COUNT, deleted_count) if __name__ == '__main__': # Quick function to convert comma-separated arg to Path list def _list_path(value: str): return [Path(d) for d in value.split(",")] # Handle command-line args parser = argparse.ArgumentParser() shared_parser = argparse.ArgumentParser(add_help=False) shared_parser.add_argument("-i", "--input-dirs", type=_list_path, required=True, help="Comma-separated list of directories containing assets") shared_parser.add_argument("-a", "--asset-config-filename", default=assets.DEFAULT_ASSET_FILENAME, help="Asset config file name to search for") subparsers = parser.add_subparsers() parser_list = subparsers.add_parser("list", help="List assets", parents=[shared_parser]) parser_list.set_defaults(func=list_assets) parser_list.add_argument("-o", "--output-file", type=Path, help="File to which asset names will be written") parser_delete = subparsers.add_parser("delete", help="Delete assets", parents=[shared_parser]) parser_delete.set_defaults(func=delete_assets) parser_delete.add_argument("-r", "--retention-file", required=True, type=Path, help="File containing names of assets that will not be deleted") parser_delete.add_argument("-d", "--dry-run", action="store_true", help="Dry run, don't actually make changes") args = parser.parse_args() args.func(args)