From 279e6533f6f54f5185c35abf7ea637d6e6c07e87 Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Jun 26 2019 07:06:48 +0000
Subject: [PATCH 1/4] rebuildSRPM task


New task rebuildSRPM for srpms uploaded to koji. If policy
'rebuild_srpm' returns True, build task will spawn rebuildSRPM task
first to recreate SRPM with updated buildroot macros, typically 'dist'.
Policy has access to same data as build_from_srpm and build_from_repo_id
policies. (user_id, source, task_id, build_tag, skip_tag, target, tag)

Fixes: https://pagure.io/koji/issue/1396

---

diff --git a/builder/kojid b/builder/kojid
index d4504eb..556f43a 100755
--- a/builder/kojid
+++ b/builder/kojid
@@ -554,6 +554,39 @@ class BuildRoot(object):
         msg = '; see %s for more information' % logfile
         return parseStatus(rv, 'mock') + msg
 
+    def rebuild_srpm(self, source_srpm):
+        self.session.host.setBuildRootState(self.id,'BUILDING')
+
+        # unpack SRPM to tempdir
+        srpm_dir = os.path.join(self.tmpdir(), 'srpm_unpacked')
+        koji.ensuredir(srpm_dir)
+        top_dir = self.path_without_to_within(srpm_dir)
+        args = ['--no-clean', '--target', 'noarch', '--chroot', '--',
+                'rpm', '--define', '_topdir %s' % top_dir, '-iv', source_srpm]
+        rv = self.mock(args)
+
+        # find specfile
+        spec_files = glob.glob("%s/SPECS/*.spec" % srpm_dir)
+        if len(spec_files) == 0:
+            raise koji.BuildError("No spec file found")
+        elif len(spec_files) > 1:
+            raise koji.BuildError("Multiple spec files found: %s" % spec_files)
+        spec_file = os.path.join(top_dir, "SPECS", os.path.basename(spec_files[0]))
+
+        # rebuild SRPM from spec + sources
+        args = ['--no-clean', '--target', 'noarch', '--chroot', '--',
+                'rpmbuild', '--define', '_topdir %s' % top_dir, '-bs', '--nodeps', spec_file]
+        rv = self.mock(args)
+
+        result_dir = os.path.join(srpm_dir, 'SRPMS')
+        for fn in glob.glob('%s/*.src.rpm' % result_dir):
+            shutil.move(os.path.join(result_dir, fn), self.resultdir())
+
+        if rv:
+            self.expire()
+            raise koji.BuildError("error building srpm, %s" % self._mockResult(rv))
+
+
     def build_srpm(self, specfile, sourcedir, source_cmd):
         self.session.host.setBuildRootState(self.id,'BUILDING')
         if source_cmd:
@@ -1025,12 +1058,31 @@ class BuildTask(BaseTaskHandler):
             if SCM.is_scm_url(src):
                 return self.getSRPMFromSCM(src, build_tag, repo_id)
             else:
-                #assume this is a path under uploads
-                return src
+                taginfo = self.session.getTag(build_tag)
+                if taginfo['extra'].get('rebuild_srpm', True):
+                    # default is to always rebuild
+                    return self.getSRPMFromSRPM(src, build_tag, repo_id)
+                else:
+                    return src
         else:
             raise koji.BuildError('Invalid source specification: %s' % src)
             #XXX - other methods?
 
+    def getSRPMFromSRPM(self, src, build_tag, repo_id):
+        # rebuild srpm in mock, so it gets correct disttag, rpm version, etc.
+        task_id = self.session.host.subtask(method='rebuildSRPM',
+                                            arglist=[src, build_tag, {'repo_id': repo_id, 'scratch': self.opts.get('scratch')}],
+                                            label='srpm',
+                                            parent=self.id)
+        # wait for subtask to finish
+        result = self.wait(task_id)[task_id]
+        if 'source' in result:
+            self.source = result['source']
+        else:
+            self.logger.warning('subtask did not provide source data')
+        srpm = result['srpm']
+        return srpm
+
     def getSRPMFromSCM(self, url, build_tag, repo_id):
         #TODO - allow different ways to get the srpm
         task_id = self.session.host.subtask(method='buildSRPMFromSCM',
@@ -4582,6 +4634,98 @@ class BuildIndirectionImageTask(OzImageTask):
         return report
 
 
+class RebuildSRPM(BaseBuildTask):
+
+    Methods = ['rebuildSRPM']
+    _taskWeight = 1.0
+
+    def checkHost(self, hostdata):
+        tag = self.params[1]
+        return self.checkHostArch(tag, hostdata)
+
+    def handler(self, source_srpm, build_tag, opts=None):
+        if opts is None:
+            opts = {}
+        repo_id = opts.get('repo_id')
+        if not repo_id:
+            raise koji.BuildError("A repo id must be provided")
+
+        repo_info = self.session.repoInfo(repo_id, strict=True)
+        event_id = repo_info['create_event']
+        build_tag = self.session.getTag(build_tag, strict=True, event=event_id)
+
+        rootopts = {'install_group': 'srpm-build', 'repo_id': repo_id}
+        br_arch = self.find_arch('noarch', self.session.host.getHost(), self.session.getBuildConfig(build_tag['id'], event=event_id))
+        broot = BuildRoot(self.session, self.options, build_tag['id'], br_arch, self.id, **rootopts)
+        broot.workdir = self.workdir
+
+        self.logger.debug("Initializing buildroot")
+        broot.init()
+
+        # Setup files and directories for SRPM rebuild
+        # We can't put this under the mock homedir because that directory
+        # is completely blown away and recreated on every mock invocation
+        srpmdir = broot.tmpdir() + '/srpm'
+        koji.ensuredir(srpmdir)
+        uploadpath = self.getUploadDir()
+
+        # Check out spec file, etc. from SCM
+        # TODO: download srpm
+        fn = self.localPath("work/%s" % source_srpm)
+        if not os.path.exists(fn):
+            raise koji.BuildError("Input SRPM file missing: %s" % fn)
+        shutil.copy(fn, srpmdir)
+
+        # chown the sourcedir and everything under it to the mockuser
+        # so we can build the srpm as non-root
+        uid = pwd.getpwnam(self.options.mockuser)[2]
+        # rpmbuild seems to complain if it's running in the "mock" group but
+        # files are in a different group
+        gid = grp.getgrnam('mock')[2]
+        self.chownTree(srpmdir, uid, gid)
+
+        # rebuild srpm
+        self.logger.debug("Running srpm rebuild")
+        br_srpm_path = os.path.join(broot.path_without_to_within(srpmdir), os.path.basename(source_srpm))
+        broot.rebuild_srpm(br_srpm_path)
+
+        srpms = glob.glob('%s/*.src.rpm' % broot.resultdir())
+        if len(srpms) == 0:
+            raise koji.BuildError("No srpms found in %s" % srpmdir)
+        elif len(srpms) > 1:
+            raise koji.BuildError("Multiple srpms found in %s: %s" % (srpmdir, ", ".join(srpms)))
+        else:
+            srpm = srpms[0]
+
+        # check srpm name
+        h = koji.get_rpm_header(srpm)
+        name = koji.get_header_field(h, 'name')
+        version = koji.get_header_field(h, 'version')
+        release = koji.get_header_field(h, 'release')
+        srpm_name = "%(name)s-%(version)s-%(release)s.src.rpm" % locals()
+        if srpm_name != os.path.basename(srpm):
+            raise koji.BuildError('srpm name mismatch: %s != %s' % (srpm_name, os.path.basename(srpm)))
+
+        # upload srpm and return
+        self.uploadFile(srpm)
+
+        brootid = broot.id
+        log_files = glob.glob('%s/*.log' % broot.resultdir())
+
+        broot.expire()
+
+        return {
+            'srpm': "%s/%s" % (uploadpath, srpm_name),
+            'logs': ["%s/%s" % (uploadpath, os.path.basename(f))
+                     for f in log_files],
+            'brootid': brootid,
+            'source': {
+                'source': os.path.basename(source_srpm),
+                'url': os.path.basename(source_srpm),
+            }
+        }
+
+
 class BuildSRPMFromSCMTask(BaseBuildTask):
 
     Methods = ['buildSRPMFromSCM']
diff --git a/koji/tasks.py b/koji/tasks.py
index 90656b8..a787c11 100644
--- a/koji/tasks.py
+++ b/koji/tasks.py
@@ -172,6 +172,9 @@ LEGACY_SIGNATURES = {
     'buildSRPMFromSCM' : [
         [['url', 'build_tag', 'opts'], None, None, (None,)],
     ],
+    'rebuildSRPM' : [
+        [['source_srpm', 'build_tag', 'opts'], None, None, (None,)],
+    ],
     'createrepo' : [
         [['repo_id', 'arch', 'oldrepo'], None, None, None],
     ],
diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py
index a57fa3a..ea09504 100644
--- a/www/kojiweb/index.py
+++ b/www/kojiweb/index.py
@@ -430,6 +430,7 @@ def notificationdelete(environ, notificationID):
 # All Tasks
 _TASKS = ['build',
           'buildSRPMFromSCM',
+          'rebuildSRPM',
           'buildArch',
           'chainbuild',
           'maven',

From 17128a67fdb23589f08452ee7ab6f204844c6ab8 Mon Sep 17 00:00:00 2001
From: Mike McLean <mikem@redhat.com>
Date: Jun 26 2019 07:06:48 +0000
Subject: [PATCH 2/4] use getBuildConfig with event_id to check for rebuild_srpm setting


---

diff --git a/builder/kojid b/builder/kojid
index 556f43a..9f36f9c 100755
--- a/builder/kojid
+++ b/builder/kojid
@@ -1058,8 +1058,8 @@ class BuildTask(BaseTaskHandler):
             if SCM.is_scm_url(src):
                 return self.getSRPMFromSCM(src, build_tag, repo_id)
             else:
-                taginfo = self.session.getTag(build_tag)
-                if taginfo['extra'].get('rebuild_srpm', True):
+                buildconfig = self.session.getBuildConfig(build_tag, event=self.event_id)
+                if buildconfig['extra'].get('rebuild_srpm', True):
                     # default is to always rebuild
                     return self.getSRPMFromSRPM(src, build_tag, repo_id)
                 else:

From 2bef7c16dc4225a30d500cccc9b3f5c6d4a328d0 Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Jun 26 2019 07:06:48 +0000
Subject: [PATCH 3/4] rename source_srpm to srpm


---

diff --git a/builder/kojid b/builder/kojid
index 9f36f9c..d7166ac 100755
--- a/builder/kojid
+++ b/builder/kojid
@@ -554,7 +554,7 @@ class BuildRoot(object):
         msg = '; see %s for more information' % logfile
         return parseStatus(rv, 'mock') + msg
 
-    def rebuild_srpm(self, source_srpm):
+    def rebuild_srpm(self, srpm):
         self.session.host.setBuildRootState(self.id,'BUILDING')
 
         # unpack SRPM to tempdir
@@ -562,7 +562,7 @@ class BuildRoot(object):
         koji.ensuredir(srpm_dir)
         top_dir = self.path_without_to_within(srpm_dir)
         args = ['--no-clean', '--target', 'noarch', '--chroot', '--',
-                'rpm', '--define', '_topdir %s' % top_dir, '-iv', source_srpm]
+                'rpm', '--define', '_topdir %s' % top_dir, '-iv', srpm]
         rv = self.mock(args)
 
         # find specfile
@@ -4643,7 +4643,7 @@ class RebuildSRPM(BaseBuildTask):
         tag = self.params[1]
         return self.checkHostArch(tag, hostdata)
 
-    def handler(self, source_srpm, build_tag, opts=None):
+    def handler(self, srpm, build_tag, opts=None):
         if opts is None:
             opts = {}
         repo_id = opts.get('repo_id')
@@ -4669,9 +4669,7 @@ class RebuildSRPM(BaseBuildTask):
         koji.ensuredir(srpmdir)
         uploadpath = self.getUploadDir()
 
-        # Check out spec file, etc. from SCM
-        # TODO: download srpm
-        fn = self.localPath("work/%s" % source_srpm)
+        fn = self.localPath("work/%s" % srpm)
         if not os.path.exists(fn):
             raise koji.BuildError("Input SRPM file missing: %s" % fn)
         shutil.copy(fn, srpmdir)
@@ -4686,7 +4684,7 @@ class RebuildSRPM(BaseBuildTask):
 
         # rebuild srpm
         self.logger.debug("Running srpm rebuild")
-        br_srpm_path = os.path.join(broot.path_without_to_within(srpmdir), os.path.basename(source_srpm))
+        br_srpm_path = os.path.join(broot.path_without_to_within(srpmdir), os.path.basename(srpm))
         broot.rebuild_srpm(br_srpm_path)
 
         srpms = glob.glob('%s/*.src.rpm' % broot.resultdir())
@@ -4720,8 +4718,8 @@ class RebuildSRPM(BaseBuildTask):
                      for f in log_files],
             'brootid': brootid,
             'source': {
-                'source': os.path.basename(source_srpm),
-                'url': os.path.basename(source_srpm),
+                'source': os.path.basename(srpm),
+                'url': os.path.basename(srpm),
             }
         }
 
diff --git a/koji/tasks.py b/koji/tasks.py
index a787c11..6ed02b6 100644
--- a/koji/tasks.py
+++ b/koji/tasks.py
@@ -173,7 +173,7 @@ LEGACY_SIGNATURES = {
         [['url', 'build_tag', 'opts'], None, None, (None,)],
     ],
     'rebuildSRPM' : [
-        [['source_srpm', 'build_tag', 'opts'], None, None, (None,)],
+        [['srpm', 'build_tag', 'opts'], None, None, (None,)],
     ],
     'createrepo' : [
         [['repo_id', 'arch', 'oldrepo'], None, None, None],

From 5882bc249bc5afe70646d416b173422d99c142e7 Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Jun 26 2019 07:06:48 +0000
Subject: [PATCH 4/4] remove unneeded chown


---

diff --git a/builder/kojid b/builder/kojid
index d7166ac..0024e4f 100755
--- a/builder/kojid
+++ b/builder/kojid
@@ -4674,14 +4674,6 @@ class RebuildSRPM(BaseBuildTask):
             raise koji.BuildError("Input SRPM file missing: %s" % fn)
         shutil.copy(fn, srpmdir)
 
-        # chown the sourcedir and everything under it to the mockuser
-        # so we can build the srpm as non-root
-        uid = pwd.getpwnam(self.options.mockuser)[2]
-        # rpmbuild seems to complain if it's running in the "mock" group but
-        # files are in a different group
-        gid = grp.getgrnam('mock')[2]
-        self.chownTree(srpmdir, uid, gid)
-
         # rebuild srpm
         self.logger.debug("Running srpm rebuild")
         br_srpm_path = os.path.join(broot.path_without_to_within(srpmdir), os.path.basename(srpm))

