From 7992ae4300777dc8d49912fe29dd00afe2f4f303 Mon Sep 17 00:00:00 2001 From: Petr "Stone" Hracek Date: Apr 07 2017 13:24:34 +0000 Subject: [PATCH 1/3] First draft of rpm2module command. Witout tests. Signed-off-by: Petr "Stone" Hracek --- diff --git a/.gitignore b/.gitignore index 8d09458..1fbe4b0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ pip-log.txt .spyderworkspace .cache -tests/openshift-template.yml \ No newline at end of file +tests/openshift-template.yml diff --git a/modularity/cli.py b/modularity/cli.py index 6d3350b..d46cc5e 100644 --- a/modularity/cli.py +++ b/modularity/cli.py @@ -3,6 +3,7 @@ import sys import argparse +from modularity.module_generator import ModuleGenerator from modularity.oc_template import OpenShiftTemplateGenerator @@ -48,3 +49,43 @@ class CliHelper(object): # except Exception as e: # print(e) # sys.exit(1) + +class CLIRpm2Module(object): + """ Class for processing data from commandline """ + + @staticmethod + def build_parser(): + parser = argparse.ArgumentParser(description="Creates an modulesMD file.") + parser.add_argument( + "pkgs", + metavar='PKGS', + help="Specify list of packages for module.", + ) + return parser + + def __init__(self, args=None): + self.parser = CLIRpm2Module.build_parser() + self.args = self.parser.parse_args(args) + + def __getattr__(self, name): + try: + return getattr(self.args, name) + except AttributeError: + return object.__getattribute__(self, name) + + +class CliRpm2ModuleHelper(object): + + @staticmethod + def run(): + try: + cli = CLIRpm2Module(sys.argv[1:]) + mg = ModuleGenerator(cli) + mg.run() + + except KeyboardInterrupt: + print('\nInterrupted by user') + # except Exception as e: + # print(e) + # sys.exit(1) + diff --git a/modularity/module_generator.py b/modularity/module_generator.py new file mode 100644 index 0000000..4dd855e --- /dev/null +++ b/modularity/module_generator.py @@ -0,0 +1,170 @@ +#!/bin/python + +import os +import yaml +import urllib +import subprocess +import re + +MODULE_TMPL = "template.yaml" + + +class ModuleGenerator(object): + + gen_core_list = "gen_core_binary_pkgs.txt" + spec_url = "http://pkgs.fedoraproject.org/cgit/rpms/%s.git/plain/%s" + gen_core_url = "https://raw.githubusercontent.com/asamalik/fake-base-runtime-module-image/" \ + "master/packages/gen-core-binary-pkgs.txt" + + def __init__(self, args): + self.module_yaml = None + self.args = args + self.package_list = None + self.spec_data = {} + + def _save_template(self): + """ + Function saves modulemd file to the current directory + based on argument name + :return: + """ + file_name = self.args.pkgs + '.yaml' + with open(file_name, 'w') as f: + try: + yaml.dump(self.module_yaml, f, default_flow_style=False) + except yaml.YAMLError as exc: + print(exc) + raise + print('Modulemd file is generated here ./%s' % file_name) + return True + + def _load_template(self): + """ + Function loads modulmd template + :return: + """ + if not os.path.exists(MODULE_TMPL): + return False + with open(MODULE_TMPL, 'r') as f: + try: + self.module_yaml = yaml.load(f) + except yaml.YAMLError as exc: + print(exc) + raise + return True + + @staticmethod + def download_file(url, filename): + """ + Function checks if file exists and if not download them + :param url: URL of file to download + :param filename: downloaded filename + :return: + """ + if not os.path.exists(filename): + gen_core_list = urllib.URLopener() + gen_core_list.retrieve(url, filename) + + def _load_gen_core_list(self): + """ + Function parses gen_core_list + :return: list of gen_core_packages + """ + ModuleGenerator.download_file(self.gen_core_url, + self.gen_core_list) + with open(self.gen_core_list, "r") as f: + try: + self.package_list = [l.strip('\n') for l in f.readlines()] + except IOError: + raise + + @staticmethod + def run_command(cmd): + """ + Function runs command + :param cmd: Command to run + :return: output, error + """ + process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) + output, error = process.communicate() + return output, error + + def _get_list_deps(self): + """ + Function gets dependencies for specific package which is defined as a argument. + Dependencies are filter out agains gen_core_list.txt file + :return: list of dependencies + """ + deps_list = [] + cmd = 'repoquery --requires --recursive --resolve --qf %{SOURCERPM} ' + self.args.pkgs + output, error = ModuleGenerator.run_command(cmd) + + output = [re.sub("-[^-]*-[^-]*$", "", l) for l in output.split('\n') if l] + for out in output: + if out not in self.package_list and out not in deps_list: + deps_list.append(out) + print('List of generated dependencies:\n%s' % '\n'.join(deps_list)) + return deps_list + + def _update_module_md(self, deps_list): + """ + Function updates modulemd file with dependencies + are information taken from SPEC file. + :param deps_list: List of dependencies + :return: + """ + yaml_data = self.module_yaml['data'] + if deps_list: + + if not yaml_data['components']['rpms']: + yaml_data['components']['rpms'] = {} + comp_rpms = yaml_data['components']['rpms'] + for pkg in deps_list: + deps_dict = {'rationale': 'build dependency', + 'ref': 'f25'} + comp_rpms[pkg] = deps_dict + yaml_data['version'] = self.spec_data['Version'].strip() + yaml_data['summary'] = self.spec_data['Summary'].strip() + yaml_data['description'] = self.spec_data['Summary'].strip() + yaml_data['license']['module'] = self.spec_data['License'].strip() + + @staticmethod + def parse_spec_data(data): + """ + Function parses SPEC file for specific fields. + :param data: SPEC file data + :return: dictionary with parsed data + """ + required_fields = ['Version', 'Summary', 'License', 'Name', 'Url'] + # select only import RPM data + spec_data = [d.split(':') for d in data for field in required_fields if d.startswith(field)] + # Create a dictionary + return dict(item for item in spec_data) + + def _get_rpm_stuff(self): + """ + Function gets RPM stuff from SPEC file. + :return: + """ + spec_file = self.args.pkgs + '.spec' + spec_url = self.spec_url % (self.args.pkgs, spec_file) + ModuleGenerator.download_file(spec_url, spec_file) + cmd = "rpmspec -P %s" % spec_file + output, error = ModuleGenerator.run_command(cmd) + if error is None: + with open(spec_file, "r") as f: + try: + lines = (line.strip() for line in f) + lines = list(line for line in lines if line) # Non-blank lines in a list + if lines: + self.spec_data = ModuleGenerator.parse_spec_data(lines) + except IOError: + raise + + def run(self): + self._load_gen_core_list() + self._load_template() + self._get_rpm_stuff() + self._update_module_md(self._get_list_deps()) + self._save_template() + diff --git a/rpm2module.py b/rpm2module.py new file mode 100755 index 0000000..098cbad --- /dev/null +++ b/rpm2module.py @@ -0,0 +1,7 @@ +#!/bin/python + +import sys +from modularity.cli import CliRpm2ModuleHelper + +if __name__ == "__main__": + sys.exit(CliRpm2ModuleHelper.run()) diff --git a/template.yaml b/template.yaml new file mode 100644 index 0000000..4837e38 --- /dev/null +++ b/template.yaml @@ -0,0 +1,25 @@ +document: modulemd +version: 1 +data: + summary: + description: + license: + module: + dependencies: + buildrequires: + base_runtime: master + requires: + base_runtime: master + references: + community: https://fedoraproject.org/wiki/Modularity + documentation: https://fedoraproject.org/wiki/Fedora_Packaging_Guidelines_for_Modules + tracker: + profiles: + default: + rpms: + minimal: + rpms: + api: + rpms: + components: + rpms: From ec15077457ab80f36975c62e2815996231fb7b38 Mon Sep 17 00:00:00 2001 From: Dominika Hodovska Date: Apr 07 2017 13:24:34 +0000 Subject: [PATCH 2/3] Use brt_dep_solver.sh, modulemd lib and dnf info --- diff --git a/modularity/module_generator.py b/modularity/module_generator.py index 4dd855e..060f0db 100644 --- a/modularity/module_generator.py +++ b/modularity/module_generator.py @@ -1,170 +1,85 @@ -#!/bin/python - import os -import yaml -import urllib import subprocess -import re - -MODULE_TMPL = "template.yaml" +import modulemd +import dnf class ModuleGenerator(object): - gen_core_list = "gen_core_binary_pkgs.txt" - spec_url = "http://pkgs.fedoraproject.org/cgit/rpms/%s.git/plain/%s" - gen_core_url = "https://raw.githubusercontent.com/asamalik/fake-base-runtime-module-image/" \ - "master/packages/gen-core-binary-pkgs.txt" - def __init__(self, args): - self.module_yaml = None self.args = args - self.package_list = None - self.spec_data = {} + self.pkg = None + self.mmd = modulemd.ModuleMetadata() - def _save_template(self): + def _save_module_md(self): """ Function saves modulemd file to the current directory based on argument name :return: """ file_name = self.args.pkgs + '.yaml' - with open(file_name, 'w') as f: - try: - yaml.dump(self.module_yaml, f, default_flow_style=False) - except yaml.YAMLError as exc: - print(exc) - raise + self.mmd.dump(file_name) print('Modulemd file is generated here ./%s' % file_name) return True - def _load_template(self): + def _update_module_md(self): """ - Function loads modulmd template + Function updates modulemd file with dependencies + are information taken from SPEC file. :return: """ - if not os.path.exists(MODULE_TMPL): - return False - with open(MODULE_TMPL, 'r') as f: - try: - self.module_yaml = yaml.load(f) - except yaml.YAMLError as exc: - print(exc) - raise - return True - @staticmethod - def download_file(url, filename): - """ - Function checks if file exists and if not download them - :param url: URL of file to download - :param filename: downloaded filename - :return: - """ - if not os.path.exists(filename): - gen_core_list = urllib.URLopener() - gen_core_list.retrieve(url, filename) + self.mmd.summary = str(self.pkg.summary) + self.mmd.description = str(self.pkg.description) + self.mmd.add_module_license(str(self.pkg.license)) - def _load_gen_core_list(self): - """ - Function parses gen_core_list - :return: list of gen_core_packages - """ - ModuleGenerator.download_file(self.gen_core_url, - self.gen_core_list) - with open(self.gen_core_list, "r") as f: - try: - self.package_list = [l.strip('\n') for l in f.readlines()] - except IOError: - raise - - @staticmethod - def run_command(cmd): - """ - Function runs command - :param cmd: Command to run - :return: output, error - """ - process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) - output, error = process.communicate() - return output, error + for pkg in self.build_deps.intersection(self.run_deps): + self.mmd.components.add_rpm(pkg, "Build and runtime dependency.") - def _get_list_deps(self): - """ - Function gets dependencies for specific package which is defined as a argument. - Dependencies are filter out agains gen_core_list.txt file - :return: list of dependencies - """ - deps_list = [] - cmd = 'repoquery --requires --recursive --resolve --qf %{SOURCERPM} ' + self.args.pkgs - output, error = ModuleGenerator.run_command(cmd) - - output = [re.sub("-[^-]*-[^-]*$", "", l) for l in output.split('\n') if l] - for out in output: - if out not in self.package_list and out not in deps_list: - deps_list.append(out) - print('List of generated dependencies:\n%s' % '\n'.join(deps_list)) - return deps_list - - def _update_module_md(self, deps_list): - """ - Function updates modulemd file with dependencies - are information taken from SPEC file. - :param deps_list: List of dependencies - :return: - """ - yaml_data = self.module_yaml['data'] - if deps_list: - - if not yaml_data['components']['rpms']: - yaml_data['components']['rpms'] = {} - comp_rpms = yaml_data['components']['rpms'] - for pkg in deps_list: - deps_dict = {'rationale': 'build dependency', - 'ref': 'f25'} - comp_rpms[pkg] = deps_dict - yaml_data['version'] = self.spec_data['Version'].strip() - yaml_data['summary'] = self.spec_data['Summary'].strip() - yaml_data['description'] = self.spec_data['Summary'].strip() - yaml_data['license']['module'] = self.spec_data['License'].strip() - - @staticmethod - def parse_spec_data(data): - """ - Function parses SPEC file for specific fields. - :param data: SPEC file data - :return: dictionary with parsed data - """ - required_fields = ['Version', 'Summary', 'License', 'Name', 'Url'] - # select only import RPM data - spec_data = [d.split(':') for d in data for field in required_fields if d.startswith(field)] - # Create a dictionary - return dict(item for item in spec_data) + for pkg in (self.build_deps - self.run_deps): + self.mmd.components.add_rpm(pkg, "Build dependency.") - def _get_rpm_stuff(self): + for pkg in (self.run_deps - self.build_deps): + self.mmd.components.add_rpm(pkg, "Runtime dependency.") + + def _get_pkg_info(self): """ - Function gets RPM stuff from SPEC file. - :return: + Function loads package from dnf + :return: + """ + b = dnf.Base() + b.read_all_repos() + b.fill_sack() + + q = b.sack.query().filter(name=self.args.pkgs, reponame='fedora', latest=True) + + if len(q) > 1: + raise ValueError('Name of package is not unique') + if len(q) == 0: + raise ValueError('No package found in repo') + self.pkg = q[0] + + def _get_dependencies(self): """ - spec_file = self.args.pkgs + '.spec' - spec_url = self.spec_url % (self.args.pkgs, spec_file) - ModuleGenerator.download_file(spec_url, spec_file) - cmd = "rpmspec -P %s" % spec_file - output, error = ModuleGenerator.run_command(cmd) - if error is None: - with open(spec_file, "r") as f: - try: - lines = (line.strip() for line in f) - lines = list(line for line in lines if line) # Non-blank lines in a list - if lines: - self.spec_data = ModuleGenerator.parse_spec_data(lines) - except IOError: - raise + Function gets build and runtime dependencies of package + :return: + """ + subprocess.call(['/bin/bash', './brt_dep_solver.sh', self.args.pkgs]) - def run(self): - self._load_gen_core_list() - self._load_template() - self._get_rpm_stuff() - self._update_module_md(self._get_list_deps()) - self._save_template() + self.build_deps_file = self.args.pkgs + '-build-deps.txt' + self.run_deps_file = self.args.pkgs + '-runtime-deps.txt' + if not os.path.exists(self.build_deps_file) or not os.path.exists(self.run_deps_file): + raise IOError("Dependency file not found.") + + with open(self.build_deps_file, 'r') as f: + self.build_deps = set([l.strip('\n') for l in f.readlines()]) + + with open(self.run_deps_file, 'r') as f: + self.run_deps = set([l.strip('\n') for l in f.readlines()]) + + def run(self): + self._get_pkg_info() + self._get_dependencies() + self._update_module_md() + self._save_module_md() diff --git a/template.yaml b/template.yaml deleted file mode 100644 index 4837e38..0000000 --- a/template.yaml +++ /dev/null @@ -1,25 +0,0 @@ -document: modulemd -version: 1 -data: - summary: - description: - license: - module: - dependencies: - buildrequires: - base_runtime: master - requires: - base_runtime: master - references: - community: https://fedoraproject.org/wiki/Modularity - documentation: https://fedoraproject.org/wiki/Fedora_Packaging_Guidelines_for_Modules - tracker: - profiles: - default: - rpms: - minimal: - rpms: - api: - rpms: - components: - rpms: From 807c11a7388a365a6748c287397a81781a5db4f0 Mon Sep 17 00:00:00 2001 From: Dominika Hodovska Date: Apr 10 2017 12:27:02 +0000 Subject: [PATCH 3/3] Allow non-existence of deps files --- diff --git a/modularity/module_generator.py b/modularity/module_generator.py index 060f0db..0a70669 100644 --- a/modularity/module_generator.py +++ b/modularity/module_generator.py @@ -10,6 +10,8 @@ class ModuleGenerator(object): self.args = args self.pkg = None self.mmd = modulemd.ModuleMetadata() + self.build_deps = set() + self.run_deps = set() def _save_module_md(self): """ @@ -69,14 +71,13 @@ class ModuleGenerator(object): self.build_deps_file = self.args.pkgs + '-build-deps.txt' self.run_deps_file = self.args.pkgs + '-runtime-deps.txt' - if not os.path.exists(self.build_deps_file) or not os.path.exists(self.run_deps_file): - raise IOError("Dependency file not found.") + if os.path.exists(self.build_deps_file): + with open(self.build_deps_file, 'r') as f: + self.build_deps = set([l.strip('\n') for l in f.readlines()]) - with open(self.build_deps_file, 'r') as f: - self.build_deps = set([l.strip('\n') for l in f.readlines()]) - - with open(self.run_deps_file, 'r') as f: - self.run_deps = set([l.strip('\n') for l in f.readlines()]) + if os.path.exists(self.run_deps_file): + with open(self.run_deps_file, 'r') as f: + self.run_deps = set([l.strip('\n') for l in f.readlines()]) def run(self): self._get_pkg_info()