public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Owen W. Taylor <otaylor@fishsoup.net>
To: git-commits@fedoraproject.org
Subject: [rpms/rpkg] 1.70-1: Add patch from upstream pull-request to add a flatpak-build subcommand
Date: Mon, 10 Aug 2026 21:43:58 GMT	[thread overview]
Message-ID: <178639823828.1.10460689309413768079.rpms-rpkg-05492aceb1fa@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/rpkg
            Branch : 1.70-1
            Commit : 05492aceb1fad4c7dedafd945f302d11ba4717e3
            Author : Owen W. Taylor <otaylor@fishsoup.net>
            Date   : 2018-09-21T11:17:39-04:00
            Stats  : +1140/-2 in 2 file(s)
            URL    : https://src.fedoraproject.org/rpms/rpkg/c/05492aceb1fad4c7dedafd945f302d11ba4717e3?branch=1.70-1

            Log:
            Add patch from upstream pull-request to add a flatpak-build subcommand

See https://pagure.io/rpkg/pull-request/361

This also adds PyYAML dependencies - without this, the spec file didn't
work on EPEL6/EPEL7 and it was hard to test the conditionalization.

---
diff --git a/add-flatpak-build.patch b/add-flatpak-build.patch
new file mode 100644
index 0000000..d9aa8af
--- /dev/null
+++ b/add-flatpak-build.patch
@@ -0,0 +1,1110 @@
+diff --git a/etc/bash_completion.d/rpkg.bash b/etc/bash_completion.d/rpkg.bash
+index 9dd8de3..c564afc 100644
+--- a/etc/bash_completion.d/rpkg.bash
++++ b/etc/bash_completion.d/rpkg.bash
+@@ -34,8 +34,8 @@ _rpkg()
+ 
+     local options="--help -v -q"
+     local options_value="--dist --release --user --path"
+-    local commands="build chain-build ci clean clog clone co container-build container-build-config commit compile copr-build diff gimmespec giturl help \
+-    gitbuildhash import install lint local mockbuild mock-config new new-sources patch prep pull push scratch-build sources \
++    local commands="build chain-build ci clean clog clone co container-build container-build-config commit compile copr-build diff flatpak-build \
++    gimmespec giturl help gitbuildhash import install lint local mockbuild mock-config new new-sources patch prep pull push scratch-build sources \
+     srpm switch-branch tag unused-patches upload verify-files verrel"
+ 
+     # parse main options and get command
+@@ -152,6 +152,12 @@ _rpkg()
+             after="file"
+             after_more=true
+             ;;
++        flatpak-build)
++            options="--scratch --nowait"
++            options_target="--target"
++            options_string="--repo-url"
++            options_arches="--arches"
++            ;;
+         import)
+             options="--create"
+             options_branch="--branch"
+diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py
+index 696c9c3..aa23cbd 100644
+--- a/pyrpkg/__init__.py
++++ b/pyrpkg/__init__.py
+@@ -39,6 +39,7 @@ from six.moves import configparser
+ from six.moves import urllib
+ from six.moves.urllib.parse import urljoin
+ import requests
++import yaml
+ 
+ from pyrpkg.errors import HashtypeMixingError, rpkgError, rpkgAuthError, \
+     UnknownTargetError
+@@ -47,6 +48,13 @@ from pyrpkg.lookaside import CGILookasideCache
+ from pyrpkg.sources import SourcesFile
+ from pyrpkg.utils import cached_property, log_result, find_me
+ 
++PY26 = sys.version_info < (2, 7, 0)
++
++if not PY26:
++    import gi
++    gi.require_version('Modulemd', '1.0')  # raises ValueError
++    from gi.repository import Modulemd  # noqa
++
+ 
+ class NullHandler(logging.Handler):
+     """Null logger to avoid spurious messages, add a handler in app code"""
+@@ -156,6 +164,8 @@ class Commands(object):
+         self._target = target
+         # The build target for containers within the buildsystem
+         self._container_build_target = target
++        # The build target for flatpaks within the buildsystem
++        self._flatpak_build_target = target
+         # The top url to our build server
+         self._topurl = None
+         # The user to use or discover
+@@ -191,6 +201,8 @@ class Commands(object):
+         self.lookaside_namespaced = lookaside_namespaced
+         # Deprecates self.module_name
+         self._repo_name = None
++        # API URL for the module build server
++        self.module_api_url = None
+ 
+     # Define properties here
+     # Properties allow us to "lazy load" various attributes, which also means
+@@ -887,6 +899,99 @@ class Commands(object):
+         """This creates a target based on git branch and namespace."""
+         self._container_build_target = '%s-%s-candidate' % (self.branch_merge, self.ns)
+ 
++    @property
++    def flatpak_build_target(self):
++        """This property ensures the target for container builds."""
++        if not self._flatpak_build_target:
++            self.load_flatpak_build_target()
++        return self._flatpak_build_target
++
++    def _find_platform_stream(self, name, stream, version=None):
++        """Recursively search for the platform module in dependencies to find its stream.
++
++        The stream of the 'platform' pseudo-module determines what base package set
++        we need for the runtime - and thus what build target we need.
++        """
++
++        if version is not None:
++            nsvc = name + ':' + stream + ':' + version
++        else:
++            nsvc = name + ':' + stream
++
++        build = self.module_get_latest_build(nsvc)
++
++        if build is None:
++            raise rpkgError("Cannot find any builds for module %s:%s" %
++                            (name, stream))
++
++        mmd_str = build['modulemd']
++
++        objects = Modulemd.objects_from_string(mmd_str)
++        modules = [o for o in objects if isinstance(o,  Modulemd.Module)]
++        if len(modules) != 1:
++            raise rpkgError("Failed to load modulemd for %s" % nsvc)
++
++        # Streams should already be expanded in the modulemd's that we retrieve
++        # from MBS - modules were built against a particular dependency.
++        def get_stream(req, req_stream):
++            req_stream_list = req_stream.get()
++            if len(req_stream_list) != 1:
++                raise rpkgError("%s: stream list for '%s' is not expanded (%s)" %
++                                (nsvc, req, req_stream_list))
++            return req_stream_list[0]
++
++        # We first look for 'platform' as a direct dependency of this module,
++        # before recursing into the dependencies
++        for dep in modules[0].peek_dependencies():
++            for req, req_stream in dep.peek_requires().items():
++                if req == 'platform':
++                    return get_stream(req, req_stream)
++
++        # Now recurse into the dependencies
++        for dep in modules[0].peek_dependencies():
++            for req, req_stream in dep.peek_requires().items():
++                platform_stream = self._find_platform_stream(req,
++                                                             get_stream(req, req_stream))
++                if platform_stream:
++                    return platform_stream
++
++        return None
++
++    def load_flatpak_build_target(self):
++        """This locates a target appropriate for the runtime that the Flatpak targets."""
++
++        # Find the module we are going to build from container.yaml
++        yaml_path = os.path.join(self.path, "container.yaml")
++        if not os.path.exists(yaml_path):
++            raise rpkgError("Cannot find 'container.yaml' to determine build target.")
++
++        with open(yaml_path) as f:
++            container_yaml = yaml.safe_load(f)
++
++        compose = container_yaml.get('compose', {})
++        modules = compose.get('modules', [])
++        if not modules:
++            raise rpkgError("No modules listed in 'container.yaml'")
++        if len(modules) > 1:
++            raise rpkgError("Multiple modules listed in 'container.yaml'")
++        module = modules[0]
++
++        parts = module.split(':')
++        if len(parts) == 2:
++            name, stream = parts
++            version = None
++        elif len(parts) == 3:
++            name, stream, version = parts
++        else:
++            raise rpkgError("Module in container.yaml should be NAME:STREAM[:VERSION]")
++
++        platform_stream = self._find_platform_stream(name, stream, version=version)
++        if platform_stream is None:
++            raise rpkgError("Unable to find 'platform' module in the dependencies of '%s'; "
++                            "can't determine target" % module)
++
++        self._flatpak_build_target = '%s-flatpak-candidate' % platform_stream
++
+     @property
+     def topurl(self):
+         """This property ensures the topurl attribute"""
+@@ -2824,10 +2929,14 @@ class Commands(object):
+                              kojiconfig=None, kojiprofile=None,
+                              build_client=None,
+                              koji_task_watcher=None,
+-                             nowait=False):
++                             nowait=False,
++                             flatpak=False):
++
+         # check if repo is dirty and all commits are pushed
+         self.check_repo()
+-        container_target = self.target if target_override else self.container_build_target
++        container_target = self.target if target_override \
++            else self.flatpak_build_target if flatpak \
++            else self.container_build_target
+ 
+         # This is for backward-compatibility of deprecated kojiconfig.
+         # Signature of container_build_koji is not changed in case someone
+@@ -2871,6 +2980,9 @@ class Commands(object):
+                     raise rpkgError('Cannot override arches for non-scratch builds')
+                 task_opts['arch_override'] = ' '.join(arches)
+ 
++            if flatpak:
++                task_opts['flatpak'] = True
++
+             priority = opts.get("priority", None)
+             task_id = self.kojisession.buildContainer(source,
+                                                       container_target,
+@@ -2933,13 +3045,12 @@ class Commands(object):
+         cmd.extend([project, srpm_name])
+         self._run_command(cmd)
+ 
+-    def module_build_cancel(self, api_url, build_id, auth_method,
++    def module_build_cancel(self, build_id, auth_method,
+                             oidc_id_provider=None, oidc_client_id=None,
+                             oidc_client_secret=None, oidc_scopes=None):
+         """
+         Cancel an MBS build
+ 
+-        :param str api_url: URL of the MBS API
+         :param int build_id: build ID to cancel
+         :param str auth_method: authentication method used by the MBS
+         :kwarg str oidc_id_provider: the OIDC provider when MBS is using OIDC
+@@ -2953,8 +3064,8 @@ class Commands(object):
+             for authentication
+         """
+         # Make sure the build they are trying to cancel exists
+-        self.module_get_build(api_url, build_id)
+-        url = self.module_get_url(api_url, build_id, action='PATCH')
++        self.module_get_build(build_id)
++        url = self.module_get_url(build_id, action='PATCH')
+         resp = self.module_send_authorized_request(
+             'PATCH', url, {'state': 'failed'}, auth_method, oidc_id_provider,
+             oidc_client_id, oidc_client_secret, oidc_scopes, timeout=60)
+@@ -2967,18 +3078,17 @@ class Commands(object):
+                 'The cancellation of module build #{0} failed with:\n{1}'
+                 .format(build_id, error_msg))
+ 
+-    def module_build_info(self, api_url, build_id):
++    def module_build_info(self, build_id):
+         """
+         Show information about an MBS build
+ 
+-        :param str api_url: URL of the MBS API
+         :param int build_id: build ID to query MBS about
+         """
+         # Load the Koji session anonymously so we get access to the Koji web
+         # URL
+         self.load_kojisession(anon=True)
+         state_names = self.module_get_koji_state_dict()
+-        data = self.module_get_build(api_url, build_id)
++        data = self.module_get_build(build_id)
+         print('Name:           {0}'.format(data['name']))
+         print('Stream:         {0}'.format(data['stream']))
+         print('Version:        {0}'.format(data['version']))
+@@ -3000,14 +3110,13 @@ class Commands(object):
+                 state_names[task_data.get('state', None)]))
+             print('    Koji Task:  {0}\n'.format(koji_task_url))
+ 
+-    def module_get_build(self, api_url, build_id):
++    def module_get_build(self, build_id):
+         """
+         Get an MBS build
+-        :param api_url: a string of the URL of the MBS API
+         :param build_id: an integer of the build ID to query MBS about
+         :return: None or a dictionary representing the module build
+         """
+-        url = self.module_get_url(api_url, build_id)
++        url = self.module_get_url(build_id)
+         response = requests.get(url, timeout=60)
+         if response.ok:
+             return response.json()
+@@ -3020,11 +3129,42 @@ class Commands(object):
+                 'The following error occurred while getting information on '
+                 'module build #{0}:\n{1}'.format(build_id, error_msg))
+ 
+-    def module_get_url(self, api_url, build_id, action='GET'):
++    def module_get_latest_build(self, nsvc):
++        """
++        Get the latest MBS build for a particular module. If the module is
++        built with multiple contexts, a random one will be returned.
++
++        :param nsvc: a NAME:STREAM:VERSION:CONTEXT to filter the query
++               (may be partial - e.g. only NAME or only NAME:STREAM)
++        :return: the latest build
++        """
++        url = self.module_get_url(None)
++        params = {
++            'nsvc': nsvc,
++            'order_desc_by': 'version',
++            'per_page': 1
++        }
++
++        response = requests.get(url, timeout=60, params=params)
++        if response.ok:
++            j = response.json()
++            if len(j['items']) == 0:
++                return None
++            else:
++                return j['items'][0]
++        else:
++            try:
++                error_msg = response.json()['message']
++            except (ValueError, KeyError):
++                error_msg = response.text
++            raise rpkgError(
++                'The following error occurred while getting information on '
++                'module #{0}:\n{1}'.format(nsvc, error_msg))
++
++    def module_get_url(self, build_id, action='GET'):
+         """
+         Get the proper MBS API URL for the desired action
+ 
+-        :param str api_url: a string of the URL of the MBS API
+         :param int build_id: an integer of the module build desired. If this is
+             set to None, then the base URL for all module builds is returned.
+         :param str action: a string determining the HTTP action. If this is set
+@@ -3033,7 +3173,7 @@ class Commands(object):
+         :return: a string of the desired MBS API URL.
+         :rtype: str
+         """
+-        url = urljoin(api_url, 'module-builds/')
++        url = urljoin(self.module_api_url, 'module-builds/')
+         if build_id is not None:
+             url = '{0}{1}'.format(url, build_id)
+         else:
+@@ -3172,11 +3312,10 @@ class Commands(object):
+             else:
+                 raise
+ 
+-    def module_overview(self, api_url, limit=10, finished=True):
++    def module_overview(self, limit=10, finished=True):
+         """
+         Show the overview of the latest builds in MBS
+ 
+-        :param str api_url: a string of the URL of the MBS API
+         :param int limit: an integer of the number of most recent module builds
+             to display. This defaults to 10.
+         :param bool finished: a boolean that determines if only finished or
+@@ -3193,7 +3332,7 @@ class Commands(object):
+             'failed': 4,
+             'ready': 5,
+         }
+-        baseurl = self.module_get_url(api_url, build_id=None)
++        baseurl = self.module_get_url(build_id=None)
+         if finished:
+             # These are the states when a build is finished
+             states = [build_states['done'], build_states['ready'],
+@@ -3339,14 +3478,13 @@ class Commands(object):
+             raise rpkgError('An unsupported MBS "auth_method" was provided')
+         return resp
+ 
+-    def module_submit_build(self, api_url, scm_url, branch, auth_method,
++    def module_submit_build(self, scm_url, branch, auth_method,
+                             optional=None, oidc_id_provider=None,
+                             oidc_client_id=None, oidc_client_secret=None,
+                             oidc_scopes=None):
+         """
+         Submit a module build to the MBS
+ 
+-        :param api_url: a string of the URL of the MBS API
+         :param scm_url: a string of the module's SCM URL
+         :param branch: a string of the module's branch
+         :param str auth_method: a string of the authentication method used by
+@@ -3378,7 +3516,7 @@ class Commands(object):
+                 'Optional arguments are not in the proper "key=value" format')
+ 
+         body.update(optional_dict)
+-        url = self.module_get_url(api_url, build_id=None, action='POST')
++        url = self.module_get_url(build_id=None, action='POST')
+         resp = self.module_send_authorized_request(
+             'POST', url, body, auth_method, oidc_id_provider, oidc_client_id,
+             oidc_client_secret, oidc_scopes, timeout=120)
+@@ -3399,13 +3537,12 @@ class Commands(object):
+         builds = data if isinstance(data, list) else [data]
+         return [build['id'] for build in builds]
+ 
+-    def module_watch_build(self, api_url, build_ids):
++    def module_watch_build(self, build_ids):
+         """
+         Watches the first MBS build in the list in a loop that updates every 15
+         seconds. The loop ends when the build state is 'failed', 'done', or
+         'ready'.
+ 
+-        :param str api_url: a string of the URL of the MBS API
+         :param build_ids: a list of module build IDs
+         :type build_ids: list[int]
+         """
+@@ -3423,7 +3560,7 @@ class Commands(object):
+         done = False
+         while not done:
+             state_names = self.module_get_koji_state_dict()
+-            build = self.module_get_build(api_url, build_id)
++            build = self.module_get_build(build_id)
+             tasks = {}
+             if 'rpms' in build['tasks']:
+                 tasks = build['tasks']['rpms']
+@@ -3494,6 +3631,10 @@ class Commands(object):
+         :param api_url: a string of the URL of the MBS API
+         :return: an int of the API version
+         """
++
++        # We don't use self.module_api_url since this is used exclusively by the code
++        # that is loading and validating the API URL before setting it.
++
+         url = '{0}/about/'.format(api_url.rstrip('/'))
+         response = requests.get(url, timeout=60)
+         if response.ok:
+diff --git a/pyrpkg/cli.py b/pyrpkg/cli.py
+index 44beb9c..36fe710 100644
+--- a/pyrpkg/cli.py
++++ b/pyrpkg/cli.py
+@@ -31,7 +31,7 @@ import koji_cli.lib
+ import pyrpkg.utils as utils
+ import six
+ 
+-from pyrpkg import rpkgError, log as rpkgLogger
++from pyrpkg import PY26, rpkgError, log as rpkgLogger
+ from six.moves import configparser
+ 
+ 
+@@ -424,6 +424,7 @@ class cliClient(object):
+ 
+         # Add a common parsers
+         self.register_build_common()
++        self.register_container_build_common()
+         self.register_rpm_common()
+ 
+         # Other targets
+@@ -438,6 +439,8 @@ class cliClient(object):
+         self.register_container_build()
+         self.register_container_build_setup()
+         self.register_diff()
++        if not PY26:
++            self.register_flatpak_build()
+         self.register_gimmespec()
+         self.register_gitbuildhash()
+         self.register_gitcred()
+@@ -1379,11 +1382,54 @@ see API KEY section of copr-cli(1) man page.
+             'verrel', help='Print the name-version-release')
+         verrel_parser.set_defaults(command=self.verrel)
+ 
++    def register_container_build_common(self):
++        parser = ArgumentParser(
++            'container_build_common', add_help=False, allow_abbrev=False)
++
++        self.container_build_parser_common = parser
++
++        parser.add_argument(
++            '--target',
++            help='Override the default target',
++            default=None)
++
++        parser.add_argument(
++            '--nowait',
++            action='store_true',
++            default=False,
++            help="Don't wait on build")
++
++        parser.add_argument(
++            '--scratch',
++            help='Scratch build',
++            action="store_true")
++
++        parser.add_argument(
++            '--arches',
++            action='store',
++            nargs='*',
++            help='Limit a scratch build to an arch. May have multiple arches.')
++
++        parser.add_argument(
++            '--skip-remote-rules-validation',
++            action='store_true',
++            default=False,
++            help="Don't check if there's a valid gating.yaml file in the repo")
++
+     def register_container_build(self):
+         self.container_build_parser = self.subparsers.add_parser(
+             'container-build',
+             help='Build a container',
+-            description='Build a container')
++            description='Build a container',
++            parents=[self.container_build_parser_common])
++
++        # These arguments are specific to non-Flatpak containers
++        #
++        # --compose-id is implemented for Flatpaks as a side-effect of the internal
++        #      implementation, but it is unlikely to be useful to trigger through rpkg.
++        # -- signing-intent is not implemented for Flatpaks, though it could be useful
++        # --repo-url makes no sense for flaptaks, since they must be built from a
++        #      compose of a single module.
+ 
+         group = self.container_build_parser.add_mutually_exclusive_group()
+         group.add_argument(
+@@ -1405,35 +1451,16 @@ see API KEY section of copr-cli(1) man page.
+                                'Cannot be used with --signing-intent or --compose-id',
+                           nargs='+')
+ 
+-        self.container_build_parser.add_argument(
+-            '--target',
+-            help='Override the default target',
+-            default=None)
+-
+-        self.container_build_parser.add_argument(
+-            '--nowait',
+-            action='store_true',
+-            default=False,
+-            help="Don't wait on build")
+-
+-        self.container_build_parser.add_argument(
+-            '--scratch',
+-            help='Scratch build',
+-            action="store_true")
+-
+-        self.container_build_parser.add_argument(
+-            '--arches',
+-            action='store',
+-            nargs='*',
+-            help='Limit a scratch build to an arch. May have multiple arches.')
++        self.container_build_parser.set_defaults(command=self.container_build)
+ 
+-        self.container_build_parser.add_argument(
+-            '--skip-remote-rules-validation',
+-            action='store_true',
+-            default=False,
+-            help="Don't check if there's a valid gating.yaml file in the repo")
++    def register_flatpak_build(self):
++        self.flatpak_build_parser = self.subparsers.add_parser(
++            'flatpak-build',
++            help='Build a Flatpak',
++            description='Build a Flatpak',
++            parents=[self.container_build_parser_common])
+ 
+-        self.container_build_parser.set_defaults(command=self.container_build)
++        self.flatpak_build_parser.set_defaults(command=self.flatpak_build)
+ 
+     def register_container_build_setup(self):
+         self.container_build_setup_parser = \
+@@ -1762,7 +1789,7 @@ see API KEY section of copr-cli(1) man page.
+         # Keep it around for backward compatibility
+         self.container_build()
+ 
+-    def container_build(self):
++    def container_build(self, flatpak=False):
+         target_override = False
+         # Override the target if we were supplied one
+         if self.args.target:
+@@ -1771,11 +1798,15 @@ see API KEY section of copr-cli(1) man page.
+ 
+         opts = {"scratch": self.args.scratch,
+                 "quiet": self.args.q,
+-                "yum_repourls": self.args.repo_url,
+                 "git_branch": self.cmd.branch_merge,
+-                "arches": self.args.arches,
++                "arches": self.args.arches}
++
++        if not flatpak:
++            opts.update({
++                "yum_repourls": self.args.repo_url,
+                 "compose_ids": self.args.compose_ids,
+-                "signing_intent": self.args.signing_intent}
++                "signing_intent": self.args.signing_intent,
++            })
+ 
+         section_name = "%s.container-build" % self.name
+         err_msg = "Missing %(option)s option in [%(plugin.section)s] section. " \
+@@ -1808,6 +1839,10 @@ see API KEY section of copr-cli(1) man page.
+ 
+         self.check_remote_rules_gating()
+ 
++        # We use MBS to find information about the module to build into a Flatpak
++        if flatpak:
++            self.set_module_api_url()
++
+         self.cmd.container_build_koji(
+             target_override,
+             opts=opts,
+@@ -1815,7 +1850,11 @@ see API KEY section of copr-cli(1) man page.
+             kojiprofile=kojiprofile,
+             build_client=build_client,
+             koji_task_watcher=koji_cli.lib.watch_tasks,
+-            nowait=self.args.nowait)
++            nowait=self.args.nowait,
++            flatpak=flatpak)
++
++    def flatpak_build(self):
++        self.container_build(flatpak=True)
+ 
+     def container_build_setup(self):
+         self.cmd.container_build_setup(get_autorebuild=self.args.get_autorebuild,
+@@ -1923,7 +1962,7 @@ see API KEY section of copr-cli(1) man page.
+ 
+     def module_build(self):
+         """Builds a module using MBS"""
+-        api_url = self.module_api_url
++        self.set_module_api_url()
+         self.module_validate_config()
+         scm_url, branch = self.cmd.module_get_scm_info(
+             self.args.scm_url, self.args.branch)
+@@ -1933,7 +1972,7 @@ see API KEY section of copr-cli(1) man page.
+         if not self.args.q:
+             print('Submitting the module build...')
+         build_ids = self._cmd.module_submit_build(
+-            api_url, scm_url, branch, auth_method, self.args.optional,
++            scm_url, branch, auth_method, self.args.optional,
+             oidc_id_provider, oidc_client_id, oidc_client_secret, oidc_scopes)
+         if self.args.watch:
+             self.module_watch_build(build_ids)
+@@ -1948,7 +1987,7 @@ see API KEY section of copr-cli(1) man page.
+ 
+     def module_build_cancel(self):
+         """Cancel an MBS build"""
+-        api_url = self.module_api_url
++        self.set_module_api_url()
+         build_id = self.args.build_id
+         auth_method, oidc_id_provider, oidc_client_id, oidc_client_secret, \
+             oidc_scopes = self.module_get_auth_config()
+@@ -1956,14 +1995,15 @@ see API KEY section of copr-cli(1) man page.
+         if not self.args.q:
+             print('Cancelling module build #{0}...'.format(build_id))
+         self.cmd.module_build_cancel(
+-            api_url, build_id, auth_method, oidc_id_provider, oidc_client_id,
++            build_id, auth_method, oidc_id_provider, oidc_client_id,
+             oidc_client_secret, oidc_scopes)
+         if not self.args.q:
+                 print('The module build #{0} was cancelled'.format(build_id))
+ 
+     def module_build_info(self):
+         """Show information about an MBS build"""
+-        self.cmd.module_build_info(self.module_api_url, self.args.build_id)
++        self.set_module_api_url()
++        self.cmd.module_build_info(self.args.build_id)
+ 
+     def module_build_local(self):
+         """Build a module locally using mbs-manager"""
+@@ -2072,14 +2112,18 @@ see API KEY section of copr-cli(1) man page.
+                 api_url.rstrip('/'), api_version)
+         return self._module_api_url
+ 
++    def set_module_api_url(self):
++        self.cmd.module_api_url = self.module_api_url
++
+     def module_build_watch(self):
+         """Watch an MBS build from the command-line"""
+         self.module_watch_build([self.args.build_id])
+ 
+     def module_overview(self):
+         """Show the overview of the latest builds in the MBS"""
++        self.set_module_api_url()
+         self.cmd.module_overview(
+-            self.module_api_url, self.args.limit,
++            self.args.limit,
+             finished=(not self.args.unfinished))
+ 
+     def module_validate_config(self):
+@@ -2134,7 +2178,8 @@ see API KEY section of copr-cli(1) man page.
+         :param build_ids: a list of module build IDs
+         :type build_ids: list[int]
+         """
+-        self.cmd.module_watch_build(self.module_api_url, build_ids)
++        self.set_module_api_url()
++        self.cmd.module_watch_build(build_ids)
+ 
+     def new(self):
+         new_diff = self.cmd.new()
+diff --git a/requirements/fedora-py2.txt b/requirements/fedora-py2.txt
+index 0cf79de..e97c9c8 100644
+--- a/requirements/fedora-py2.txt
++++ b/requirements/fedora-py2.txt
+@@ -1,10 +1,13 @@
++libmodulemd
+ python2-cccolutils
+ python2-GitPython
++python2-gobject-base
+ python2-koji
+ python2-pycurl
+ python-six
+ python2-rpm  # rpm-python originally
+ python2-requests
++PyYAML
+ # python2-openidc-client # used for MBS OIDC authentication
+ # python2-requests-kerberos # used for MBS Kerberos authentication
+ 
+diff --git a/requirements/fedora-py3.txt b/requirements/fedora-py3.txt
+index cc99912..cb71549 100644
+--- a/requirements/fedora-py3.txt
++++ b/requirements/fedora-py3.txt
+@@ -1,10 +1,13 @@
++libmodulemd
+ python3-cccolutils
+ python3-GitPython
++python3-gobject-base
+ python3-koji
+ python3-pycurl
+ python3-six
+ python3-rpm  # rpm-python originally
+ python3-requests
++python3-yaml
+ # python3-openidc-client # used for MBS OIDC authentication
+ # python3-requests-kerberos # used for MBS Kerberos authentication
+ 
+diff --git a/requirements/pypi.txt b/requirements/pypi.txt
+index 5d35f3b..e4c43bb 100644
+--- a/requirements/pypi.txt
++++ b/requirements/pypi.txt
+@@ -8,6 +8,7 @@ koji >= 1.15
+ pycurl >= 7.19
+ requests
+ six >= 1.9.0
++PyYAML
+ 
+ # rpm-py-installer
+ #
+diff --git a/tests/test_cli.py b/tests/test_cli.py
+index ea35afe..745a97c 100644
+--- a/tests/test_cli.py
++++ b/tests/test_cli.py
+@@ -23,6 +23,7 @@ from six.moves import http_client
+ 
+ import git
+ import pyrpkg.cli
++from pyrpkg import PY26
+ 
+ try:
+     import openidc_client
+@@ -197,7 +198,7 @@ class TestContainerBuildWithKoji(CliTestCase):
+             self.container_build_koji_patcher.start()
+ 
+     def tearDown(self):
+-        self.mock_container_build_koji.stop()
++        self.container_build_koji_patcher.stop()
+         super(TestContainerBuildWithKoji, self).tearDown()
+ 
+     def test_using_kojiprofile(self):
+@@ -223,7 +224,8 @@ class TestContainerBuildWithKoji(CliTestCase):
+             kojiprofile='koji',
+             build_client=utils.build_client,
+             koji_task_watcher=koji_cli.lib.watch_tasks,
+-            nowait=False
++            nowait=False,
++            flatpak=False
+         )
+ 
+     def test_override_target(self):
+@@ -250,7 +252,8 @@ class TestContainerBuildWithKoji(CliTestCase):
+             kojiprofile='koji',
+             build_client=utils.build_client,
+             koji_task_watcher=koji_cli.lib.watch_tasks,
+-            nowait=False
++            nowait=False,
++            flatpak=False
+         )
+ 
+     def test_using_deprecated_kojiconfig(self):
+@@ -286,7 +289,8 @@ class TestContainerBuildWithKoji(CliTestCase):
+             kojiprofile=None,
+             build_client=utils.build_client,
+             koji_task_watcher=koji_cli.lib.watch_tasks,
+-            nowait=False
++            nowait=False,
++            flatpak=False
+         )
+ 
+     def test_use_container_build_own_config(self):
+@@ -304,6 +308,43 @@ class TestContainerBuildWithKoji(CliTestCase):
+         self.assertEqual('koji-container', kwargs['kojiprofile'])
+         self.assertEqual('koji', kwargs['build_client'])
+ 
++    @unittest.skipIf(
++        PY26,
++        'Skip on old Python versions where libmodulemd is not available.')
++    @patch('requests.get')
++    def test_flatpak(self, mock_get):
++        mock_rv = Mock()
++        mock_rv.ok = True
++        mock_rv.json.return_value = {
++            'auth_method': 'oidc',
++            'api_version': 2
++        }
++
++        mock_get.return_value = mock_rv
++
++        cli_cmd = ['rpkg', '--path', self.cloned_repo_path,
++                   'flatpak-build']
++
++        with patch('sys.argv', new=cli_cmd):
++            cli = self.new_cli()
++            cli.flatpak_build()
++
++        self.mock_container_build_koji.assert_called_once_with(
++            False,
++            opts={
++                'scratch': False,
++                'quiet': False,
++                'git_branch': 'eng-rhel-7',
++                'arches': None,
++            },
++            kojiconfig=None,
++            kojiprofile='koji',
++            build_client=utils.build_client,
++            koji_task_watcher=koji_cli.lib.watch_tasks,
++            nowait=False,
++            flatpak=True
++        )
++
+ 
+ class TestClog(CliTestCase):
+ 
+diff --git a/tests/test_flatpak_build.py b/tests/test_flatpak_build.py
+new file mode 100644
+index 0000000..97ab456
+--- /dev/null
++++ b/tests/test_flatpak_build.py
+@@ -0,0 +1,305 @@
++import os
++import subprocess
++from textwrap import dedent
++
++try:
++    import unittest2 as unittest
++except ImportError:
++    import unittest
++
++from mock import Mock, patch
++import requests
++
++from pyrpkg import PY26
++from utils import CommandTestCase
++
++EOG_MODULEMD = """
++document: modulemd
++version: 2
++data:
++  name: eog
++  stream: f28
++  version: 20170629213428
++  summary: Eye of GNOME Application Module
++  description: The Eye of GNOME image viewer (eog) is the official image viewer for
++    the GNOME desktop. It can view single image files in a variety of formats, as
++    well as large image collections.
++  license:
++    module: [MIT]
++  dependencies:
++  - buildrequires:
++      flatpak-runtime: [f28]
++    requires:
++      flatpak-runtime: [f28]
++  profiles:
++    default:
++      rpms: [eog]
++  components:
++    rpms: {}
++  xmd:
++    mbs: OMITTED
++"""
++
++FLATPAK_RUNTIME_MODULEMD = """
++document: modulemd
++version: 2
++data:
++  name: flatpak-runtime
++  stream: f28
++  version: 20170701152209
++  summary: Flatpak Runtime
++  description: Libraries and data files shared between applications
++  api:
++    rpms: [librsvg2, gnome-themes-standard, abattis-cantarell-fonts, rest, xkeyboard-config,
++      adwaita-cursor-theme, python3-gobject-base, json-glib, zenity, gsettings-desktop-schemas,
++      glib-networking, gobject-introspection, gobject-introspection-devel, flatpak-rpm-macros,
++      python3-gobject, gvfs-client, colord-libs, flatpak-runtime-config, hunspell-en-GB,
++      libsoup, glib2-devel, hunspell-en-US, at-spi2-core, gtk3, libXtst, adwaita-gtk2-theme,
++      libnotify, adwaita-icon-theme, libgcab1, libxkbcommon, libappstream-glib, python3-cairo,
++      gnome-desktop3, libepoxy, hunspell, libgusb, glib2, enchant, at-spi2-atk]
++  dependencies:
++  - buildrequires:
++      platform: [f28]
++    requires:
++      platform: [f28]
++  license:
++    module: [MIT]
++  profiles:
++    buildroot:
++      rpms: [flatpak-rpm-macros, flatpak-runtime-config]
++    runtime:
++      rpms: [libwayland-server, librsvg2, libX11, libfdisk, adwaita-cursor-theme,
++        libsmartcols, popt, gdbm, libglvnd, openssl-libs, gobject-introspection, systemd,
++        ncurses-base, lcms2, libpcap, crypto-policies, fontconfig, libacl, libwayland-cursor,
++        libseccomp, gmp, jbigkit-libs, bzip2-libs, libunistring, freetype, nettle,
++        libidn, python3-six, gtk2, gtk3, ca-certificates, libdrm, rest, lzo, libcap,
++        gnutls, pango, util-linux, basesystem, p11-kit, libgcab1, iptables-libs, dbus,
++        python3-gobject-base, cryptsetup-libs, krb5-libs, sqlite-libs, kmod-libs,
++        libmodman, libarchive, enchant, libXfixes, systemd-libs, shared-mime-info,
++        coreutils-common, libglvnd-glx, abattis-cantarell-fonts, cairo, audit-libs,
++        libwayland-client, libpciaccess, sed, libgcc, libXrender, json-glib, libxshmfence,
++        glib-networking, libdb, fedora-modular-repos, keyutils-libs, hwdata, glibc,
++        libproxy, python3-pyparsing, device-mapper, libgpg-error, system-python, shadow-utils,
++        libXtst, libstemmer, dbus-libs, libpng, cairo-gobject, libXau, pcre, python3-packaging,
++        at-spi2-core, gawk, mesa-libglapi, libXinerama, adwaita-gtk2-theme, libX11-common,
++        device-mapper-libs, python3-appdirs, libXrandr, bash, glibc-common, libselinux,
++        elfutils-libs, libxkbcommon, libjpeg-turbo, libuuid, atk, acl, libmount, lz4-libs,
++        ncurses, libgusb, glib2, python3, libpwquality, at-spi2-atk, libattr, libcrypt,
++        gnome-themes-standard, libtiff, harfbuzz, libstdc++, libXcomposite, xkeyboard-config,
++        libxcb, libnotify, systemd-pam, readline, libXxf86vm, python3-cairo, gtk-update-icon-cache,
++        python3-pip, mesa-libEGL, zenity, python3-gobject, libXcursor, tzdata, gvfs-client,
++        libverto, libblkid, cracklib, libusbx, libcroco, libdatrie, gdk-pixbuf2, libXi,
++        qrencode-libs, python3-libs, graphite2, mesa-libwayland-egl, mesa-libGL, pixman,
++        libXext, glibc-all-langpacks, info, grep, fedora-modular-release, setup, zlib,
++        libtasn1, libepoxy, hunspell, libsemanage, python3-setuptools, fontpackages-filesystem,
++        libsigsegv, hicolor-icon-theme, libxml2, expat, libgcrypt, emacs-filesystem,
++        gsettings-desktop-schemas, chkconfig, xz-libs, mesa-libgbm, libthai, coreutils,
++        colord-libs, libcap-ng, flatpak-runtime-config, elfutils-libelf, hunspell-en-GB,
++        libsoup, pam, hunspell-en-US, jasper-libs, p11-kit-trust, avahi-libs, elfutils-default-yama-scope,
++        libutempter, adwaita-icon-theme, ncurses-libs, libidn2, system-python-libs,
++        libffi, libXdamage, libglvnd-egl, libXft, cups-libs, ustr, libcom_err, libappstream-glib,
++        gnome-desktop3, gdk-pixbuf2-modules, libsepol, filesystem, gzip, mpfr]
++    sdk:
++      rpms: [gcc]
++  components:
++    rpms: {}
++  xmd:
++    flatpak:
++      # This gives information about how to map this module into Flatpak terms
++      # this is used when building application modules against this module.
++      branch: f28
++      runtimes: # Keys are profile names
++        runtime:
++          id: org.fedoraproject.Platform
++          sdk: org.fedoraproject.Sdk
++        sdk:
++          id: org.fedoraproject.Sdk
++          runtime: org.fedoraproject.Platform
++    mbs: OMITTED
++"""  # noqa
++
++UNEXPANDED_MODULEMD = """
++document: modulemd
++version: 2
++data:
++  name: nodeps
++  stream: f28
++  version: 20181234567890
++  summary: No dependencies
++  description: This module has no deps
++  license:
++    module: [MIT]
++  dependencies:
++  - buildrequires:
++      platform: [f27, f28]
++    requires:
++      platform: [f27, f28]
++  components:
++    rpms: {}
++"""
++
++NODEPS_MODULEMD = """
++document: modulemd
++version: 2
++data:
++  name: nodeps
++  stream: f28
++  version: 20181234567890
++  summary: No dependencies
++  description: This module has no deps
++  license:
++    module: [MIT]
++  dependencies: []
++  components:
++    rpms: {}
++"""
++
++BUILDS = {
++    'eog:f28': [
++        {'modulemd': EOG_MODULEMD}
++    ],
++    'eog:f28:20170629213428': [
++        {'modulemd': EOG_MODULEMD}
++    ],
++    'flatpak-runtime:f28': [
++        {'modulemd': FLATPAK_RUNTIME_MODULEMD}
++    ],
++    'bad-modulemd:f28': [
++        {'modulemd': "BLAH"}
++    ],
++    'unexpanded:f28': [
++        {'modulemd': UNEXPANDED_MODULEMD}
++    ],
++    'nodeps:f28': [
++        {'modulemd': NODEPS_MODULEMD}
++    ],
++}
++
++
++@unittest.skipIf(
++    PY26,
++    'Skip on old Python versions where libmodulemd is not available.')
++class FlatpakBuildCase(CommandTestCase):
++    def set_container_modules(self, container_modules):
++        with open(os.path.join(self.repo_path, 'container.yaml'), 'w') as f:
++            f.write(dedent("""\
++                compose:
++                    modules: {0}
++                """.format(container_modules)))
++        git_cmds = [
++            ['git', 'add', 'container.yaml'],
++            ['git', 'commit', '-m', 'Update container.yaml'],
++        ]
++        for cmd in git_cmds:
++            self.run_cmd(cmd, cwd=self.repo_path,
++                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
++        git_cmds = [
++            ['git', 'fetch', 'origin'],
++            ['git', 'reset', '--hard', 'origin/master'],
++        ]
++        for cmd in git_cmds:
++            self.run_cmd(cmd, cwd=self.cloned_repo_path,
++                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
++
++    def setUp(self):
++        super(FlatpakBuildCase, self).setUp()
++
++        self.cmd = self.make_commands()
++        self.cmd.module_api_url = "https://mbs.example.com/module-build-service/1/"
++
++        self.requests_get_p = patch('requests.get')
++        self.mock_requests_get = self.requests_get_p.start()
++
++        def mock_get(url, params=None, timeout=None):
++            nsvc = params['nsvc']
++            del params['nsvc']
++            self.assertEquals(params, {
++                'order_desc_by': 'version',
++                'per_page': 1
++            })
++
++            response = Mock(requests.Response)
++            response.ok = True
++            response.json.return_value = {'items': BUILDS.get(nsvc, [])}
++
++            return response
++
++        self.mock_requests_get.side_effect = mock_get
++
++        self.load_krb_user_p = patch('pyrpkg.Commands._load_krb_user')
++        self.mock_load_krb_user = self.load_krb_user_p.start()
++
++        session = Mock()
++        self.kojisession = session
++        session.system.listMethods.return_value = ['buildContainer']
++
++        def load_kojisession(self):
++            self._kojisession = session
++
++        self.load_kojisession_p = patch('pyrpkg.Commands.load_kojisession',
++                                        new=load_kojisession)
++        self.mock_load_kojisession = self.load_kojisession_p.start()
++
++        session.getBuildTarget.return_value = {'dest_tag': 'f28-flatpak'}
++        session.getTag.return_value = {'locked': False}
++
++    def tearDown(self):
++        self.requests_get_p.stop()
++        self.load_krb_user_p.stop()
++        self.load_kojisession_p.stop()
++
++        super(FlatpakBuildCase, self).tearDown()
++
++    def test_find_target(self):
++        self.set_container_modules(['eog:f28'])
++        assert self.cmd.flatpak_build_target == 'f28-flatpak-candidate'
++
++    def test_find_target_version(self):
++        self.set_container_modules(['eog:f28:20170629213428'])
++        assert self.cmd.flatpak_build_target == 'f28-flatpak-candidate'
++
++    def module_failure(self, container_modules, exception_str):
++        if container_modules is not None:
++            self.set_container_modules(container_modules)
++        with self.assertRaises(Exception) as e:
++            self.cmd.load_flatpak_build_target()
++        self.assertIn(exception_str, str(e.exception))
++
++    def test_find_target_no_container_yaml(self):
++        self.module_failure(None, "Cannot find 'container.yaml'")
++
++    def test_find_target_no_modules(self):
++        self.module_failure([], "No modules listed in 'container.yaml'")
++
++    def test_find_target_multiple_modules(self):
++        self.module_failure(['eog:f28', 'foo:f28'],
++                            "Multiple modules listed in 'container.yaml'")
++
++    def test_find_target_bad_nsv(self):
++        self.module_failure(['NOT_A_MODULE'], "should be NAME:STREAM[:VERSION]")
++
++    def test_find_target_no_builds(self):
++        self.module_failure(['eog:f1'], "Cannot find any builds for module")
++
++    def test_find_target_bad_modulemd(self):
++        self.module_failure(['bad-modulemd:f28'], "Failed to load modulemd")
++
++    def test_find_target_unexpected(self):
++        self.module_failure(['unexpanded:f28'], "stream list for 'platform' is not expanded")
++
++    def test_find_target_no_platform(self):
++        self.module_failure(['nodeps:f28'], "Unable to find 'platform' module in the dependencies")
++
++    def test_flatpak_build(self):
++        self.set_container_modules(['eog:f28'])
++        self.cmd.container_build_koji(nowait=True, flatpak=True)
++
++        session = self.kojisession
++        session.getBuildTarget.assert_called_with('f28-flatpak-candidate')
++        session.getTag.assert_called_with('f28-flatpak')
++
++        session.buildContainer.assert_called()
++        args, kwargs = session.buildContainer.call_args
++        source, container_target, taskinfo = args
++
++        assert container_target == 'f28-flatpak-candidate'

diff --git a/rpkg.spec b/rpkg.spec
index e10c623..72bbbc4 100644
--- a/rpkg.spec
+++ b/rpkg.spec
@@ -4,7 +4,7 @@
 
 Name:           rpkg
 Version:        1.56
-Release:        1%{?dist}
+Release:        2%{?dist}
 
 Summary:        Python library for interacting with rpm+git
 License:        GPLv2+ and LGPLv2
@@ -22,6 +22,8 @@ BuildArch:      noarch
 # and there is only old rpm-python package in EL6 and 7, so just simply to
 # remove rpm-py-installer for now.
 Patch0:         remove-koji-and-rpm-py-installer-from-requires.patch
+# https://pagure.io/rpkg/pull-request/361
+Patch1:         add-flatpak-build.patch
 
 %if 0%{?fedora} || 0%{?rhel} > 7
 # Enable python3 build by default
@@ -52,6 +54,7 @@ BuildRequires:  python2-devel
 BuildRequires:  python2-koji >= 1.15
 BuildRequires:  python2-cccolutils
 BuildRequires:  python2-openidc-client
+BuildRequires:  PyYAML
 
 %if 0%{?rhel}
 BuildRequires:  GitPython
@@ -60,11 +63,16 @@ BuildRequires:  python-requests
 BuildRequires:  python-six >= 1.9.0
 %else
 BuildRequires:  python2-GitPython
+BuildRequires:  python2-gobject-base
 BuildRequires:  python2-pycurl
 BuildRequires:  python2-requests
 BuildRequires:  python2-six >= 1.9.0
 %endif
 
+%if 0%{?rhel} && 0%{?rhel} == 7
+BuildRequires:  python-gobject-base
+%endif
+
 %if 0%{?rhel} && 0%{?rhel} < 7
 BuildRequires:  python-argparse
 BuildRequires:  python-mock
@@ -75,6 +83,7 @@ BuildRequires:  python-unittest2
 BuildRequires:  python2-mock
 BuildRequires:  python2-nose
 BuildRequires:  python2-setuptools
+BuildRequires:  libmodulemd
 %endif
 
 BuildRequires:  rpmlint
@@ -86,6 +95,7 @@ Requires:       rpmlint
 
 Requires:       python2-cccolutils
 Requires:       python2-koji >= 1.15
+Requires:       PyYAML
 
 %if 0%{?rhel}
 Requires:       GitPython >= 0.2.0
@@ -98,10 +108,18 @@ Requires:       python2-GitPython
 Requires:       python2-rpm
 Requires:       python2-pycurl
 Requires:       python2-six >= 1.9.0
+Requires:       python2-gobject-base
+
+Requires:       libmodulemd
 %endif
 
 %if 0%{?rhel} && 0%{?rhel} < 7
-Requires:  python-argparse
+Requires:       python-argparse
+%endif
+
+%if 0%{?rhel} && 0%{?rhel} == 7
+Requires:       python-gobject-base
+Requires:       libmodulemd
 %endif
 
 Requires:       %{name}-common = %{version}-%{release}
@@ -135,6 +153,8 @@ BuildRequires:  python3-six >= 1.9.0
 BuildRequires:  python3-requests
 BuildRequires:  python3-setuptools
 BuildRequires:  python3-nose
+BuildRequires:  python3-gobject-base
+BuildRequires:  python3-PyYAML
 
 BuildRequires:  rpmlint
 
@@ -149,6 +169,9 @@ Requires:       python3-koji >= 1.15
 Requires:       python3-rpm
 Requires:       python3-pycurl
 Requires:       python3-six >= 1.9.0
+Requires:       python3-gobject-base
+Requires:       python3-PyYAML
+Requires:       libmodulemd
 
 Requires:       %{name}-common = %{version}-%{release}
 
@@ -173,6 +196,7 @@ Common files for python2-%{name} and python3-%{name}.
 %prep
 %setup -q
 %patch0 -p1
+%patch1 -p1
 
 
 %build
@@ -246,6 +270,10 @@ nosetests tests
 
 
 %changelog
+* Fri Sep  7 2018 Owen Taylor <otaylor@redhat.com> - 1.56-2
+- Add patch from upstream pull-request to add a flatpak-build subcommand
+- Add PyYAML dependencies so that the spec file at least builds on epel6/epel7
+
 * Tue Aug 21 2018 Chenxiong Qi <cqi@redhat.com> - 1.56-1
 - Validate greenwave policy early in Commands.build (cqi)
 - Refine error message for failure gating.yaml validation (cqi)

                 reply	other threads:[~2026-08-10 21:43 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178639823828.1.10460689309413768079.rpms-rpkg-05492aceb1fa@fedoraproject.org \
    --to=otaylor@fishsoup.net \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox