""" Generates a library ready to be used as a VCSP endpoint for content library 2016 (vsphere 6.5) and beyond. """ __author__ = 'VMware, Inc.' __copyright__ = 'Copyright 2019 VMware, Inc. All rights reserved.' import argparse import boto3 import datetime import hashlib import logging import json import os import uuid import sys from botocore.client import ClientError from dateutil.tz import tzutc VCSP_VERSION = 2 ISO_FORMAT = "%Y-%m-%dT%H:%MZ" FORMAT = "json" FILE_EXTENSION_CERT = ".cert" LIB_FILE = ''.join(("lib", os.extsep, FORMAT)) ITEMS_FILE = ''.join(("items", os.extsep, FORMAT)) ITEM_FILE = ''.join(("item", os.extsep, FORMAT)) VCSP_TYPE_OVF = "vcsp.ovf" VCSP_TYPE_ISO = "vcsp.iso" VCSP_TYPE_OTHER = "vcsp.other" logger = logging.getLogger(__name__) def _md5_for_file(f, md5=None, block_size=2**20): if md5 is None: md5 = hashlib.md5() while True: data = f.read(block_size) if not data: break md5.update(data) return md5 def _md5_for_folder(folder): md5 = None for files in os.listdir(folder): if ITEM_FILE not in files: with open(os.path.join(folder, files), "rb") as handle: md5 = _md5_for_file(handle, md5) return md5.hexdigest() def _make_lib(name, id=uuid.uuid4(), creation=datetime.datetime.now(), version=1): return { "vcspVersion": str(VCSP_VERSION), "version": str(version), "contentVersion": "1", "name": name, "id": "urn:uuid:%s" % id, "created": creation.strftime(ISO_FORMAT), "capabilities": { "transferIn": [ "httpGet" ], "transferOut": [ "httpGet" ], }, "itemsHref": ITEMS_FILE } def _make_item(directory, vcsp_type, name, files, description="", properties={}, identifier=uuid.uuid4(), creation=datetime.datetime.now(), version=2, library_id="", is_vapp_template="false"): ''' add type adapter metadata for OVF template ''' if "urn:uuid:" not in str(identifier): item_id = "urn:uuid:%s" % identifier else: item_id = identifier type_metadata = None if vcsp_type == VCSP_TYPE_OVF: # generate sample type metadata for OVF template so that subscriber can show OVF VM type type_metadata_value = "{\"id\":\"%s\",\"version\":\"%s\",\"libraryIdParent\":\"%s\",\"isVappTemplate\":\"%s\",\"vmTemplate\":null,\"vappTemplate\":null,\"networks\":[],\"storagePolicyGroups\":null}" % (item_id, str(version), library_id, is_vapp_template) type_metadata = { "key": "type-metadata", "value": type_metadata_value, "type": "String", "domain": "SYSTEM", "visibility": "READONLY" } if type_metadata: return { "created": creation.strftime(ISO_FORMAT), "description": description, "version": str(version), "files": files, "id": item_id, "name": name, "metadata": [type_metadata], "properties": properties, "selfHref": "%s/%s" % (directory, ITEM_FILE), "type": vcsp_type } else: return { "created": creation.strftime(ISO_FORMAT), "description": description, "version": str(version), "files": files, "id": item_id, "name": name, "properties": properties, "selfHref": "%s/%s" % (directory, ITEM_FILE), "type": vcsp_type } def _make_items(items, version=1): return { "items": items } def _dir2item(path, directory, md5_enabled, lib_id): files_items = [] name = os.path.split(path)[-1] vcsp_type = VCSP_TYPE_OTHER folder = "" folder_md5 = "" is_vapp = "" for f in os.listdir(path): if f == ".DS_Store" or f == ''.join((directory, os.extsep, FORMAT)): continue else: if f == "item.json": continue # skip the item.json meta data files p = os.path.join(path, f) m = hashlib.md5() new_folder = os.path.dirname(p) if new_folder != folder: # new folder (ex: template1/) if md5_enabled: folder_md5 = _md5_for_folder(new_folder) folder = new_folder if md5_enabled: m.update(os.path.dirname(p).encode('utf-8')) if ".ovf" in p or ".ova" in p: vcsp_type = VCSP_TYPE_OVF # TODO: ready ovf descriptor for type metadata is_vapp = "false" elif ".iso" in p: vcsp_type = VCSP_TYPE_ISO size = os.path.getsize(p) href = "%s/%s" % (directory, f) h = "" if md5_enabled: with open(p, "rb") as handle: h = _md5_for_file(handle) files_items.append({ "name": f, "size": size, "etag": folder_md5, "hrefs": [ href ] }) return _make_item(name, vcsp_type, name, files_items, identifier = uuid.uuid4(), library_id=lib_id, is_vapp_template=is_vapp) def _dir2item_s3(s3_client, bucket_name, path, item_name, skip_cert, lib_id, old_item=""): """ Generate items jsons for the given item path on s3 if the folder only contains iso files, then one item will be created for each iso file, and its item json will be generated accordingly; otherwise only one item json will be generated. Args: s3_client: S3 client bucket_name: S3 bucket name path: item path on S3 bucket item_name: name of the item skip_cert: whether or not to skip cert file lib_id: library id old_item: old item json Returns: map of item name to item json """ items_json = {} files_items = [] vcsp_type = None response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=path, Delimiter="/") is_vapp = "false" for content in response['Contents']: file_path = content['Key'] if file_path == path or file_path.endswith("item.json"): continue file_name = file_path.split("/")[-1] if ".ovf" in file_name or ".ova" in file_name: vcsp_type = VCSP_TYPE_OVF # check if the existing item json already contains "type-metadata" metadata, if not # download the OVF file and parse the descriptor for metadata and search for " -t -p --etag --skip-cert Note that s3 requires the following configurations: 1. ~/.aws/config [default] region=us-west-1 2. ~/.aws/credentials [default] aws_access_key_id = aws_secret_access_key = """ def main(): args = parse_options() lib_name = args.name storage_type = args.type lib_path = args.path md5_enabled = args.etag == 'true' or args.etag == 'True' skip_cert = args.skip_cert == 'true' or args.skip_cert == 'True' if "local" == storage_type: make_vcsp(lib_name, lib_path, md5_enabled) elif "s3" == storage_type: make_vcsp_s3(lib_name, lib_path, skip_cert) if __name__ == "__main__": main()