Fixes bug 1657 repo generation from osm-packages
[osm/osmclient.git] / osmclient / sol005 / osmrepo.py
index c2bb0c9..469d6fd 100644 (file)
@@ -31,7 +31,7 @@ from osmclient.common.package_tool import PackageTool
 from osmclient.sol005.repo import Repo
 from packaging import version as versioning
 import requests
-import ruamel.yaml
+import yaml
 
 
 class OSMRepo(Repo):
@@ -64,7 +64,7 @@ class OSMRepo(Repo):
                 r = requests.get('{}/index.yaml'.format(repository.get('url')))
 
                 if r.status_code == 200:
-                    repo_list = ruamel.yaml.safe_load(r.text)
+                    repo_list = yaml.safe_load(r.text)
                     vnf_packages = repo_list.get('{}_packages'.format(pkgtype))
                     for repo in vnf_packages:
                         versions = vnf_packages.get(repo)
@@ -86,7 +86,12 @@ class OSMRepo(Repo):
                 else:
                     raise Exception('repository in url {} unreachable'.format(repository.get('url')))
             except Exception as e:
-                logging.error("Error cannot read from repository {} '{}': {}".format(repository['name'], repository['url'], e))
+                self._logger.error(
+                    "Error cannot read from repository {} '{}': {}".format(
+                        repository["name"], repository["url"], e
+                    ),
+                    exc_info=True
+                )
                 continue
 
         vnf_repos_filtered = []
@@ -137,7 +142,7 @@ class OSMRepo(Repo):
             raise ClientException('Package not found')
         folder, descriptor = self.zip_extraction(pkg_name)
         with open(descriptor) as pkg:
-            pkg_descriptor = ruamel.yaml.safe_load(pkg)
+            pkg_descriptor = yaml.safe_load(pkg)
         rmtree(folder, ignore_errors=False)
         if ((pkgtype == 'vnf' and (pkg_descriptor.get('vnfd') or pkg_descriptor.get('vnfd:vnfd_catalog'))) or
                 (pkgtype == 'ns' and (pkg_descriptor.get('nsd') or pkg_descriptor.get('nsd:nsd_catalog')))):
@@ -150,28 +155,51 @@ class OSMRepo(Repo):
             :param origin: origin directory for getting all the artifacts
             :param destination: destination folder for create and index the valid artifacts
         """
-        if destination == '.':
+        self._logger.debug("Starting index composition")
+        if destination == ".":
             if origin == destination:
                 destination = 'repository'
 
         destination = abspath(destination)
         origin = abspath(origin)
-
-        if origin[0] != '/':
+        self._logger.debug(f"Paths {destination}, {origin}")
+        if origin[0] != "/":
             origin = join(getcwd(), origin)
         if destination[0] != '/':
             destination = join(getcwd(), destination)
 
         self.init_directory(destination)
-        artifacts = [f for f in listdir(origin) if isfile(join(origin, f))]
-        directories = [f for f in listdir(origin) if isdir(join(origin, f))]
+        artifacts = []
+        directories = []
+        for f in listdir(origin):
+            if isfile(join(origin, f)) and f.endswith('.tar.gz'):
+                artifacts.append(f)
+            elif isdir(join(origin, f)) and f != destination.split('/')[-1] and not f.startswith('.'):
+                directories.append(f)  # TODO: Document that nested directories are not supported
+            else:
+                self._logger.debug(f"Ignoring {f}")
         for artifact in artifacts:
-            self.register_artifact_in_repository(join(origin, artifact), destination, source='file')
+            self.register_artifact_in_repository(
+                join(origin, artifact), destination, source="artifact"
+            )
         for artifact in directories:
-            self.register_artifact_in_repository(join(origin, artifact), destination, source='directory')
-        print("\nFinal Results: ")
-        print("VNF Packages Indexed: " + str(len(glob.glob(destination + "/vnf/*/*/metadata.yaml"))))
-        print("NS Packages Indexed: " + str(len(glob.glob(destination + "/ns/*/*/metadata.yaml"))))
+            self.register_artifact_in_repository(
+                join(origin, artifact), destination, source="directory"
+            )
+        self._logger.info("\nFinal Results: ")
+        self._logger.info(
+            "VNF Packages Indexed: "
+            + str(len(glob.glob(destination + "/vnf/*/*/metadata.yaml")))
+        )
+        self._logger.info(
+            "NS Packages Indexed: "
+            + str(len(glob.glob(destination + "/ns/*/*/metadata.yaml")))
+        )
+
+        self._logger.info(
+            "NST Packages Indexed: "
+            + str(len(glob.glob(destination + "/nst/*/*/metadata.yaml")))
+        )
 
     def md5(self, fname):
         """
@@ -179,53 +207,100 @@ class OSMRepo(Repo):
             :param fname: file path
             :return: checksum string
         """
+        self._logger.debug("")
         hash_md5 = hashlib.md5()
         with open(fname, "rb") as f:
             for chunk in iter(lambda: f.read(4096), b""):
                 hash_md5.update(chunk)
         return hash_md5.hexdigest()
 
-    def fields_building(self, descriptor_json, file, package_type):
+    def fields_building(self, descriptor_dict, file, package_type):
         """
-            From an artifact descriptor, obtain the fields required for indexing
-            :param descriptor_json: artifact description
-            :param file: artifact package
-            :param package_type: type of artifact (vnf or ns)
-            :return: fields
+        From an artifact descriptor, obtain the fields required for indexing
+        :param descriptor_dict: artifact description
+        :param file: artifact package
+        :param package_type: type of artifact (vnf, ns, nst)
+        :return: fields
         """
+        self._logger.debug("")
+
         fields = {}
         base_path = '/{}/'.format(package_type)
         aux_dict = {}
         if package_type == "vnf":
-            if descriptor_json.get('vnfd-catalog', False):
-                aux_dict = descriptor_json.get('vnfd-catalog', {}).get('vnfd', [{}])[0]
+            if descriptor_dict.get("vnfd-catalog", False):
+                aux_dict = descriptor_dict.get("vnfd-catalog", {}).get("vnfd", [{}])[0]
+            elif descriptor_dict.get("vnfd:vnfd-catalog"):
+                aux_dict = descriptor_dict.get("vnfd:vnfd-catalog", {}).get("vnfd", [{}])[0]
+            elif descriptor_dict.get("vnfd"):
+                aux_dict = descriptor_dict["vnfd"]
+                if aux_dict.get("vnfd"):
+                    aux_dict = aux_dict['vnfd'][0]
             else:
-                aux_dict = descriptor_json.get('vnfd:vnfd-catalog', {}).get('vnfd', [{}])[0]
-
+                msg = f"Unexpected descriptor format {descriptor_dict}"
+                self._logger.error(msg)
+                raise ValueError(msg)
+            self._logger.debug(f"Extracted descriptor info for {package_type}: {aux_dict}")
             images = []
-            for vdu in aux_dict.get('vdu', ()):
-                images.append(vdu.get('image'))
-            fields['images'] = images
-        if package_type == "ns":
-            if descriptor_json.get('nsd-catalog', False):
-                aux_dict = descriptor_json.get('nsd-catalog', {}).get('nsd', [{}])[0]
+            for vdu in aux_dict.get("vdu", aux_dict.get('kdu', ())):
+                images.append(vdu.get("image", vdu.get('name')))
+            fields["images"] = images
+        elif package_type == "ns":
+            if descriptor_dict.get("nsd-catalog", False):
+                aux_dict = descriptor_dict.get("nsd-catalog", {}).get("nsd", [{}])[0]
+            elif descriptor_dict.get("nsd:nsd-catalog"):
+                aux_dict = descriptor_dict.get("nsd:nsd-catalog", {}).get("nsd", [{}])[0]
+            elif descriptor_dict.get("nsd"):
+                aux_dict = descriptor_dict['nsd']
+                if aux_dict.get("nsd"):
+                    aux_dict = descriptor_dict["nsd"]["nsd"][0]
             else:
-                aux_dict = descriptor_json.get('nsd:nsd-catalog', {}).get('nsd', [{}])[0]
-
+                msg = f"Unexpected descriptor format {descriptor_dict}"
+                self._logger.error(msg)
+                raise ValueError(msg)
             vnfs = []
-
-            for vnf in aux_dict.get('constituent-vnfd', ()):
-                vnfs.append(vnf.get('vnfd-id-ref'))
-            self._logger.debug('Used VNFS in the NSD: ' + str(vnfs))
-            fields['vnfd-id-ref'] = vnfs
-
-        fields['name'] = aux_dict.get('name')
-        fields['id'] = aux_dict.get('id')
-        fields['description'] = aux_dict.get('description')
-        fields['vendor'] = aux_dict.get('vendor')
-        fields['version'] = aux_dict.get('version', '1.0')
-        fields['path'] = "{}{}/{}/{}-{}.tar.gz".format(base_path, fields['id'], fields['version'], fields.get('id'),
-                                                       fields.get('version'))
+            if aux_dict.get("constituent-vnfd"):
+                for vnf in aux_dict.get("constituent-vnfd", ()):
+                    vnfs.append(vnf.get("vnfd-id-ref"))
+            else:
+                vnfs = aux_dict.get('vnfd-id')
+            self._logger.debug("Used VNFS in the NSD: " + str(vnfs))
+            fields["vnfd-id-ref"] = vnfs
+        elif package_type == 'nst':
+            if descriptor_dict.get("nst-catalog", False):
+                aux_dict = descriptor_dict.get("nst-catalog", {}).get("nst", [{}])[0]
+            elif descriptor_dict.get("nst:nst-catalog"):
+                aux_dict = descriptor_dict.get("nst:nst-catalog", {}).get("nst", [{}])[0]
+            elif descriptor_dict.get("nst"):
+                aux_dict = descriptor_dict['nst']
+                if aux_dict.get("nst"):
+                    aux_dict = descriptor_dict["nst"]["nst"][0]
+            nsds = []
+            for nsd in aux_dict.get("netslice-subnet", ()):
+                nsds.append(nsd.get("nsd-ref"))
+            self._logger.debug("Used NSDs in the NST: " + str(nsds))
+            if not nsds:
+                msg = f"Unexpected descriptor format {descriptor_dict}"
+                self._logger.error(msg)
+                raise ValueError(msg)
+            fields["nsd-id-ref"] = nsds
+        else:
+            msg = f"Unexpected descriptor format {descriptor_dict}"
+            self._logger.error(msg)
+            raise ValueError(msg)
+
+        fields["name"] = aux_dict.get("name")
+        fields["id"] = aux_dict.get("id")
+        fields["description"] = aux_dict.get("description")
+        fields["vendor"] = aux_dict.get("vendor")
+        fields["version"] = str(aux_dict.get("version", "1.0"))
+        fields["path"] = "{}{}/{}/{}-{}.tar.gz".format(
+            base_path,
+            fields["id"],
+            fields["version"],
+            fields.get("id"),
+            fields.get("version"),
+        )
         return fields
 
     def zip_extraction(self, file_name):
@@ -248,10 +323,12 @@ class OSMRepo(Repo):
 
     def validate_artifact(self, path, source):
         """
-            Validation of artifact.
-            :param path: file path
-            :return: status details, status, fields, package_type
+        Validation of artifact.
+        :param path: file path
+        :param source: flag to select the correct file type (directory or artifact)
+        :return: status details, status, fields, package_type
         """
+        self._logger.debug("")
         package_type = ''
         folder = ''
         try:
@@ -264,20 +341,37 @@ class OSMRepo(Repo):
 
             with open(descriptor_file, 'r') as f:
                 descriptor_data = f.read()
+            self._logger.debug(f"Descriptor data: {descriptor_data}")
             validation = validation_im()
-            desc_type, descriptor_data = validation.yaml_validation(descriptor_data)
-            validation_im.pyangbind_validation(self, desc_type, descriptor_data)
-            if 'vnf' in list(descriptor_data.keys())[0]:
-                package_type = 'vnf'
+            desc_type, descriptor_dict = validation.yaml_validation(descriptor_data)
+            try:
+                validation_im.pyangbind_validation(self, desc_type, descriptor_dict)
+            except Exception as e:
+                self._logger.error(e, exc_info=True)
+                raise e
+            descriptor_type_ref = list(descriptor_dict.keys())[0].lower()
+            if "vnf" in descriptor_type_ref:
+                package_type = "vnf"
+            elif "nst" in descriptor_type_ref:
+                package_type = "nst"
+            elif "ns" in descriptor_type_ref:
+                package_type = "ns"
             else:
-                # raise ClientException("Not VNF package")
-                package_type = 'ns'
-
-            self._logger.debug("Descriptor: {}".format(descriptor_data))
-            fields = self.fields_building(descriptor_data, path, package_type)
-            self._logger.debug("Descriptor sucessfully validated")
-            return {"detail": "{}D successfully validated".format(package_type.upper()),
-                    "code": "OK"}, True, fields, package_type
+                msg = f"Unknown package type {descriptor_type_ref}"
+                self._logger.error(msg)
+                raise ValueError(msg)
+            self._logger.debug("Descriptor: {}".format(descriptor_dict))
+            fields = self.fields_building(descriptor_dict, path, package_type)
+            self._logger.debug(f"Descriptor successfully validated {fields}")
+            return (
+                {
+                    "detail": "{}D successfully validated".format(package_type.upper()),
+                    "code": "OK",
+                },
+                True,
+                fields,
+                package_type,
+            )
         except Exception as e:
             # Delete the folder we just created
             return {"detail": str(e)}, False, {}, package_type
@@ -291,25 +385,29 @@ class OSMRepo(Repo):
             file: VNF or NS
             destination: path for index creation
         """
+        self._logger.debug("")
         pt = PackageTool()
-        compresed = False
+        compressed = False
         try:
             fields = {}
             _, valid, fields, package_type = self.validate_artifact(path, source)
             if not valid:
-                raise Exception('{} {} Not well configured.'.format(package_type.upper(), str(path)))
+                raise Exception(
+                    "{} {} Not well configured.".format(package_type.upper(), str(path))
+                )
             else:
-                if source == 'directory':
+                if source == "directory":
                     path = pt.build(path)
-                    compresed = True
-                fields['checksum'] = self.md5(path)
+                    self._logger.debug(f"Directory path {path}")
+                    compressed = True
+                fields["checksum"] = self.md5(path)
                 self.indexation(destination, path, package_type, fields)
 
         except Exception as e:
             self._logger.exception("Error registering artifact in Repository: {}".format(e))
 
         finally:
-            if source == 'directory' and compresed:
+            if source == "directory" and compressed:
                 remove(path)
 
     def indexation(self, destination, path, package_type, fields):
@@ -320,6 +418,7 @@ class OSMRepo(Repo):
             :param package_type: package type (vnf, ns)
             :param fields: dict with the required values
         """
+        self._logger.debug("")
         data_ind = {'name': fields.get('name'), 'description': fields.get('description'),
                     'vendor': fields.get('vendor'), 'path': fields.get('path')}
 
@@ -331,49 +430,62 @@ class OSMRepo(Repo):
                 mkdir(final_path)
                 copyfile(path,
                          final_path + '/' + fields.get('id') + "-" + fields.get('version') + '.tar.gz')
-                ruamel.yaml.dump(fields, open(final_path + '/' + 'metadata.yaml', 'w'),
-                                 Dumper=ruamel.yaml.RoundTripDumper)
-                index = ruamel.yaml.load(open(destination + '/index.yaml'), Loader=ruamel.yaml.Loader)
+                yaml.safe_dump(fields, open(final_path + '/' + 'metadata.yaml', 'w'),
+                               default_flow_style=False, width=80, indent=4)
+                index = yaml.safe_load(open(destination + '/index.yaml'))
 
                 index['{}_packages'.format(package_type)][fields.get('id')][fields.get('version')] = data_ind
                 if versioning.parse(index['{}_packages'.format(package_type)][fields.get('id')][
                                     'latest']) < versioning.parse(fields.get('version')):
                     index['{}_packages'.format(package_type)][fields.get('id')]['latest'] = fields.get(
                         'version')
-                ruamel.yaml.dump(index, open(destination + '/index.yaml', 'w'), Dumper=ruamel.yaml.RoundTripDumper)
+                yaml.safe_dump(index, open(destination + '/index.yaml', 'w'),
+                               default_flow_style=False, width=80, indent=4)
                 self._logger.info('{} {} added in the repository'.format(package_type.upper(), str(path)))
         else:
             mkdir(destination + '/{}/'.format(package_type) + fields.get('id'))
             mkdir(final_path)
             copyfile(path,
                      final_path + '/' + fields.get('id') + "-" + fields.get('version') + '.tar.gz')
-            ruamel.yaml.dump(fields, open(join(final_path, 'metadata.yaml'), 'w'), Dumper=ruamel.yaml.RoundTripDumper)
-            index = ruamel.yaml.load(open(destination + '/index.yaml'), Loader=ruamel.yaml.Loader)
+            yaml.safe_dump(fields, open(join(final_path, 'metadata.yaml'), 'w'),
+                           default_flow_style=False, width=80, indent=4)
+            index = yaml.safe_load(open(destination + '/index.yaml'))
 
             index['{}_packages'.format(package_type)][fields.get('id')] = {fields.get('version'): data_ind}
             index['{}_packages'.format(package_type)][fields.get('id')]['latest'] = fields.get('version')
-            ruamel.yaml.dump(index, open(join(destination, 'index.yaml'), 'w'), Dumper=ruamel.yaml.RoundTripDumper)
+            yaml.safe_dump(index, open(join(destination, 'index.yaml'), 'w'),
+                           default_flow_style=False, width=80, indent=4)
             self._logger.info('{} {} added in the repository'.format(package_type.upper(), str(path)))
 
-    def current_datatime(self):
+    def current_datetime(self):
         """
             Datetime Generator
             :return: Datetime as string with the following structure "2020-04-29T08:41:07.681653Z"
         """
+        self._logger.debug("")
         return time.strftime('%Y-%m-%dT%H:%M:%S.%sZ')
 
     def init_directory(self, destination):
         """
-            Initialize the index directory. Creation of index.yaml, and the directories for vnf and ns
-            :param destination:
-            :return:
+        Initialize the index directory. Creation of index.yaml, and the directories for vnf and ns
+        :param destination:
+        :return:
         """
+        self._logger.debug("")
         if not isdir(destination):
             mkdir(destination)
-        if not isfile(join(destination, 'index.yaml')):
-            mkdir(join(destination, 'vnf'))
-            mkdir(join(destination, 'ns'))
-            index_data = {'apiVersion': 'v1', 'generated': self.current_datatime(), 'vnf_packages': {},
-                          'ns_packages': {}}
-            with open(join(destination, 'index.yaml'), 'w') as outfile:
-                ruamel.yaml.dump(index_data, outfile, default_flow_style=False)
+        if not isfile(join(destination, "index.yaml")):
+            mkdir(join(destination, "vnf"))
+            mkdir(join(destination, "ns"))
+            mkdir(join(destination, "nst"))
+            index_data = {
+                "apiVersion": "v1",
+                "generated": self.current_datetime(),
+                "vnf_packages": {},
+                "ns_packages": {},
+                "nst_packages": {},
+            }
+            with open(join(destination, "index.yaml"), "w") as outfile:
+                yaml.safe_dump(
+                    index_data, outfile, default_flow_style=False, width=80, indent=4
+                )