|
|
|
|
@ -6,20 +6,22 @@
|
|
|
|
|
#
|
|
|
|
|
#
|
|
|
|
|
"""
|
|
|
|
|
Implements functionality for downloading o3de objecs either locally or from a URI
|
|
|
|
|
Implements functionality for downloading o3de objects either locally or from a URI
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import hashlib
|
|
|
|
|
import json
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
import pathlib
|
|
|
|
|
import shutil
|
|
|
|
|
import sys
|
|
|
|
|
import urllib.parse
|
|
|
|
|
import urllib.request
|
|
|
|
|
import zipfile
|
|
|
|
|
|
|
|
|
|
from o3de import manifest, repo, utils, validation
|
|
|
|
|
from o3de import manifest, repo, utils, validation, register
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
|
logging.basicConfig()
|
|
|
|
|
@ -37,21 +39,21 @@ def unzip_manifest_json_data(download_zip_path: pathlib.Path, zip_file_name: str
|
|
|
|
|
|
|
|
|
|
def validate_downloaded_zip_sha256(download_uri_json_data: dict, download_zip_path: pathlib.Path,
|
|
|
|
|
manifest_json_name) -> int:
|
|
|
|
|
# if the engine.json has a sha256 check it against a sha256 of the zip
|
|
|
|
|
# if the json has a sha256 check it against a sha256 of the zip
|
|
|
|
|
try:
|
|
|
|
|
sha256A = download_uri_json_data['sha256']
|
|
|
|
|
except KeyError as e:
|
|
|
|
|
logger.warn(f'SECURITY WARNING: The advertised o3de object you downloaded has no "sha256"!!! Be VERY careful!!!'
|
|
|
|
|
f' We cannot verify this is the actually the advertised object!!!')
|
|
|
|
|
return 0
|
|
|
|
|
else:
|
|
|
|
|
sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest()
|
|
|
|
|
if sha256A != sha256B:
|
|
|
|
|
logger.error(f'SECURITY VIOLATION: Downloaded zip sha256 {sha256B} does not match'
|
|
|
|
|
f' the advertised "sha256":{sha256A} in the f{manifest_json_name}. Deleting unzipped files!!!')
|
|
|
|
|
shutil.rmtree(dest_path)
|
|
|
|
|
f' the advertised "sha256":{sha256A} in the f{manifest_json_name}.')
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
manifest_json_data = unzip_manifest_json_data(download_zip_path, manifest_json_name)
|
|
|
|
|
unzipped_manifest_json_data = unzip_manifest_json_data(download_zip_path, manifest_json_name)
|
|
|
|
|
|
|
|
|
|
# remove the sha256 if present in the advertised downloadable manifest json
|
|
|
|
|
# then compare it to the json in the zip, they should now be identical
|
|
|
|
|
@ -61,19 +63,11 @@ def validate_downloaded_zip_sha256(download_uri_json_data: dict, download_zip_pa
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
sha256A = hashlib.sha256(json.dumps(download_uri_json_data, indent=4).encode('utf8')).hexdigest()
|
|
|
|
|
with unzipped_manifest_json.open('r') as s:
|
|
|
|
|
try:
|
|
|
|
|
unzipped_manifest_json_data = json.load(s)
|
|
|
|
|
except json.JSONDecodeError as e:
|
|
|
|
|
logger.error(f'Failed to read manifest json {unzipped_manifest_json}. Unable to confirm this'
|
|
|
|
|
f' is the same template that was advertised.')
|
|
|
|
|
return 1
|
|
|
|
|
sha256B = hashlib.sha256(json.dumps(unzipped_manifest_json_data, indent=4).encode('utf8')).hexdigest()
|
|
|
|
|
if sha256A != sha256B:
|
|
|
|
|
logger.error(f'SECURITY VIOLATION: Downloaded manifest json does not match'
|
|
|
|
|
f' the advertised manifest json. Deleting unzipped files!!!')
|
|
|
|
|
shutil.rmtree(dest_path)
|
|
|
|
|
return 1
|
|
|
|
|
f' the advertised manifest json.')
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
@ -91,23 +85,15 @@ def get_downloadable(engine_name: str = None,
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
manifest_json = 'repo.json'
|
|
|
|
|
search_func = lambda: repo.search_repo(manifest_json, engine_name, project_name, gem_name, template_name)
|
|
|
|
|
search_func = lambda manifest_json_data: repo.search_repo(manifest_json_data, engine_name, project_name, gem_name, template_name)
|
|
|
|
|
return repo.search_o3de_object(manifest_json, o3de_object_uris, search_func)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_o3de_object(object_name: str, default_folder_name: str, dest_path: str or pathlib.Path,
|
|
|
|
|
object_type: str, downloadable_kwarg_key) -> int:
|
|
|
|
|
if not dest_path:
|
|
|
|
|
dest_path = manifest.get_registered(default_folder=default_folder_name)
|
|
|
|
|
if not dest_path:
|
|
|
|
|
logger.error(f'Destination path not cannot be empty.')
|
|
|
|
|
return 1
|
|
|
|
|
object_type: str, downloadable_kwarg_key, skip_auto_register: bool) -> int:
|
|
|
|
|
|
|
|
|
|
dest_path = pathlib.Path(dest_path).resolve()
|
|
|
|
|
dest_path.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
download_path = manifest.get_o3de_download_folder() / default_folder_name / object_name
|
|
|
|
|
download_path.mkdir(exist_ok=True)
|
|
|
|
|
download_path = manifest.get_o3de_cache_folder() / default_folder_name / object_name
|
|
|
|
|
download_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
download_zip_path = download_path / f'{object_type}.zip'
|
|
|
|
|
|
|
|
|
|
downloadable_object_data = get_downloadable(**{downloadable_kwarg_key : object_name})
|
|
|
|
|
@ -115,41 +101,79 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path:
|
|
|
|
|
logger.error(f'Downloadable o3de object {object_name} not found.')
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
origin = downloadable_json_data['origin']
|
|
|
|
|
url = f'{origin}/object_type.zip'
|
|
|
|
|
url = downloadable_object_data['originuri']
|
|
|
|
|
parsed_uri = urllib.parse.urlparse(url)
|
|
|
|
|
|
|
|
|
|
download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path)
|
|
|
|
|
if download_zip_result != 0:
|
|
|
|
|
return download_zip_result
|
|
|
|
|
|
|
|
|
|
return validate_downloaded_zip_sha256(downloadable_object_data, download_zip_path)
|
|
|
|
|
if validate_downloaded_zip_sha256(downloadable_object_data, download_zip_path, f'{object_type}.json'):
|
|
|
|
|
logger.error(f'Could not validate zip, deleting {download_zip_path}')
|
|
|
|
|
os.unlink(download_zip_path)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
if not dest_path:
|
|
|
|
|
dest_path = manifest.get_registered(default_folder=default_folder_name)
|
|
|
|
|
dest_path = pathlib.Path(dest_path).resolve()
|
|
|
|
|
dest_path = dest_path / object_name
|
|
|
|
|
else:
|
|
|
|
|
dest_path = pathlib.Path(dest_path).resolve()
|
|
|
|
|
|
|
|
|
|
if not dest_path:
|
|
|
|
|
logger.error(f'Destination path not cannot be empty.')
|
|
|
|
|
return 1
|
|
|
|
|
if dest_path.exists():
|
|
|
|
|
logger.error(f'Destination path {dest_path} already exists.')
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
dest_path.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# extract zip
|
|
|
|
|
with zipfile.ZipFile(download_zip_path, 'r') as zip_file_ref:
|
|
|
|
|
try:
|
|
|
|
|
zip_file_ref.extractall(dest_path)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.error(f'Error unzipping {download_zip_path} to {dest_path}. Deleting {dest_path}.')
|
|
|
|
|
shutil.rmtree(dest_path)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
if not skip_auto_register:
|
|
|
|
|
if object_type == 'gem':
|
|
|
|
|
return register.register(gem_path=dest_path)
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_engine(engine_name: str,
|
|
|
|
|
dest_path: str or pathlib.Path) -> int:
|
|
|
|
|
return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name')
|
|
|
|
|
dest_path: str or pathlib.Path,
|
|
|
|
|
skip_auto_register: bool) -> int:
|
|
|
|
|
return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_project(project_name: str,
|
|
|
|
|
dest_path: str or pathlib.Path) -> int:
|
|
|
|
|
return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name')
|
|
|
|
|
dest_path: str or pathlib.Path,
|
|
|
|
|
skip_auto_register: bool) -> int:
|
|
|
|
|
return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_gem(gem_name: str,
|
|
|
|
|
dest_path: str or pathlib.Path) -> int:
|
|
|
|
|
return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name')
|
|
|
|
|
dest_path: str or pathlib.Path,
|
|
|
|
|
skip_auto_register: bool) -> int:
|
|
|
|
|
return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_template(template_name: str,
|
|
|
|
|
dest_path: str or pathlib.Path) -> int:
|
|
|
|
|
return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name')
|
|
|
|
|
dest_path: str or pathlib.Path,
|
|
|
|
|
skip_auto_register: bool) -> int:
|
|
|
|
|
return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def download_restricted(restricted_name: str,
|
|
|
|
|
dest_path: str or pathlib.Path) -> int:
|
|
|
|
|
return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name')
|
|
|
|
|
dest_path: str or pathlib.Path,
|
|
|
|
|
skip_auto_register: bool) -> int:
|
|
|
|
|
return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _run_download(args: argparse) -> int:
|
|
|
|
|
@ -158,16 +182,20 @@ def _run_download(args: argparse) -> int:
|
|
|
|
|
|
|
|
|
|
if args.engine_name:
|
|
|
|
|
return download_engine(args.engine_name,
|
|
|
|
|
args.dest_path)
|
|
|
|
|
args.dest_path,
|
|
|
|
|
args.skip_auto_register)
|
|
|
|
|
elif args.project_name:
|
|
|
|
|
return download_project(args.project_name,
|
|
|
|
|
args.dest_path)
|
|
|
|
|
elif args.gem_nanme:
|
|
|
|
|
args.dest_path,
|
|
|
|
|
args.skip_auto_register)
|
|
|
|
|
elif args.gem_name:
|
|
|
|
|
return download_gem(args.gem_name,
|
|
|
|
|
args.dest_path)
|
|
|
|
|
args.dest_path,
|
|
|
|
|
args.skip_auto_register)
|
|
|
|
|
elif args.template_name:
|
|
|
|
|
return download_template(args.template_name,
|
|
|
|
|
args.dest_path)
|
|
|
|
|
args.dest_path,
|
|
|
|
|
args.skip_auto_register)
|
|
|
|
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
@ -193,7 +221,9 @@ def add_parser_args(parser):
|
|
|
|
|
' i.e. download --project-name "AstomSamplerViewer" --dest-path "C:/projects"'
|
|
|
|
|
' will result in C:/projects/AtomSampleViewer'
|
|
|
|
|
' If blank will download to default object type folder')
|
|
|
|
|
|
|
|
|
|
parser.add_argument('-sar', '--skip-auto-register', action='store_true', required=False,
|
|
|
|
|
default=False,
|
|
|
|
|
help = 'Skip the automatic registration of new object download')
|
|
|
|
|
parser.add_argument('-ohf', '--override-home-folder', type=str, required=False,
|
|
|
|
|
help='By default the home folder is the user folder, override it to this folder.')
|
|
|
|
|
|
|
|
|
|
|