#26 Rebuild docker image when bodhi update moves to stable
Merged 8 years ago by cqi. Opened 8 years ago by cqi.

file modified
+2
@@ -1,5 +1,6 @@ 

  from os import path

  

+ 

  # FIXME: workaround for this moment till confdir, dbdir (installdir etc.) are

  # declared properly somewhere/somehow

  confdir = path.abspath(path.dirname(__file__))
@@ -43,6 +44,7 @@ 

      HANDLERS = [

          "freshmaker.handlers.mbs:MBS",  # Module Build Service

          "freshmaker.handlers.image_builder:DockerImageRebuildHandler",

+         "freshmaker.handlers.image_builder:DockerImageRebuildHandlerForBodhi",

      ]

  

      # Base URL of git repository with source artifacts.

file modified
+2
@@ -30,6 +30,7 @@ 

  import freshmaker.handlers

  import freshmaker.parsers.mbsmodule

  import freshmaker.parsers.gitreceive

+ import freshmaker.parsers.bodhiupdate

  

  from freshmaker import log, conf, messaging, events

  
@@ -63,6 +64,7 @@ 

      def register_parsers(self):

          events.BaseEvent.register_parser(freshmaker.parsers.mbsmodule.MBSModuleParser)

          events.BaseEvent.register_parser(freshmaker.parsers.gitreceive.GitReceiveParser)

+         events.BaseEvent.register_parser(freshmaker.parsers.bodhiupdate.UpdateCompleteStableParser)

          log.debug("Parser classes: %r", events.BaseEvent._parsers)

  

          self.topic = events.BaseEvent.get_parsed_topics()

file modified
+28
@@ -182,3 +182,31 @@ 

          self.namespace = namespace

          self.repo = repo

          self.rev = rev

+ 

+ 

+ class BodhiUpdateCompleteStable(BaseEvent):

+     """Event when RPMs are available in Fedora master mirrors

+ 

+     Refer to an example in datagrepper:

+ 

+     https://apps.fedoraproject.org/datagrepper/raw?delta=572800& \

+         topic=org.fedoraproject.prod.bodhi.update.complete.stable

+     """

+ 

+     def __init__(self, msg_id, update_id, builds, release):

+         """Initiate event with data from message got from fedmsg

+ 

+         Not complete data is required, only part of attributes that are useful

+         for rebuild are stored in this event.

+ 

+         :param str update_id: the Bodhi update ID got from message.

+         :param list builds: a list of maps, each of them contains build NVRs

+             that are useful for getting RPMs for the rebuild.

+         :param dist release: a map contains release information, e.g. name and

+             branch. Refer to the example given above to see all available

+             attributes in a message.

+         """

+         super(BodhiUpdateCompleteStable, self).__init__(msg_id)

+         self.update_id = update_id

+         self.builds = builds

+         self.release = release

@@ -21,9 +21,19 @@ 

  #

  # Written by Chenxiong Qi <cqi@redhat.com>

  

+ import os

+ 

+ from itertools import chain

+ 

+ import freshmaker.pdc as pdc

+ 

  from freshmaker import log, conf

  from freshmaker.handlers import BaseHandler

  from freshmaker.events import DockerfileChanged

+ from freshmaker.events import BodhiUpdateCompleteStable

+ from freshmaker.utils import temp_dir

+ from freshmaker.utils import _run_command

+ from freshmaker.utils import get_commit_hash

  from freshmaker.kojiservice import koji_service

  

  
@@ -36,29 +46,102 @@ 

          """Rebuild docker image"""

          import koji

  

+         log.info('Start to rebuild docker image %s', event.repo)

+ 

          try:

-             self.build_image(event)

+             self.build_image(repo_url=event.repo_url,

+                              rev=event.rev,

+                              branch=event.branch,

+                              namespace=event.namespace)

          except koji.krbV.Krb5Error as e:

              log.exception('Failed to login Koji via Kerberos using GSSAPI. %s', e.args[1])

          except:

              log.exception('Could not create task to build docker image %s', event.repo)

  

-     def build_image(self, event):

+     def build_image(self, repo_url, rev, branch, namespace=None):

          with koji_service(profile=conf.koji_profile, logger=log) as service:

              log.debug('Logging into {0} with Kerberos authentication.'.format(service.server))

              proxyuser = conf.koji_build_owner if conf.koji_proxyuser else None

- 

              service.krb_login(proxyuser=proxyuser)

  

              if not service.logged_in:

                  log.error('Could not login server %s', service.server)

                  return

  

-             build_source = '{}#{}'.format(event.repo_url, event.rev)

+             build_source = '{}?#{}'.format(repo_url, rev)

  

-             log.info('Start to build docker image %s', event.repo)

              log.debug('Build from source: %s', build_source)

  

-             return service.build_container(build_source, event.branch,

-                                            namespace=event.namespace,

+             return service.build_container(build_source,

+                                            branch,

+                                            namespace=namespace,

                                             scratch=conf.koji_container_scratch_build)

+ 

+ 

+ class DockerImageRebuildHandlerForBodhi(DockerImageRebuildHandler):

+     """Rebuild docker images when RPMs are synced by Bodhi"""

+ 

+     def __init__(self):

+         self.pdc_session = pdc.get_client_session(conf)

+ 

+     def can_handle(self, event):

+         return isinstance(event, BodhiUpdateCompleteStable)

+ 

+     def handle(self, event):

+         log.info('Rebuild docker images for event %s, msgid: %s',

+                  BodhiUpdateCompleteStable.__name__, event.msg_id)

+ 

+         rpms = self.get_rpms_included_in_bodhi_update(event.builds)

+         containers = self.get_containers_including_rpms(rpms)

+ 

+         log.info('Found docker images to rebuild: %s', containers)

+ 

+         for container in containers:

+             try:

+                 self.handle_image_build(container)

+             except:

+                 log.exception('Error when rebuild %s', container)

+ 

+     def handle_image_build(self, container_info):

+         container_detail = pdc.get_release_component(self.pdc_session,

+                                                      container_info['id'])

+ 

+         branch = container_detail['dist_git_branch']

+         image_name = container_detail['name']

+         repo_url = '{}/{}/{}'.format(conf.git_base_url,

+                                      'container',

+                                      image_name)

+ 

+         log.info('Start to rebuild docker image %s from branch %s',

+                  image_name, branch)

+ 

+         with temp_dir(suffix='-rebuild-docker-image') as working_dir:

+             self.clone_repository(repo_url, branch, working_dir)

+ 

+             last_commit_hash = get_commit_hash(

+                 os.path.join(working_dir, image_name))

+ 

+             return self.build_image(repo_url=repo_url,

+                                     branch=branch,

+                                     rev=last_commit_hash)

+ 

+     def clone_repository(self, url, branch, working_dir):

+         cmd = ['git', 'clone', '-b', branch, url]

+         log.debug('Clone repository: %s', cmd)

+         _run_command(cmd, rundir=working_dir)
mjia commented 8 years ago

I think you should move this method into utils.py as _run_command is a private module method and should not be imported here from the perspective of best practices. Another benefit is you can pass logger=logger which would make debug easier if something is going wrong.

cqi commented 8 years ago

Hi mjia, clone_repository is specific to DockerImageRebuildHandlerForBodhi. And, there is also a background of why I considered to put this method here at current stage. In utils.py, there is a similar method that clones a module repository from dist-git. Ideally, both of it and this method clone_repository could be merged into one, that is what I'm doing in my local branch. :) Once it is done and ready for review, I'll make a PR.

mjia commented 8 years ago

Okay, it makes sense to me.

+ 

+     def get_rpms_included_in_bodhi_update(self, builds):

+         build_nvrs = (build['nvr'] for build in builds)

+         with koji_service(profile=conf.koji_profile, logger=log) as service:

+             return chain(*[service.get_build_rpms(nvr) for nvr in build_nvrs])

+ 

+     def get_containers_including_rpms(self, rpms):

+         containers = {}

+         for rpm in rpms:

+             found = pdc.find_containers_by_rpm_name(self.pdc_session, rpm['name'])

+             for container in found:

+                 id = container['id']

+                 if id not in containers:

+                     containers[id] = container

+ 

+         return containers.values()

@@ -95,6 +95,10 @@ 

  

          return task_id

  

+     def get_build_rpms(self, build_nvr):

+         build_info = self.session.getBuild(build_nvr)

+         return self.session.listRPMs(buildID=build_info['id'])

+ 

  

  @contextlib.contextmanager

  def koji_service(profile=None, logger=None):

@@ -0,0 +1,50 @@ 

+ # -*- coding: utf-8 -*-

+ # Copyright (c) 2016  Red Hat, Inc.

+ #

+ # Permission is hereby granted, free of charge, to any person obtaining a copy

+ # of this software and associated documentation files (the "Software"), to deal

+ # in the Software without restriction, including without limitation the rights

+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+ # copies of the Software, and to permit persons to whom the Software is

+ # furnished to do so, subject to the following conditions:

+ #

+ # The above copyright notice and this permission notice shall be included in all

+ # copies or substantial portions of the Software.

+ #

+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+ # SOFTWARE.

+ #

+ # Written by Chenxiong Qi <cqi@redhat.com>

+ 

+ from freshmaker import log

+ from freshmaker.parsers import BaseParser

+ from freshmaker.events import BodhiUpdateCompleteStable

+ 

+ 

+ class UpdateCompleteStableParser(BaseParser):

+     """Parse Bodhi message from topic bodhi.update.complete.stable"""

+ 

+     name = 'UpdateCompleteStableParser'

+     topic_suffixes = ['bodhi.update.complete.stable']

+ 

+     def can_parse(self, topic, msg):

+         return any([topic.endswith(suffix) for suffix in self.topic_suffixes])

+ 

+     def parse(self, topic, msg):

+         msg_id = msg.get('msg_id')

+         msg_inner_msg = msg.get('msg')

+ 

+         if not msg_inner_msg:

+             log.debug('Skipping message without any content with the topic "%s"', topic)

+             return None

+ 

+         update = msg_inner_msg['update']

+         return BodhiUpdateCompleteStable(msg_id,

+                                          update_id=update['updateid'],

+                                          builds=update['builds'],

+                                          release=update['release'])

file modified
+11 -1
@@ -24,7 +24,6 @@ 

  import requests

  from pdc_client import PDCClient

  

- import freshmaker

  import freshmaker.utils

  

  
@@ -79,3 +78,14 @@ 

          mods = get_modules(pdc_session, variant_name=name, variant_version=version, active=active)

          latest_modules.append(sorted(mods, key=lambda x: x['variant_release']).pop())

      return list(filter(lambda x: x in latest_modules, modules))

+ 

+ 

+ @freshmaker.utils.retry(wait_on=(requests.ConnectTimeout, requests.ConnectionError), logger=freshmaker.log)

+ def find_containers_by_rpm_name(pdc_session, rpm_name):

+     rels = pdc_session['release-component-relationships'](type='ContainerIncludesRPM',

+                                                           to_component_name=rpm_name)

+     return [rel['from_component'] for rel in rels['results']]

+ 

+ 

+ def get_release_component(pdc_session, id):

+     return pdc_session['release-components/{}/'.format(id)]()

@@ -0,0 +1,129 @@ 

+ {

+   "source_name": "datanommer",

+   "certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVYakNDQThlZ0F3SUJBZ0lDQWhRd0RRWUpL\nb1pJaHZjTkFRRUZCUUF3Z2FBeEN6QUpCZ05WQkFZVEFsVlQKTVFzd0NRWURWUVFJRXdKT1F6RVFN\nQTRHQTFVRUJ4TUhVbUZzWldsbmFERVhNQlVHQTFVRUNoTU9SbVZrYjNKaApJRkJ5YjJwbFkzUXhE\nekFOQmdOVkJBc1RCbVpsWkcxelp6RVBNQTBHQTFVRUF4TUdabVZrYlhObk1ROHdEUVlEClZRUXBF\nd1ptWldSdGMyY3hKakFrQmdrcWhraUc5dzBCQ1FFV0YyRmtiV2x1UUdabFpHOXlZWEJ5YjJwbFkz\nUXUKYjNKbk1CNFhEVEUxTURneE9ESXhNakUwT0ZvWERUSTFNRGd4TlRJeE1qRTBPRm93Z2V3eEN6\nQUpCZ05WQkFZVApBbFZUTVFzd0NRWURWUVFJRXdKT1F6RVFNQTRHQTFVRUJ4TUhVbUZzWldsbmFE\nRVhNQlVHQTFVRUNoTU9SbVZrCmIzSmhJRkJ5YjJwbFkzUXhEekFOQmdOVkJBc1RCbVpsWkcxelp6\nRTFNRE1HQTFVRUF4TXNZbTlrYUdrdFltOWsKYUdrdFltRmphMlZ1WkRBeExuQm9lREl1Wm1Wa2Iz\nSmhjSEp2YW1WamRDNXZjbWN4TlRBekJnTlZCQ2tUTEdKdgpaR2hwTFdKdlpHaHBMV0poWTJ0bGJt\nUXdNUzV3YUhneUxtWmxaRzl5WVhCeWIycGxZM1F1YjNKbk1TWXdKQVlKCktvWklodmNOQVFrQkZo\nZGhaRzFwYmtCbVpXUnZjbUZ3Y205cVpXTjBMbTl5WnpDQm56QU5CZ2txaGtpRzl3MEIKQVFFRkFB\nT0JqUUF3Z1lrQ2dZRUF1b3NVcXpXM010RlpkRTdEelFCT0VzVzY3ZThsZnQ5S3QzMFVJbGZSVUhS\nVwpVMGpWSEoyaWtic1oxQmZMckh3Ly8yd1lUa2E3cE1Xblh2KzZnRVhhYTNFZDRqbk8zTDNVallj\nQ1pBUzBDYW93CmZad1VVTWwwNFN6N1ZSZnVRdElJbG5ocjFxSjhKTTJqZXZNQkpyaTQyZDZZKzdF\nTEFNZjgvTkVjQ2p6NERuY0MKQXdFQUFhT0NBVmN3Z2dGVE1Ba0dBMVVkRXdRQ01BQXdMUVlKWUla\nSUFZYjRRZ0VOQkNBV0hrVmhjM2t0VWxOQgpJRWRsYm1WeVlYUmxaQ0JEWlhKMGFXWnBZMkYwWlRB\nZEJnTlZIUTRFRmdRVXVBb1E0T05pRlFERXpvSmE1ZG9CCnA4NTJoeEV3Z2RVR0ExVWRJd1NCelRD\nQnlvQVVhMEJhK1JJSWlWbm5XZVVGOVFJZENrNS9GQUNoZ2Fha2dhTXcKZ2FBeEN6QUpCZ05WQkFZ\nVEFsVlRNUXN3Q1FZRFZRUUlFd0pPUXpFUU1BNEdBMVVFQnhNSFVtRnNaV2xuYURFWApNQlVHQTFV\nRUNoTU9SbVZrYjNKaElGQnliMnBsWTNReER6QU5CZ05WQkFzVEJtWmxaRzF6WnpFUE1BMEdBMVVF\nCkF4TUdabVZrYlhObk1ROHdEUVlEVlFRcEV3Wm1aV1J0YzJjeEpqQWtCZ2txaGtpRzl3MEJDUUVX\nRjJGa2JXbHUKUUdabFpHOXlZWEJ5YjJwbFkzUXViM0puZ2drQTQxQWVSMDhYSGtVd0V3WURWUjBs\nQkF3d0NnWUlLd1lCQlFVSApBd0l3Q3dZRFZSMFBCQVFEQWdlQU1BMEdDU3FHU0liM0RRRUJCUVVB\nQTRHQkFHazBJVGhUT2xzV29RdncxQzdCCjNINnh6cEp0Znd4NHNVdnk4cXVXN2FHOE9VNXdscmNF\ndmZzZENJbmdKdEwxZndWTGgwT3M3SmhyaGxkdzZBUkoKRWdIS2NtSnVjbkJEZW9tck1DdTVaM3U1\nNWp4SWlHcG5wMThRcUhlSHVmZUEzLysvWlhTL3F6bGRxdjNrMG9kMQpIbFNweFZxWUQ5OGs2eHRD\nQXo5YWh0Q20KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=\n",

+   "i": 12,

+   "timestamp": 1493430570.0,

+   "msg_id": "2017-0c7f37f3-d0b5-474f-b600-198d9b3be748",

+   "topic": "org.fedoraproject.prod.bodhi.update.complete.stable",

+   "source_version": "0.6.5",

+   "signature": "n3DUWAJMCMF3ipgcxUnb7EaCNQydfKvjr+AQA4S3nuTqUT/0wpuOAqTnOOXhW6wL6LVmzleOQdo7\nWH/tR5qPkemPTs6wWTNIlon1QJWaMWpNHzYn3O1yzcFiTizX98Fi+p5oBKzyafHHqAiTN5Nj+RjR\nT+xz/rkmRy+Ql+96m04=\n",

+   "msg": {

+     "update": {

+       "date_testing": "2017-04-20 17:22:23",

+       "old_updateid": null,

+       "pushed": true,

+       "require_testcases": false,

+       "date_stable": "2017-04-28 19:59:31",

+       "critpath": false,

+       "date_approved": null,

+       "stable_karma": 3,

+       "date_pushed": "2017-04-28 19:59:31",

+       "requirements": "",

+       "severity": "medium",

+       "autokarma": true,

+       "title": "community-mysql-5.7.18-2.fc25",

+       "suggest": "unspecified",

+       "require_bugs": false,

+       "comments": [

+         {

+           "bug_feedback": [],

+           "user_id": 91,

+           "timestamp": "2017-04-19 23:31:30",

+           "text": "This update has been submitted for testing by mschorm. ",

+           "karma_critpath": 0,

+           "update_id": 85679,

+           "karma": 0,

+           "anonymous": false,

+           "testcase_feedback": [],

+           "id": 596151,

+           "user": {

+             "openid": null,

+             "name": "bodhi",

+             "show_popups": true,

+             "email": null,

+             "avatar": null,

+             "groups": [],

+             "id": 91

+           }

+         }

+       ],

+       "updateid": "FEDORA-2017-fe6e14dcf9",

+       "test_cases": [],

+       "close_bugs": false,

+       "meets_testing_requirements": true,

+       "date_locked": "2017-04-28 19:59:21",

+       "date_submitted": "2017-04-19 23:31:30",

+       "unstable_karma": -2,

+       "submitter": "mschorm",

+       "user": {

+         "openid": null,

+         "name": "mschorm",

+         "show_popups": true,

+         "email": "mschorm@redhat.com",

+         "avatar": null,

+         "groups": [

+           {

+             "name": "packager"

+           }

+         ],

+         "id": 3283

+       },

+       "locked": true,

+       "builds": [

+         {

+           "epoch": 0,

+           "nvr": "community-mysql-5.7.18-2.fc25",

+           "signed": true

+         }

+       ],

+       "date_modified": null,

+       "url": "https://bodhi.fedoraproject.org/updates/FEDORA-2017-fe6e14dcf9",

+       "type": "security",

+       "notes": "Update to 5.7.18\n\nCVEs fixed by this update can be found here:\nhttp://www.oracle.com/technetwork/security-advisory/cpuapr2017-3236618.html\n\n",

+       "request": null,

+       "bugs": [

+         {

+           "bug_id": 1414386,

+           "security": true,

+           "feedback": [],

+           "parent": false,

+           "title": "CVE-2017-3265 community-mysql: various flaws [fedora-all]"

+         },

+         {

+           "bug_id": 1443407,

+           "security": true,

+           "feedback": [],

+           "parent": false,

+           "title": "CVE-2017-3308 CVE-2017-3309 CVE-2017-3450 CVE-2017-3453 CVE-2017-3456 CVE-2017-3461 CVE-2017-3462 CVE-2017-3463 CVE-2017-3464 CVE-2017-3599 community-mysql: various flaws [fedora-all]"

+         },

+         {

+           "bug_id": 1441001,

+           "security": false,

+           "feedback": [],

+           "parent": false,

+           "title": "community-mysql-5.7.18 is available"

+         }

+       ],

+       "alias": "FEDORA-2017-fe6e14dcf9",

+       "status": "stable",

+       "karma": 0,

+       "release": {

+         "dist_tag": "f25",

+         "name": "F25",

+         "testing_tag": "f25-updates-testing",

+         "pending_stable_tag": "f25-updates-pending",

+         "pending_signing_tag": "f25-signing-pending",

+         "long_name": "Fedora 25",

+         "state": "current",

+         "version": "25",

+         "override_tag": "f25-override",

+         "branch": "f25",

+         "id_prefix": "FEDORA",

+         "pending_testing_tag": "f25-updates-testing-pending",

+         "stable_tag": "f25-updates",

+         "candidate_tag": "f25-updates-candidate"

+       }

+     },

+     "agent": "masher"

+   }

+ } 

\ No newline at end of file

@@ -20,19 +20,25 @@ 

  #

  # Written by Chenxiong Qi <cqi@redhat.com>

  

+ import shutil

+ import tempfile

+ import unittest

+ 

  import fedmsg.config

  import pytest

  import six

- import unittest

  

  from mock import patch

  from mock import MagicMock

+ from mock import call

+ 

+ from freshmaker import conf

  from freshmaker.consumer import FreshmakerConsumer

+ from freshmaker.handlers.image_builder import DockerImageRebuildHandlerForBodhi

  from tests import get_fedmsg

  

  

- @pytest.mark.skipif(six.PY3, reason='koji does not work in Python 3')

- class TestImageBuilderHandler(unittest.TestCase):

+ class BaseTestCase(unittest.TestCase):

  

      def create_consumer(self):

          hub = MagicMock()
@@ -41,32 +47,37 @@ 

  

      @patch("requests.request")

      @patch('freshmaker.consumer.get_global_consumer')

-     def consume_git_receive_msg(self, msg, global_consumer, request):

+     def consume_fedmsg(self, msg, global_consumer, request):

          consumer = self.create_consumer()

          global_consumer.return_value = consumer

          consumer.consume(msg)

  

+ 

+ @pytest.mark.skipif(six.PY3, reason='koji does not work in Python 3')

+ class TestImageBuilderHandler(BaseTestCase):

+ 

      @patch('koji.read_config')

      @patch('koji.ClientSession')

      def test_rebuild_if_Dockerfile_changed(self, ClientSession, read_config):

          read_config.return_value = {

              'server': 'https://localhost/kojihub',

              'krb_rdns': False,

+             'weburl': 'https://localhost/koji',

          }

  

-         self.consume_git_receive_msg(get_fedmsg("git_receive_dockerfile_changed"))

+         self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed'))

  

          mock_session = ClientSession.return_value

          mock_session.krb_login.assert_called_once_with(proxyuser=None)

          mock_session.buildContainer.assert_called_once_with(

-             'git://pkgs.fedoraproject.org/container/testimage.git#e1f39d43471fc37ec82616f76a119da4eddec787',

+             'git://pkgs.fedoraproject.org/container/testimage.git?#e1f39d43471fc37ec82616f76a119da4eddec787',

              'rawhide-container-candidate',

              {'scratch': True, 'git_branch': 'master'})

          mock_session.logout.assert_called_once()

  

      @patch('freshmaker.handlers.image_builder.DockerImageRebuildHandler.build_image')

      def test_not_rebuild_if_Dockerfile_not_changed(self, build_image):

-         self.consume_git_receive_msg(get_fedmsg("git_receive_dockerfile_not_changed"))

+         self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_not_changed'))

          build_image.assert_not_called()

  

      @patch('koji.read_config')
@@ -76,9 +87,10 @@ 

          read_config.return_value = {

              'server': 'https://localhost/kojihub',

              'krb_rdns': False,

+             'weburl': 'https://localhost/koji',

          }

  

-         self.consume_git_receive_msg(get_fedmsg("git_receive_dockerfile_changed"))

+         self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed'))

  

          ClientSession.return_value.logout.assert_called_once()

  
@@ -89,8 +101,256 @@ 

          read_config.return_value = {

              'server': 'https://localhost/kojihub',

              'krb_rdns': False,

+             'weburl': 'https://localhost/koji',

          }

  

-         self.consume_git_receive_msg(get_fedmsg("git_receive_dockerfile_changed"))

+         self.consume_fedmsg(get_fedmsg('git_receive_dockerfile_changed'))

  

          ClientSession.return_value.buildContainer.assert_not_called()

+ 

+ 

+ mock_found_containers = [

+     {

+         'release': 'fedora-25-updates',

+         'id': 5430,

+         'name': 'testimage1'

+     },

+     {

+         'release': 'fedora-25-updates',

+         'id': 5431,

+         'name': 'testimage2'

+     },

+ ]

+ 

+ mock_release_components = {

+     5430: {

+         'id': 5430,

+         'release': {

+             'active': True,

+             'release_id': 'fedora-25-updates'

+         },

+         'bugzilla_component': None,

+         'brew_package': None,

+         'global_component': 'testimage1',

+         'name': 'testimage1',

+         'dist_git_branch': 'f25',

+         'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage1',

+         'active': True,

+         'type': 'container',

+         'srpm': None,

+     },

+     5431: {

+         'id': 5431,

+         'release': {

+             'active': True,

+             'release_id': 'fedora-25-updates'

+         },

+         'bugzilla_component': None,

+         'brew_package': None,

+         'global_component': 'testimage2',

+         'name': 'testimage2',

+         'dist_git_branch': 'f25',

+         'dist_git_web_url': 'http://pkgs.example.com/cgit/container/testimage2',

+         'active': True,

+         'type': 'container',

+         'srpm': None,

+     }

+ }

+ 

+ def mock_get_release_component(pdc_session, id):

+     return mock_release_components[id]

+ 

+ 

+ @pytest.mark.skipif(six.PY3, reason='koji does not work in Python 3')

+ class TestRebuildWhenBodhiUpdateStable(BaseTestCase):

+ 

+     def setUp(self):

+         # Use to return a temporary directory from temp_dir method. So, no need

+         # to delete this directory, since temp_dir ensures to do that.

+         self.working_dir = tempfile.mkdtemp(prefix='test-image-rebuild-')

+ 

+     @patch('koji.ClientSession')

+     @patch('tempfile.mkdtemp')

+     @patch('freshmaker.handlers.image_builder._run_command')

+     @patch('freshmaker.handlers.image_builder.get_commit_hash')

+     @patch('freshmaker.handlers.image_builder.'

+            'DockerImageRebuildHandlerForBodhi.get_rpms_included_in_bodhi_update')

+     @patch('freshmaker.handlers.image_builder.'

+            'DockerImageRebuildHandlerForBodhi.get_containers_including_rpms')

+     @patch('freshmaker.pdc.get_release_component', new=mock_get_release_component)

+     def test_rebuild(self,

+                      get_containers_including_rpms,

+                      get_rpms_included_in_bodhi_update,

+                      get_commit_hash,

+                      _run_command,

+                      mkdtemp,

+                      ClientSession):

+         last_commit_hash = 'dea19c748434ec962f13d680682eee87393a4d8e'

+ 

+         # A repository is not cloned actually, so just use a fake commit hash

+         # to construct build source URL.

+         get_commit_hash.return_value = last_commit_hash

+ 

+         # temp_dir creates a temporary file using mkdtemp that is used for

+         # working directory for everything related to rebuild docker image.

+         # It is difficult to catch that temporary directory, so mock mkdtemp

+         # and use this test's own directory.

+         mkdtemp.return_value = self.working_dir

+ 

+         get_containers_including_rpms.return_value = mock_found_containers

+ 

+         self.consume_fedmsg(get_fedmsg('bodhi_update_stable'))

+ 

+         self.assertEqual(2, _run_command.call_count)

+ 

+         _run_command.assert_has_calls([

+             call(['git', 'clone', '-b', 'f25',

+                   '{}/container/{}'.format(conf.git_base_url, 'testimage1')],

+                  rundir=self.working_dir),

+             call(['git', 'clone', '-b', 'f25',

+                   '{}/container/{}'.format(conf.git_base_url, 'testimage2')],

+                  rundir=self.working_dir)

+         ])

+ 

+         session = ClientSession.return_value

+ 

+         self.assertEqual(2, session.krb_login.call_count)

+ 

+         buildContainer = session.buildContainer

+         self.assertEqual(2, buildContainer.call_count)

+         buildContainer.assert_has_calls([

+                 call('{}/container/{}?#{}'.format(conf.git_base_url,

+                                                   'testimage1',

+                                                   last_commit_hash),

+                      'f25-container-candidate',

+                      {'scratch': True, 'git_branch': 'f25'}),

+                 call('{}/container/{}?#{}'.format(conf.git_base_url,

+                                                   'testimage2',

+                                                   last_commit_hash),

+                      'f25-container-candidate',

+                      {'scratch': True, 'git_branch': 'f25'}),

+             ],

+             any_order=True)

+ 

+ 

+ class TestContainersIncludingRPMs(unittest.TestCase):

+ 

+     @patch('freshmaker.handlers.image_builder.pdc.find_containers_by_rpm_name')

+     def test_get_containers(self, find_containers_by_rpm_name):

+         expected_found_containers = [

+             {

+                 'release': 'fedora-24-updates',

+                 'id': 5430,

+                 'name': 'testimage1',

+             },

+             {

+                 'release': 'fedora-24-updates',

+                 'id': 5432,

+                 'name': 'testimage2',

+             },

+         ]

+         find_containers_by_rpm_name.return_value = expected_found_containers

+ 

+         handler = DockerImageRebuildHandlerForBodhi()

+         rpms = [

+             {'id': 9515683,

+              'name': 'community-mysql-devel',

+              'nvr': 'community-mysql-devel-5.7.18-2.fc25',

+              'release': '2.fc25',

+              'version': '5.7.18'},

+             {'id': 9515682,

+              'name': 'community-mysql-libs',

+              'nvr': 'community-mysql-libs-5.7.18-2.fc25',

+              'release': '2.fc25',

+              'version': '5.7.18'},

+             {'id': 9515681,

+              'name': 'community-mysql-server',

+              'nvr': 'community-mysql-server-5.7.18-2.fc25',

+              'release': '2.fc25',

+              'version': '5.7.18'},

+         ]

+         containers = handler.get_containers_including_rpms(rpms)

+ 

+         self.assertEqual(3, find_containers_by_rpm_name.call_count)

+         found_containers = sorted(containers, key=lambda item: item['id'])

+         self.assertEqual(expected_found_containers, found_containers)

+ 

+ 

+ def mock_get_build_rpms(self, nvr):

+     """Used to patch KojiService.get_build_rpms"""

+ 

+     rpms = {

+         'community-mysql-5.7.18-2.fc25': [

+             {

+                 'id': 9515683,

+                 'name': 'community-mysql-devel',

+                 'nvr': 'community-mysql-devel-5.7.18-2.fc25',

+                 'release': '2.fc25',

+                 'version': '5.7.18',

+             },

+             {

+                 'id': 9515682,

+                 'name': 'community-mysql-libs',

+                 'nvr': 'community-mysql-libs-5.7.18-2.fc25',

+                 'release': '2.fc25',

+                 'version': '5.7.18',

+             },

+             {

+                 'id': 9515681,

+                 'name': 'community-mysql-server',

+                 'nvr': 'community-mysql-server-5.7.18-2.fc25',

+                 'release': '2.fc25',

+                 'version': '5.7.18',

+             },

+         ],

+         'qt5-qtwebengine-5.8.0-11.fc25': [

+             {

+                 'id': 9571317,

+                 'name': 'qt5-qtwebengine-devel',

+                 'nvr': 'qt5-qtwebengine-devel-5.8.0-11.fc25',

+                 'release': '11.fc25',

+                 'version': '5.8.0',

+             },

+             {

+                 'id': 9571316,

+                 'name': 'qt5-qtwebengine-examples',

+                 'nvr': 'qt5-qtwebengine-examples-5.8.0-11.fc25',

+                 'release': '11.fc25',

+                 'version': '5.8.0',

+             }

+         ],

+     }

+ 

+     return rpms[nvr]

+ 

+ 

+ @pytest.mark.skipif(six.PY3, reason='koji does not work in Python 3')

+ class TestGetRpmsIncludedInBodhiUpdate(unittest.TestCase):

+     """Test case for get_rpms_included_in_bodhi_update"""

+ 

+     @patch('freshmaker.kojiservice.KojiService.get_build_rpms',

+            new=mock_get_build_rpms)

+     def test_get_rpms(self):

+         builds = [

+             {

+                 'build_id': 884455,

+                 'name': 'qt5-qtwebengine',

+                 'nvr': 'qt5-qtwebengine-5.8.0-11.fc25',

+                 'release': '11.fc25',

+                 'version': '5.8.0',

+             },

+             {

+                 'build_id': 881597,

+                 'name': 'community-mysql',

+                 'nvr': 'community-mysql-5.7.18-2.fc25',

+                 'release': '2.fc25',

+                 'version': '5.7.18',

+             }

+         ]

+         handler = DockerImageRebuildHandlerForBodhi()

+         rpms = list(handler.get_rpms_included_in_bodhi_update(builds))

+ 

+         self.assertEqual(5, len(rpms))

+ 

+         rpm = filter(lambda item: item['id'] == 9515681, rpms)

+         self.assertEqual(1, len(rpm))

This is also for original issue that is to rebuild docker image when
RPMs are built/tagged. Since building docker image should get RPMs from
official Fedora repositories, freshmaker has to wait for message of
topic bodhi.update.complete.stable.

Signed-off-by: Chenxiong Qi cqi@redhat.com

Using itertools.chain. :rainbow: :yellow_heart:

This looks good to me! Nice work.

/cc @bowlofeggs and @maxamillion for visibility.

rebased

8 years ago

I think you should move this method into utils.py as _run_command is a private module method and should not be imported here from the perspective of best practices. Another benefit is you can pass logger=logger which would make debug easier if something is going wrong.

Hi mjia, clone_repository is specific to DockerImageRebuildHandlerForBodhi. And, there is also a background of why I considered to put this method here at current stage. In utils.py, there is a similar method that clones a module repository from dist-git. Ideally, both of it and this method clone_repository could be merged into one, that is what I'm doing in my local branch. :) Once it is done and ready for review, I'll make a PR.

Pull-Request has been merged by cqi

8 years ago