diff --git a/kolla/image/build.py b/kolla/image/build.py index ff045bbc6c..4dad92428f 100755 --- a/kolla/image/build.py +++ b/kolla/image/build.py @@ -14,25 +14,20 @@ import contextlib import datetime -import errno import json import logging import os import queue import re -import requests import shutil import sys -import tarfile import tempfile import threading import time import docker -import git import jinja2 from oslo_config import cfg -from requests import exceptions as requests_exc # NOTE(SamYaple): Update the search path to prefer PROJECT_ROOT as the source @@ -44,9 +39,9 @@ if PROJECT_ROOT not in sys.path: sys.path.insert(0, PROJECT_ROOT) from kolla.common import config as common_config # noqa -from kolla.common import task # noqa from kolla.common import utils # noqa from kolla.image.unbuildable import UNBUILDABLE_IMAGES # noqa +from kolla.image.tasks import BuildTask # noqa from kolla.image.utils import LOG # noqa from kolla.image.utils import Status # noqa from kolla.image.utils import STATUS_ERRORS # noqa @@ -56,10 +51,6 @@ from kolla.template import methods as jinja_methods # noqa from kolla import version # noqa -class ArchivingError(Exception): - pass - - @contextlib.contextmanager def join_many(threads): try: @@ -81,23 +72,6 @@ def join_many(threads): LOG.warning('Force exits') -class DockerTask(task.Task): - - docker_kwargs = docker.utils.kwargs_from_env() - - def __init__(self): - super(DockerTask, self).__init__() - self._dc = None - - @property - def dc(self): - if self._dc is not None: - return self._dc - docker_kwargs = self.docker_kwargs.copy() - self._dc = docker.APIClient(version='auto', **docker_kwargs) - return self._dc - - class Image(object): def __init__(self, name, canonical_name, path, parent_name='', status=Status.UNPROCESSED, parent=None, @@ -141,355 +115,6 @@ class Image(object): self.parent_name, self.status, self.parent, self.source) -class PushIntoQueueTask(task.Task): - """Task that pushes some other task into a queue.""" - - def __init__(self, push_task, push_queue): - super(PushIntoQueueTask, self).__init__() - self.push_task = push_task - self.push_queue = push_queue - - @property - def name(self): - return 'PushIntoQueueTask(%s)' % (self.push_task.name) - - def run(self): - self.push_queue.put(self.push_task) - self.success = True - - -class PushError(Exception): - """Raised when there is a problem with pushing image to repository.""" - pass - - -class PushTask(DockerTask): - """Task that pushes an image to a docker repository.""" - - def __init__(self, conf, image): - super(PushTask, self).__init__() - self.conf = conf - self.image = image - self.logger = image.logger - - @property - def name(self): - return 'PushTask(%s)' % self.image.name - - def run(self): - image = self.image - self.logger.info('Trying to push the image') - try: - self.push_image(image) - except requests_exc.ConnectionError: - self.logger.exception('Make sure Docker is running and that you' - ' have the correct privileges to run Docker' - ' (root)') - image.status = Status.CONNECTION_ERROR - except PushError as exception: - self.logger.error(exception) - image.status = Status.PUSH_ERROR - except Exception: - self.logger.exception('Unknown error when pushing') - image.status = Status.PUSH_ERROR - finally: - if (image.status not in STATUS_ERRORS and - image.status != Status.UNPROCESSED): - self.logger.info('Pushed successfully') - self.success = True - else: - self.success = False - - def push_image(self, image): - kwargs = dict(stream=True, decode=True) - - for response in self.dc.push(image.canonical_name, **kwargs): - if 'stream' in response: - self.logger.info(response['stream']) - elif 'errorDetail' in response: - raise PushError(response['errorDetail']['message']) - - # Reset any previous errors. - image.status = Status.BUILT - - -class BuildTask(DockerTask): - """Task that builds out an image.""" - - def __init__(self, conf, image, push_queue): - super(BuildTask, self).__init__() - self.conf = conf - self.image = image - self.push_queue = push_queue - self.forcerm = not conf.keep - self.logger = image.logger - - @property - def name(self): - return 'BuildTask(%s)' % self.image.name - - def run(self): - self.builder(self.image) - if self.image.status in (Status.BUILT, Status.SKIPPED): - self.success = True - - @property - def followups(self): - followups = [] - if self.conf.push and self.success: - followups.extend([ - # If we are supposed to push the image into a docker - # repository, then make sure we do that... - PushIntoQueueTask( - PushTask(self.conf, self.image), - self.push_queue), - ]) - if self.image.children and self.success: - for image in self.image.children: - if image.status in (Status.UNMATCHED, Status.SKIPPED, - Status.UNBUILDABLE): - continue - followups.append(BuildTask(self.conf, image, self.push_queue)) - return followups - - def process_source(self, image, source): - if not source['enabled']: - self.logger.debug("Skipping disabled source %s", source['name']) - return - - dest_archive = os.path.join(image.path, source['name'] + '-archive') - - # NOTE(mgoddard): Change ownership of files to root:root. This - # avoids an issue introduced by the fix for git CVE-2022-24765, - # which breaks PBR when the source checkout is not owned by the - # user installing it. LP#1969096 - def reset_userinfo(tarinfo): - tarinfo.uid = tarinfo.gid = 0 - tarinfo.uname = tarinfo.gname = "root" - return tarinfo - - if source.get('type') == 'url': - self.logger.debug("Getting archive from %s", source['source']) - try: - r = requests.get(source['source'], timeout=self.conf.timeout) - except requests_exc.Timeout: - self.logger.exception( - 'Request timed out while getting archive from %s', - source['source']) - image.status = Status.ERROR - return - - if r.status_code == 200: - with open(dest_archive, 'wb') as f: - f.write(r.content) - else: - self.logger.error( - 'Failed to download archive: status_code %s', - r.status_code) - image.status = Status.ERROR - return - - elif source.get('type') == 'git': - clone_dir = '{}-{}'.format(dest_archive, - source['reference'].replace('/', '-')) - if os.path.exists(clone_dir): - self.logger.info("Clone dir %s exists. Removing it.", - clone_dir) - shutil.rmtree(clone_dir) - - try: - self.logger.debug("Cloning from %s", source['source']) - git.Git().clone(source['source'], clone_dir) - git.Git(clone_dir).checkout(source['reference']) - reference_sha = git.Git(clone_dir).rev_parse('HEAD') - self.logger.debug("Git checkout by reference %s (%s)", - source['reference'], reference_sha) - except Exception as e: - self.logger.error("Failed to get source from git: %s", - source['source']) - self.logger.error("Error: %s", e) - # clean-up clone folder to retry - shutil.rmtree(clone_dir) - image.status = Status.ERROR - return - - with tarfile.open(dest_archive, 'w') as tar: - tar.add(clone_dir, arcname=os.path.basename(clone_dir), - filter=reset_userinfo) - - elif source.get('type') == 'local': - self.logger.debug("Getting local archive from %s", - source['source']) - if os.path.isdir(source['source']): - with tarfile.open(dest_archive, 'w') as tar: - tar.add(source['source'], - arcname=os.path.basename(source['source']), - filter=reset_userinfo) - else: - shutil.copyfile(source['source'], dest_archive) - - else: - self.logger.error("Wrong source type '%s'", source.get('type')) - image.status = Status.ERROR - return - - # Set time on destination archive to epoch 0 - os.utime(dest_archive, (0, 0)) - - return dest_archive - - def update_buildargs(self): - buildargs = dict() - if self.conf.build_args: - buildargs = dict(self.conf.build_args) - - proxy_vars = ('HTTP_PROXY', 'http_proxy', 'HTTPS_PROXY', - 'https_proxy', 'FTP_PROXY', 'ftp_proxy', - 'NO_PROXY', 'no_proxy') - - for proxy_var in proxy_vars: - if proxy_var in os.environ and proxy_var not in buildargs: - buildargs[proxy_var] = os.environ.get(proxy_var) - - if not buildargs: - return None - return buildargs - - def builder(self, image): - - def make_an_archive(items, arcname, item_child_path=None): - if not item_child_path: - item_child_path = arcname - archives = list() - items_path = os.path.join(image.path, item_child_path) - for item in items: - archive_path = self.process_source(image, item) - if image.status in STATUS_ERRORS: - raise ArchivingError - if archive_path: - archives.append(archive_path) - if archives: - for archive in archives: - with tarfile.open(archive, 'r') as archive_tar: - archive_tar.extractall(path=items_path) - else: - try: - os.mkdir(items_path) - except OSError as e: - if e.errno == errno.EEXIST: - self.logger.info( - 'Directory %s already exist. Skipping.', - items_path) - else: - self.logger.error('Failed to create directory %s: %s', - items_path, e) - image.status = Status.CONNECTION_ERROR - raise ArchivingError - arc_path = os.path.join(image.path, '%s-archive' % arcname) - with tarfile.open(arc_path, 'w') as tar: - tar.add(items_path, arcname=arcname) - return len(os.listdir(items_path)) - - self.logger.debug('Processing') - - if image.status in [Status.SKIPPED, Status.UNBUILDABLE]: - self.logger.info('Skipping %s' % image.name) - return - - if image.status == Status.UNMATCHED: - return - - if (image.parent is not None and - image.parent.status in STATUS_ERRORS): - self.logger.error('Parent image error\'d with message "%s"', - image.parent.status) - image.status = Status.PARENT_ERROR - return - - image.status = Status.BUILDING - image.start = datetime.datetime.now() - self.logger.info('Building started at %s' % image.start) - - if image.source and 'source' in image.source: - self.process_source(image, image.source) - if image.status in STATUS_ERRORS: - return - - try: - plugins_am = make_an_archive(image.plugins, 'plugins') - except ArchivingError: - self.logger.error( - "Failed turning any plugins into a plugins archive") - return - else: - self.logger.debug( - "Turned %s plugins into plugins archive", - plugins_am) - try: - additions_am = make_an_archive(image.additions, 'additions') - except ArchivingError: - self.logger.error( - "Failed turning any additions into a additions archive") - return - else: - self.logger.debug( - "Turned %s additions into additions archive", - additions_am) - - # Pull the latest image for the base distro only - pull = self.conf.pull if image.parent is None else False - - buildargs = self.update_buildargs() - try: - for stream in self.dc.build(path=image.path, - tag=image.canonical_name, - nocache=not self.conf.cache, - rm=True, - decode=True, - network_mode=self.conf.network_mode, - pull=pull, - forcerm=self.forcerm, - buildargs=buildargs): - if 'stream' in stream: - for line in stream['stream'].split('\n'): - if line: - self.logger.info('%s', line) - if 'errorDetail' in stream: - image.status = Status.ERROR - self.logger.error('Error\'d with the following message') - for line in stream['errorDetail']['message'].split('\n'): - if line: - self.logger.error('%s', line) - return - - if image.status != Status.ERROR and self.conf.squash: - self.squash() - except docker.errors.DockerException: - image.status = Status.ERROR - self.logger.exception('Unknown docker error when building') - except Exception: - image.status = Status.ERROR - self.logger.exception('Unknown error when building') - else: - image.status = Status.BUILT - now = datetime.datetime.now() - self.logger.info('Built at %s (took %s)' % - (now, now - image.start)) - - def squash(self): - image_tag = self.image.canonical_name - image_id = self.dc.inspect_image(image_tag)['Id'] - - parent_history = self.dc.history(self.image.parent_name) - parent_last_layer = parent_history[0]['Id'] - self.logger.info('Parent lastest layer is: %s' % parent_last_layer) - - utils.squash(image_id, image_tag, from_layer=parent_last_layer, - cleanup=self.conf.squash_cleanup, - tmp_dir=self.conf.squash_tmp_dir) - self.logger.info('Image is squashed successfully') - - class WorkerThread(threading.Thread): """Thread that executes tasks until the queue provides a tombstone.""" diff --git a/kolla/image/tasks.py b/kolla/image/tasks.py new file mode 100644 index 0000000000..cbfecce095 --- /dev/null +++ b/kolla/image/tasks.py @@ -0,0 +1,397 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import docker +import errno +import os +import shutil +import tarfile + +import git +import requests +from requests import exceptions as requests_exc + +from kolla.common import task # noqa +from kolla.common import utils # noqa +from kolla.image.utils import Status +from kolla.image.utils import STATUS_ERRORS + + +class ArchivingError(Exception): + pass + + +class DockerTask(task.Task): + + docker_kwargs = docker.utils.kwargs_from_env() + + def __init__(self): + super(DockerTask, self).__init__() + self._dc = None + + @property + def dc(self): + if self._dc is not None: + return self._dc + docker_kwargs = self.docker_kwargs.copy() + self._dc = docker.APIClient(version='auto', **docker_kwargs) + return self._dc + + +class PushIntoQueueTask(task.Task): + """Task that pushes some other task into a queue.""" + + def __init__(self, push_task, push_queue): + super(PushIntoQueueTask, self).__init__() + self.push_task = push_task + self.push_queue = push_queue + + @property + def name(self): + return 'PushIntoQueueTask(%s)' % (self.push_task.name) + + def run(self): + self.push_queue.put(self.push_task) + self.success = True + + +class PushError(Exception): + """Raised when there is a problem with pushing image to repository.""" + pass + + +class PushTask(DockerTask): + """Task that pushes an image to a docker repository.""" + + def __init__(self, conf, image): + super(PushTask, self).__init__() + self.conf = conf + self.image = image + self.logger = image.logger + + @property + def name(self): + return 'PushTask(%s)' % self.image.name + + def run(self): + image = self.image + self.logger.info('Trying to push the image') + try: + self.push_image(image) + except requests_exc.ConnectionError: + self.logger.exception('Make sure Docker is running and that you' + ' have the correct privileges to run Docker' + ' (root)') + image.status = Status.CONNECTION_ERROR + except PushError as exception: + self.logger.error(exception) + image.status = Status.PUSH_ERROR + except Exception: + self.logger.exception('Unknown error when pushing') + image.status = Status.PUSH_ERROR + finally: + if (image.status not in STATUS_ERRORS and + image.status != Status.UNPROCESSED): + self.logger.info('Pushed successfully') + self.success = True + else: + self.success = False + + def push_image(self, image): + kwargs = dict(stream=True, decode=True) + + for response in self.dc.push(image.canonical_name, **kwargs): + if 'stream' in response: + self.logger.info(response['stream']) + elif 'errorDetail' in response: + raise PushError(response['errorDetail']['message']) + + # Reset any previous errors. + image.status = Status.BUILT + + +class BuildTask(DockerTask): + """Task that builds out an image.""" + + def __init__(self, conf, image, push_queue): + super(BuildTask, self).__init__() + self.conf = conf + self.image = image + self.push_queue = push_queue + self.forcerm = not conf.keep + self.logger = image.logger + + @property + def name(self): + return 'BuildTask(%s)' % self.image.name + + def run(self): + self.builder(self.image) + if self.image.status in (Status.BUILT, Status.SKIPPED): + self.success = True + + @property + def followups(self): + followups = [] + if self.conf.push and self.success: + followups.extend([ + # If we are supposed to push the image into a docker + # repository, then make sure we do that... + PushIntoQueueTask( + PushTask(self.conf, self.image), + self.push_queue), + ]) + if self.image.children and self.success: + for image in self.image.children: + if image.status in (Status.UNMATCHED, Status.SKIPPED, + Status.UNBUILDABLE): + continue + followups.append(BuildTask(self.conf, image, self.push_queue)) + return followups + + def process_source(self, image, source): + if not source['enabled']: + self.logger.debug("Skipping disabled source %s", source['name']) + return + + dest_archive = os.path.join(image.path, source['name'] + '-archive') + + # NOTE(mgoddard): Change ownership of files to root:root. This + # avoids an issue introduced by the fix for git CVE-2022-24765, + # which breaks PBR when the source checkout is not owned by the + # user installing it. LP#1969096 + def reset_userinfo(tarinfo): + tarinfo.uid = tarinfo.gid = 0 + tarinfo.uname = tarinfo.gname = "root" + return tarinfo + + if source.get('type') == 'url': + self.logger.debug("Getting archive from %s", source['source']) + try: + r = requests.get(source['source'], timeout=self.conf.timeout) + except requests_exc.Timeout: + self.logger.exception( + 'Request timed out while getting archive from %s', + source['source']) + image.status = Status.ERROR + return + + if r.status_code == 200: + with open(dest_archive, 'wb') as f: + f.write(r.content) + else: + self.logger.error( + 'Failed to download archive: status_code %s', + r.status_code) + image.status = Status.ERROR + return + + elif source.get('type') == 'git': + clone_dir = '{}-{}'.format(dest_archive, + source['reference'].replace('/', '-')) + if os.path.exists(clone_dir): + self.logger.info("Clone dir %s exists. Removing it.", + clone_dir) + shutil.rmtree(clone_dir) + + try: + self.logger.debug("Cloning from %s", source['source']) + git.Git().clone(source['source'], clone_dir) + git.Git(clone_dir).checkout(source['reference']) + reference_sha = git.Git(clone_dir).rev_parse('HEAD') + self.logger.debug("Git checkout by reference %s (%s)", + source['reference'], reference_sha) + except Exception as e: + self.logger.error("Failed to get source from git: %s", + source['source']) + self.logger.error("Error: %s", e) + # clean-up clone folder to retry + shutil.rmtree(clone_dir) + image.status = Status.ERROR + return + + with tarfile.open(dest_archive, 'w') as tar: + tar.add(clone_dir, arcname=os.path.basename(clone_dir), + filter=reset_userinfo) + + elif source.get('type') == 'local': + self.logger.debug("Getting local archive from %s", + source['source']) + if os.path.isdir(source['source']): + with tarfile.open(dest_archive, 'w') as tar: + tar.add(source['source'], + arcname=os.path.basename(source['source']), + filter=reset_userinfo) + else: + shutil.copyfile(source['source'], dest_archive) + + else: + self.logger.error("Wrong source type '%s'", source.get('type')) + image.status = Status.ERROR + return + + # Set time on destination archive to epoch 0 + os.utime(dest_archive, (0, 0)) + + return dest_archive + + def update_buildargs(self): + buildargs = dict() + if self.conf.build_args: + buildargs = dict(self.conf.build_args) + + proxy_vars = ('HTTP_PROXY', 'http_proxy', 'HTTPS_PROXY', + 'https_proxy', 'FTP_PROXY', 'ftp_proxy', + 'NO_PROXY', 'no_proxy') + + for proxy_var in proxy_vars: + if proxy_var in os.environ and proxy_var not in buildargs: + buildargs[proxy_var] = os.environ.get(proxy_var) + + if not buildargs: + return None + return buildargs + + def builder(self, image): + + def make_an_archive(items, arcname, item_child_path=None): + if not item_child_path: + item_child_path = arcname + archives = list() + items_path = os.path.join(image.path, item_child_path) + for item in items: + archive_path = self.process_source(image, item) + if image.status in STATUS_ERRORS: + raise ArchivingError + if archive_path: + archives.append(archive_path) + if archives: + for archive in archives: + with tarfile.open(archive, 'r') as archive_tar: + archive_tar.extractall(path=items_path) + else: + try: + os.mkdir(items_path) + except OSError as e: + if e.errno == errno.EEXIST: + self.logger.info( + 'Directory %s already exist. Skipping.', + items_path) + else: + self.logger.error('Failed to create directory %s: %s', + items_path, e) + image.status = Status.CONNECTION_ERROR + raise ArchivingError + arc_path = os.path.join(image.path, '%s-archive' % arcname) + with tarfile.open(arc_path, 'w') as tar: + tar.add(items_path, arcname=arcname) + return len(os.listdir(items_path)) + + self.logger.debug('Processing') + + if image.status in [Status.SKIPPED, Status.UNBUILDABLE]: + self.logger.info('Skipping %s' % image.name) + return + + if image.status == Status.UNMATCHED: + return + + if (image.parent is not None and + image.parent.status in STATUS_ERRORS): + self.logger.error('Parent image error\'d with message "%s"', + image.parent.status) + image.status = Status.PARENT_ERROR + return + + image.status = Status.BUILDING + image.start = datetime.datetime.now() + self.logger.info('Building started at %s' % image.start) + + if image.source and 'source' in image.source: + self.process_source(image, image.source) + if image.status in STATUS_ERRORS: + return + + try: + plugins_am = make_an_archive(image.plugins, 'plugins') + except ArchivingError: + self.logger.error( + "Failed turning any plugins into a plugins archive") + return + else: + self.logger.debug( + "Turned %s plugins into plugins archive", + plugins_am) + try: + additions_am = make_an_archive(image.additions, 'additions') + except ArchivingError: + self.logger.error( + "Failed turning any additions into a additions archive") + return + else: + self.logger.debug( + "Turned %s additions into additions archive", + additions_am) + + # Pull the latest image for the base distro only + pull = self.conf.pull if image.parent is None else False + + buildargs = self.update_buildargs() + try: + for stream in self.dc.build(path=image.path, + tag=image.canonical_name, + nocache=not self.conf.cache, + rm=True, + decode=True, + network_mode=self.conf.network_mode, + pull=pull, + forcerm=self.forcerm, + buildargs=buildargs): + if 'stream' in stream: + for line in stream['stream'].split('\n'): + if line: + self.logger.info('%s', line) + if 'errorDetail' in stream: + image.status = Status.ERROR + self.logger.error('Error\'d with the following message') + for line in stream['errorDetail']['message'].split('\n'): + if line: + self.logger.error('%s', line) + return + + if image.status != Status.ERROR and self.conf.squash: + self.squash() + except docker.errors.DockerException: + image.status = Status.ERROR + self.logger.exception('Unknown docker error when building') + except Exception: + image.status = Status.ERROR + self.logger.exception('Unknown error when building') + else: + image.status = Status.BUILT + now = datetime.datetime.now() + self.logger.info('Built at %s (took %s)' % + (now, now - image.start)) + + def squash(self): + image_tag = self.image.canonical_name + image_id = self.dc.inspect_image(image_tag)['Id'] + + parent_history = self.dc.history(self.image.parent_name) + parent_last_layer = parent_history[0]['Id'] + self.logger.info('Parent lastest layer is: %s' % parent_last_layer) + + utils.squash(image_id, image_tag, from_layer=parent_last_layer, + cleanup=self.conf.squash_cleanup, + tmp_dir=self.conf.squash_tmp_dir) + self.logger.info('Image is squashed successfully') diff --git a/kolla/tests/test_build.py b/kolla/tests/test_build.py index 89c4065030..b466a68101 100644 --- a/kolla/tests/test_build.py +++ b/kolla/tests/test_build.py @@ -18,6 +18,7 @@ from unittest import mock from kolla.cmd import build as build_cmd from kolla.image import build +from kolla.image import tasks from kolla.image import utils from kolla.tests import base @@ -64,7 +65,7 @@ class TasksTest(base.TestCase): @mock.patch('docker.APIClient') def test_push_image(self, mock_client): self.dc = mock_client - pusher = build.PushTask(self.conf, self.image) + pusher = tasks.PushTask(self.conf, self.image) pusher.run() mock_client().push.assert_called_once_with( self.image.canonical_name, decode=True, stream=True) @@ -76,7 +77,7 @@ class TasksTest(base.TestCase): """failure on connecting Docker API""" self.dc = mock_client mock_client().push.side_effect = Exception - pusher = build.PushTask(self.conf, self.image) + pusher = tasks.PushTask(self.conf, self.image) pusher.run() mock_client().push.assert_called_once_with( self.image.canonical_name, decode=True, stream=True) @@ -89,7 +90,7 @@ class TasksTest(base.TestCase): """failure on connecting Docker API, success on retry""" self.dc = mock_client mock_client().push.side_effect = [Exception, []] - pusher = build.PushTask(self.conf, self.image) + pusher = tasks.PushTask(self.conf, self.image) pusher.run() mock_client().push.assert_called_once_with( self.image.canonical_name, decode=True, stream=True) @@ -110,7 +111,7 @@ class TasksTest(base.TestCase): self.dc = mock_client mock_client().push.return_value = [{'errorDetail': {'message': 'mock push fail'}}] - pusher = build.PushTask(self.conf, self.image) + pusher = tasks.PushTask(self.conf, self.image) pusher.run() mock_client().push.assert_called_once_with( self.image.canonical_name, decode=True, stream=True) @@ -124,7 +125,7 @@ class TasksTest(base.TestCase): self.dc = mock_client mock_client().push.return_value = [{'errorDetail': {'message': 'mock push fail'}}] - pusher = build.PushTask(self.conf, self.image) + pusher = tasks.PushTask(self.conf, self.image) pusher.run() mock_client().push.assert_called_once_with( self.image.canonical_name, decode=True, stream=True) @@ -144,7 +145,7 @@ class TasksTest(base.TestCase): def test_build_image(self, mock_client): self.dc = mock_client push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) builder.run() mock_client().build.assert_called_once_with( @@ -161,7 +162,7 @@ class TasksTest(base.TestCase): push_queue = mock.Mock() self.conf.set_override('network_mode', 'bridge') - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) builder.run() mock_client().build.assert_called_once_with( @@ -181,7 +182,7 @@ class TasksTest(base.TestCase): } self.conf.set_override('build_args', build_args) push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) builder.run() mock_client().build.assert_called_once_with( @@ -200,7 +201,7 @@ class TasksTest(base.TestCase): build_args = { 'http_proxy': 'http://FROM_ENV:8080', } - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) builder.run() mock_client().build.assert_called_once_with( @@ -221,7 +222,7 @@ class TasksTest(base.TestCase): self.conf.set_override('build_args', build_args) push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) builder.run() mock_client().build.assert_called_once_with( @@ -242,7 +243,7 @@ class TasksTest(base.TestCase): 'enabled': True } push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) mock_get.side_effect = requests.exceptions.Timeout get_result = builder.process_source(self.image, self.image.source) @@ -278,7 +279,7 @@ class TasksTest(base.TestCase): 'enabled': True}]: self.image.source = source push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) get_result = builder.process_source(self.image, self.image.source) self.assertEqual(self.image.status, utils.Status.ERROR) self.assertFalse(builder.success) @@ -301,7 +302,7 @@ class TasksTest(base.TestCase): self.image.path = "fake_image_path" mock_path_exists.return_value = True push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.image, push_queue) + builder = tasks.BuildTask(self.conf, self.image, push_queue) get_result = builder.process_source(self.image, self.image.source) mock_rmtree.assert_called_with( @@ -319,7 +320,7 @@ class TasksTest(base.TestCase): } self.imageChild.children.append(FAKE_IMAGE_CHILD_UNMATCHED) push_queue = mock.Mock() - builder = build.BuildTask(self.conf, self.imageChild, push_queue) + builder = tasks.BuildTask(self.conf, self.imageChild, push_queue) builder.success = True self.conf.push = FAKE_IMAGE_CHILD_UNMATCHED get_result = builder.followups