def test_workflow_plugin_results(): """ Verifies the results of plugins in different phases are stored properly. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) prebuild_plugins = [{'name': 'pre_build_value'}] postbuild_plugins = [{'name': 'post_build_value'}] prepublish_plugins = [{'name': 'pre_publish_value'}] exit_plugins = [{'name': 'exit_value'}] workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=prebuild_plugins, prepublish_plugins=prepublish_plugins, postbuild_plugins=postbuild_plugins, exit_plugins=exit_plugins, plugin_files=[this_file]) workflow.build_docker_image() assert workflow.prebuild_results == {'pre_build_value': 'pre_build_value_result'} assert workflow.postbuild_results == {'post_build_value': 'post_build_value_result'} assert workflow.prepub_results == {'pre_publish_value': 'pre_publish_value_result'} assert workflow.exit_results == {'exit_value': 'exit_value_result'}
def test_workflow_compat(request): """ Some of our plugins have changed from being run post-build to being run at exit. Let's test what happens when we try running an exit plugin as a post-build plugin. """ this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_exit = Watcher() fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', postbuild_plugins=[{'name': 'store_logs_to_file', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) workflow.build_docker_image() assert watch_exit.was_called() assert len(fake_logger.errors) > 0
def test_layer_sizes(): flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_exit = Watcher() watch_buildstep = Watcher() workflow = DockerBuildWorkflow(SOURCE, 'test-image', exit_plugins=[{'name': 'uses_source', 'args': { 'watcher': watch_exit, }}], buildstep_plugins=[{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep, }}], plugin_files=[this_file]) workflow.build_docker_image() expected = [ {'diff_id': u'sha256:diff_id1-oldest', 'size': 4}, {'diff_id': u'sha256:diff_id2', 'size': 3}, {'diff_id': u'sha256:diff_id3', 'size': 2}, {'diff_id': u'sha256:diff_id4-newest', 'size': 1} ] assert workflow.layer_sizes == expected
def test_plugin_errors(plugins, should_fail, should_log): """ Try bad plugin configuration. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) fake_logger = FakeLogger() atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', plugin_files=[this_file], **plugins) # Find the 'watcher' parameter watchers = [conf.get('args', {}).get('watcher') for plugin in plugins.values() for conf in plugin] watcher = [x for x in watchers if x][0] if should_fail: with pytest.raises(PluginFailedException): workflow.build_docker_image() assert not watcher.was_called() else: workflow.build_docker_image() assert watcher.was_called() if should_log: assert len(fake_logger.errors) > 0 else: assert len(fake_logger.errors) == 0
def test_build(is_failed, image_id): """ tests docker build api plugin working """ flexmock(DockerfileParser, content='df_content') mock_docker() fake_builder = MockInsideBuilder(image_id=image_id) flexmock(InsideBuilder).new_instances(fake_builder) workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image') flexmock(CommandResult).should_receive('is_failed').and_return(is_failed) error = "error message" error_detail = "{u'message': u\"%s\"}" % error if is_failed: flexmock(CommandResult, error=error, error_detail=error_detail) with pytest.raises(PluginFailedException): workflow.build_docker_image() else: workflow.build_docker_image() assert isinstance(workflow.buildstep_result['docker_api'], BuildResult) assert workflow.build_result == workflow.buildstep_result['docker_api'] assert workflow.build_result.is_failed() == is_failed if is_failed: assert workflow.build_result.fail_reason == error assert '\\' not in workflow.plugins_errors['docker_api'] assert error in workflow.plugins_errors['docker_api'] else: assert workflow.build_result.image_id.startswith('sha256:') assert workflow.build_result.image_id.count(':') == 1
def test_autorebuild_stop_prevents_build(): """ test that a plugin that raises AutoRebuildCanceledException results in actually skipped build """ this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow( MOCK_SOURCE, "test-image", prebuild_plugins=[{"name": "stopstopstop", "args": {}}], prepublish_plugins=[{"name": "prepub_watched", "args": {"watcher": watch_prepub}}], postbuild_plugins=[{"name": "post_watched", "args": {"watcher": watch_post}}], exit_plugins=[{"name": "exit_watched", "args": {"watcher": watch_exit}}], plugin_files=[this_file], ) with pytest.raises(AutoRebuildCanceledException): workflow.build_docker_image() assert not watch_prepub.was_called() assert not watch_post.was_called() assert watch_exit.was_called() assert workflow.autorebuild_canceled == True
def test_workflow(): """ Test normal workflow. """ this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow( MOCK_SOURCE, "test-image", prebuild_plugins=[{"name": "pre_watched", "args": {"watcher": watch_pre}}], prepublish_plugins=[{"name": "prepub_watched", "args": {"watcher": watch_prepub}}], postbuild_plugins=[{"name": "post_watched", "args": {"watcher": watch_post}}], exit_plugins=[{"name": "exit_watched", "args": {"watcher": watch_exit}}], plugin_files=[this_file], ) workflow.build_docker_image() assert watch_pre.was_called() assert watch_prepub.was_called() assert watch_post.was_called() assert watch_exit.was_called()
def test_syntax_error(): """ tests reporting of syntax errors """ flexmock(DockerfileParser, content='df_content') mock_docker() fake_builder = MockInsideBuilder() def raise_exc(*args, **kwargs): explanation = ("Syntax error - can't find = in \"CMD\". " "Must be of the form: name=value") http_error = requests.HTTPError('500 Server Error') raise docker.errors.APIError(message='foo', response=http_error, explanation=explanation) yield {} fake_builder.tasker.build_image_from_path = raise_exc flexmock(InsideBuilder).new_instances(fake_builder) workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image') with pytest.raises(PluginFailedException): workflow.build_docker_image() assert isinstance(workflow.buildstep_result['docker_api'], BuildResult) assert workflow.build_result == workflow.buildstep_result['docker_api'] assert workflow.build_result.is_failed() assert "Syntax error" in workflow.build_result.fail_reason
def test_workflow_docker_build_error_reports(steps_to_fail, step_reported): """ Test if first error is reported properly. (i.e. exit plugins are not hiding the original root cause) """ def exc_string(step): return 'test_workflow_docker_build_error_reports.{}'.format(step) def construct_watcher(step): watcher = Watcher(raise_exc=Exception(exc_string(step)) if step in steps_to_fail else None) return watcher flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder(failed=True) flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = construct_watcher('pre') watch_buildstep = construct_watcher('buildstep') watch_prepub = construct_watcher('prepub') watch_post = construct_watcher('post') watch_exit = construct_watcher('exit') workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'is_allowed_to_fail': False, 'args': { 'watcher': watch_pre }}], buildstep_plugins=[{'name': 'buildstep_watched', 'is_allowed_to_fail': False, 'args': { 'watcher': watch_buildstep, }}], prepublish_plugins=[{'name': 'prepub_watched', 'is_allowed_to_fail': False, 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'is_allowed_to_fail': False, 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'is_allowed_to_fail': False, 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) with pytest.raises(Exception) as exc: workflow.build_docker_image() assert exc_string(step_reported) in str(exc)
def test_plugin_errors(request, plugins, should_fail, should_log): """ Try bad plugin configuration. """ flexmock(DockerfileParser, content='df_content') flexmock(DockerApiPlugin).should_receive('run').and_return(DUMMY_BUILD_RESULT) this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', plugin_files=[this_file], **plugins) # Find the 'watcher' parameter watchers = [conf.get('args', {}).get('watcher') for plugin in plugins.values() for conf in plugin] watcher = [x for x in watchers if x][0] if should_fail: with pytest.raises(PluginFailedException): workflow.build_docker_image() assert not watcher.was_called() assert workflow.plugins_errors assert all([is_string_type(plugin) for plugin in workflow.plugins_errors]) assert all([is_string_type(reason) for reason in workflow.plugins_errors.values()]) else: workflow.build_docker_image() assert watcher.was_called() assert not workflow.plugins_errors if should_log: assert len(fake_logger.errors) > 0 else: assert len(fake_logger.errors) == 0
def test_workflow_errors(): """ This is a test for what happens when plugins fail. When a prebuild plugin fails, no postbuild plugins should run. However, all the exit plugins should run. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_raises', 'args': {}}, {'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_raises', 'args': {} }, {'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) with pytest.raises(PluginFailedException): workflow.build_docker_image() # A pre-build plugin caused the build to fail, so post-build # plugins should not run. assert not watch_post.was_called() # But all exit plugins should run, even if one of them also raises # an exception. assert watch_exit.was_called() # All plugins of the same type (e.g. pre-build) are run, even if # one of them failed. assert watch_pre.was_called()
def test_workflow_base_images(): """ Test workflow for base images """ flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder(is_base_image=True) flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_buildstep = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], buildstep_plugins=[{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) workflow.build_docker_image() assert watch_pre.was_called() assert watch_prepub.was_called() assert watch_buildstep.was_called() assert watch_post.was_called() assert watch_exit.was_called() with pytest.raises(KeyError): assert workflow.base_image_inspect
def test_workflow_docker_build_error(): """ This is a test for what happens when the docker build fails. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder(failed=True) flexmock(InsideBuilder).new_instances(fake_builder) watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) assert workflow.build_docker_image().is_failed() # No subsequent build phases should have run except 'exit' assert not watch_prepub.was_called() assert not watch_post.was_called() assert watch_exit.was_called()
def build_image_here(source, image, parent_registry=None, target_registries=None, parent_registry_insecure=False, target_registries_insecure=False, dont_pull_base_image=False, **kwargs): """ build image from provided dockerfile (specified by `source`) in current environment :param source: dict, where/how to get source code to put in image :param image: str, tag for built image ([registry/]image_name[:tag]) :param parent_registry: str, registry to pull base image from :param target_registries: list of str, list of registries to push image to (might change in future) :param parent_registry_insecure: bool, allow connecting to parent registry over plain http :param target_registries_insecure: bool, allow connecting to target registries over plain http :param dont_pull_base_image: bool, don't pull or update base image specified in dockerfile :return: BuildResults """ build_json = { "image": image, "source": source, "parent_registry": parent_registry, "target_registries": target_registries, "parent_registry_insecure": parent_registry_insecure, "target_registries_insecure": target_registries_insecure, "dont_pull_base_image": dont_pull_base_image, } build_json.update(kwargs) m = DockerBuildWorkflow(**build_json) return m.build_docker_image()
def test_source_not_removed_for_exit_plugins(): this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_exit = Watcher() workflow = DockerBuildWorkflow(SOURCE, 'test-image', exit_plugins=[{'name': 'uses_source', 'args': { 'watcher': watch_exit, }}], plugin_files=[this_file]) workflow.build_docker_image() # Make sure that the plugin was actually run assert watch_exit.was_called()
def test_workflow_docker_build_error(): """ This is a test for what happens when the docker build fails. """ flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder(failed=True) flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_buildstep = Watcher(raise_exc=Exception()) watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], buildstep_plugins=[{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep, }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) with pytest.raises(Exception): workflow.build_docker_image() # No subsequent build phases should have run except 'exit' assert watch_pre.was_called() assert watch_buildstep.was_called() assert not watch_prepub.was_called() assert not watch_post.was_called() assert watch_exit.was_called()
def test_workflow_plugin_results(buildstep_plugin, buildstep_raises): """ Verifies the results of plugins in different phases are stored properly. It also verifies failed and remote BuildResult is handled properly. """ flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) prebuild_plugins = [{'name': 'pre_build_value'}] buildstep_plugins = [{'name': buildstep_plugin}] postbuild_plugins = [{'name': 'post_build_value'}] prepublish_plugins = [{'name': 'pre_publish_value'}] exit_plugins = [{'name': 'exit_value'}] workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=prebuild_plugins, buildstep_plugins=buildstep_plugins, prepublish_plugins=prepublish_plugins, postbuild_plugins=postbuild_plugins, exit_plugins=exit_plugins, plugin_files=[this_file]) if buildstep_raises: with pytest.raises(PluginFailedException): workflow.build_docker_image() else: workflow.build_docker_image() assert workflow.prebuild_results == {'pre_build_value': 'pre_build_value_result'} assert isinstance(workflow.buildstep_result[buildstep_plugin], BuildResult) if buildstep_raises: assert workflow.postbuild_results == {} assert workflow.prepub_results == {} else: assert workflow.postbuild_results == {'post_build_value': 'post_build_value_result'} assert workflow.prepub_results == {'pre_publish_value': 'pre_publish_value_result'} assert workflow.exit_results == {'exit_value': 'exit_value_result'}
def test_show_version(request, has_version): """ Test atomic-reactor print version of osbs-client used to build the build json if available """ VERSION = "1.0" flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_buildstep = Watcher() fake_logger = FakeLogger() existing_logger = atomic_reactor.inner.logger def restore_logger(): atomic_reactor.inner.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.inner.logger = fake_logger params = { 'prebuild_plugins': [], 'buildstep_plugins': [{'name': 'buildstep_watched', 'args': {'watcher': watch_buildstep}}], 'prepublish_plugins': [], 'postbuild_plugins': [], 'exit_plugins': [], 'plugin_files': [this_file], } if has_version: params['client_version'] = VERSION workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', **params) workflow.build_docker_image() expected_log_message = ("build json was built by osbs-client %s", VERSION) assert (expected_log_message in fake_logger.debugs) == has_version
def test_workflow(): """ Test normal workflow. """ this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) workflow.build_docker_image() assert watch_pre.was_called() assert watch_prepub.was_called() assert watch_post.was_called() assert watch_exit.was_called()
def test_workflow_compat(): """ Some of our plugins have changed from being run post-build to being run at exit. Let's test what happens when we try running an exit plugin as a post-build plugin. """ this_file = inspect.getfile(PreWatched) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_exit = Watcher() fake_logger = FakeLogger() atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow( MOCK_SOURCE, "test-image", postbuild_plugins=[{"name": "store_logs_to_file", "args": {"watcher": watch_exit}}], plugin_files=[this_file], ) workflow.build_docker_image() assert watch_exit.was_called() assert len(fake_logger.errors) > 0
def test_plugin_errors(): """ Try bad plugin configuration. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) fake_logger = FakeLogger() atomic_reactor.plugin.logger = fake_logger # No 'name' key workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'args': {}}], plugin_files=[this_file]) workflow.build_docker_image() assert len(fake_logger.errors) > 0 # No 'args' key workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre'}], plugin_files=[this_file]) workflow.build_docker_image() assert len(fake_logger.errors) > 0 # No such plugin workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'no plugin', 'args': {}}], plugin_files=[this_file]) workflow.build_docker_image() assert len(fake_logger.errors) > 0
def test_cancel_build(request, fail_at): """ Verifies that exit plugins are executed when the build is canceled """ # Make the phase we're testing send us SIGTERM phase_signal = defaultdict(lambda: None) phase_signal[fail_at] = signal.SIGTERM this_file = inspect.getfile(PreRaises) mock_docker() build_timeout = 10 if fail_at == 'build' else 0 sigterm_timeout = 2 fake_builder = MockInsideBuilder(timeout=build_timeout) flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = WatcherWithSignal(phase_signal['pre']) watch_prepub = WatcherWithSignal(phase_signal['prepub']) watch_post = WatcherWithSignal(phase_signal['post']) watch_exit = WatcherWithSignal(phase_signal['exit']) fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) if fail_at == 'build': pid = os.getpid() thread = threading.Thread( target=lambda: ( sleep(sigterm_timeout), os.kill(pid, signal.SIGTERM))) thread.start() with pytest.raises(BuildCanceledException): workflow.build_docker_image() else: workflow.build_docker_image() if fail_at not in ['exit', 'build']: assert ("plugin '%s_watched' raised an exception:" % fail_at + " BuildCanceledException('Build was canceled',)",) in fake_logger.warnings assert watch_exit.was_called() assert watch_pre.was_called() if fail_at not in ['pre', 'build']: assert watch_prepub.was_called() if fail_at not in ['pre', 'prepub', 'build']: assert watch_post.was_called()
def test_cancel_build(request, fail_at): """ Verifies that exit plugins are executed when the build is canceled """ # Make the phase we're testing send us SIGTERM phase_signal = defaultdict(lambda: None) phase_signal[fail_at] = signal.SIGTERM flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = WatcherWithSignal(phase_signal['pre']) watch_prepub = WatcherWithSignal(phase_signal['prepub']) watch_buildstep = WatcherWithSignal(phase_signal['buildstep']) watch_post = WatcherWithSignal(phase_signal['post']) watch_exit = WatcherWithSignal(phase_signal['exit']) fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], buildstep_plugins=[{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) if fail_at == 'buildstep': with pytest.raises(PluginFailedException): workflow.build_docker_image() assert ("plugin '%s_watched' raised an exception:" % fail_at + " BuildCanceledException('Build was canceled',)",) in fake_logger.errors else: workflow.build_docker_image() if fail_at != 'exit': assert ("plugin '%s_watched' raised an exception:" % fail_at + " BuildCanceledException('Build was canceled',)",) in fake_logger.warnings assert watch_exit.was_called() assert watch_pre.was_called() if fail_at not in ['pre', 'buildstep']: assert watch_prepub.was_called() if fail_at not in ['pre', 'prepub', 'buildstep']: assert watch_post.was_called()
def test_workflow_plugin_error(fail_at): """ This is a test for what happens when plugins fail. When a prebuild or postbuild plugin fails, and doesn't have is_allowed_to_fail=True set, the whole build should fail. However, all the exit plugins should run. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() prebuild_plugins = [{'name': 'pre_watched', 'args': { 'watcher': watch_pre, }}] prepublish_plugins = [{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}] postbuild_plugins = [{'name': 'post_watched', 'args': { 'watcher': watch_post }}] exit_plugins = [{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}] # Insert a failing plugin into one of the build phases if fail_at == 'pre': prebuild_plugins.insert(0, {'name': 'pre_raises', 'args': {}}) elif fail_at == 'prepub': prepublish_plugins.insert(0, {'name': 'prepub_raises', 'args': {}}) elif fail_at == 'post': postbuild_plugins.insert(0, {'name': 'post_raises', 'args': {}}) elif fail_at == 'exit': exit_plugins.insert(0, {'name': 'exit_raises', 'args': {}}) else: # Typo in the parameter list? assert False workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=prebuild_plugins, prepublish_plugins=prepublish_plugins, postbuild_plugins=postbuild_plugins, exit_plugins=exit_plugins, plugin_files=[this_file]) # Failures in any phase except 'exit' cause the build process to # abort. if fail_at == 'exit': build_result = workflow.build_docker_image() assert build_result and not build_result.is_failed() else: with pytest.raises(PluginFailedException): workflow.build_docker_image() # The pre-build phase should only complete if there were no # earlier plugin failures. assert watch_pre.was_called() == (fail_at != 'pre') # The prepublish phase should only complete if there were no # earlier plugin failures. assert watch_prepub.was_called() == (fail_at not in ('pre', 'prepub')) # The post-build phase should only complete if there were no # earlier plugin failures. assert watch_post.was_called() == (fail_at not in ('pre', 'prepub', 'post')) # But all exit plugins should run, even if one of them also raises # an exception. assert watch_exit.was_called()
def test_workflow_plugin_error(fail_at): """ This is a test for what happens when plugins fail. When a prebuild or postbuild plugin fails, and doesn't have is_allowed_to_fail=True set, the whole build should fail. However, all the exit plugins should run. """ flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_buildstep = Watcher() watch_post = Watcher() watch_exit = Watcher() prebuild_plugins = [{'name': 'pre_watched', 'args': { 'watcher': watch_pre, }}] buildstep_plugins = [{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep, }}] prepublish_plugins = [{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}] postbuild_plugins = [{'name': 'post_watched', 'args': { 'watcher': watch_post }}] exit_plugins = [{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}] # Insert a failing plugin into one of the build phases if fail_at == 'pre_raises': prebuild_plugins.insert(0, {'name': fail_at, 'args': {}}) elif fail_at == 'buildstep_raises': buildstep_plugins.insert(0, {'name': fail_at, 'args': {}}) elif fail_at == 'prepub_raises': prepublish_plugins.insert(0, {'name': fail_at, 'args': {}}) elif fail_at == 'post_raises': postbuild_plugins.insert(0, {'name': fail_at, 'args': {}}) elif fail_at == 'exit_raises' or fail_at == 'exit_raises_allowed': exit_plugins.insert(0, {'name': fail_at, 'args': {}}) else: # Typo in the parameter list? assert False workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=prebuild_plugins, buildstep_plugins=buildstep_plugins, prepublish_plugins=prepublish_plugins, postbuild_plugins=postbuild_plugins, exit_plugins=exit_plugins, plugin_files=[this_file]) # Most failures cause the build process to abort. Unless, it's # an exit plugin that's explicitly allowed to fail. if fail_at == 'exit_raises_allowed': workflow.build_docker_image() assert not workflow.plugins_errors else: with pytest.raises(PluginFailedException): workflow.build_docker_image() assert fail_at in workflow.plugins_errors # The pre-build phase should only complete if there were no # earlier plugin failures. assert watch_pre.was_called() == (fail_at != 'pre_raises') # The buildstep phase should only complete if there were no # earlier plugin failures. assert watch_buildstep.was_called() == (fail_at not in ('pre_raises', 'buildstep_raises')) # The prepublish phase should only complete if there were no # earlier plugin failures. assert watch_prepub.was_called() == (fail_at not in ('pre_raises', 'prepub_raises', 'buildstep_raises')) # The post-build phase should only complete if there were no # earlier plugin failures. assert watch_post.was_called() == (fail_at not in ('pre_raises', 'prepub_raises', 'buildstep_raises', 'post_raises')) # But all exit plugins should run, even if one of them also raises # an exception. assert watch_exit.was_called()
def test_workflow_plugin_error(fail_at): """ This is a test for what happens when plugins fail. When a prebuild or postbuild plugin fails, and doesn't have is_allowed_to_fail=True set, the whole build should fail. However, all the exit plugins should run. """ this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = Watcher() watch_prepub = Watcher() watch_post = Watcher() watch_exit = Watcher() prebuild_plugins = [{"name": "pre_watched", "args": {"watcher": watch_pre}}] prepublish_plugins = [{"name": "prepub_watched", "args": {"watcher": watch_prepub}}] postbuild_plugins = [{"name": "post_watched", "args": {"watcher": watch_post}}] exit_plugins = [{"name": "exit_watched", "args": {"watcher": watch_exit}}] # Insert a failing plugin into one of the build phases if fail_at == "pre": prebuild_plugins.insert(0, {"name": "pre_raises", "args": {}}) elif fail_at == "prepub": prepublish_plugins.insert(0, {"name": "prepub_raises", "args": {}}) elif fail_at == "post": postbuild_plugins.insert(0, {"name": "post_raises", "args": {}}) elif fail_at == "exit": exit_plugins.insert(0, {"name": "exit_raises", "args": {}}) else: # Typo in the parameter list? assert False workflow = DockerBuildWorkflow( MOCK_SOURCE, "test-image", prebuild_plugins=prebuild_plugins, prepublish_plugins=prepublish_plugins, postbuild_plugins=postbuild_plugins, exit_plugins=exit_plugins, plugin_files=[this_file], ) # Failures in any phase except 'exit' cause the build process to # abort. if fail_at == "exit": workflow.build_docker_image() else: with pytest.raises(PluginFailedException): workflow.build_docker_image() # The pre-build phase should only complete if there were no # earlier plugin failures. assert watch_pre.was_called() == (fail_at != "pre") # The prepublish phase should only complete if there were no # earlier plugin failures. assert watch_prepub.was_called() == (fail_at not in ("pre", "prepub")) # The post-build phase should only complete if there were no # earlier plugin failures. assert watch_post.was_called() == (fail_at not in ("pre", "prepub", "post")) # But all exit plugins should run, even if one of them also raises # an exception. assert watch_exit.was_called()
def test_cancel_build(request, fail_at): """ Verifies that exit plugins are executed when the build is canceled """ # Make the phase we're testing send us SIGTERM phase_signal = defaultdict(lambda: None) phase_signal[fail_at] = signal.SIGTERM flexmock(DockerfileParser, content='df_content') this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder() flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = WatcherWithSignal(phase_signal['pre']) watch_prepub = WatcherWithSignal(phase_signal['prepub']) watch_buildstep = WatcherWithSignal(phase_signal['buildstep']) watch_post = WatcherWithSignal(phase_signal['post']) watch_exit = WatcherWithSignal(phase_signal['exit']) fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], buildstep_plugins=[{'name': 'buildstep_watched', 'args': { 'watcher': watch_buildstep }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) # BaseException repr does not include trailing comma in Python >= 3.7 # we look for a partial match in log strings for Python < 3.7 compatibility expected_entry = ( "plugin '{}_watched' raised an exception: BuildCanceledException('Build was canceled'" ) if fail_at == 'buildstep': with pytest.raises(PluginFailedException): workflow.build_docker_image() assert workflow.build_canceled assert any(expected_entry.format(fail_at) in entry[0] for entry in fake_logger.errors) else: workflow.build_docker_image() if fail_at != 'exit': assert workflow.build_canceled assert any(expected_entry.format(fail_at) in entry[0] for entry in fake_logger.warnings) else: assert not workflow.build_canceled assert watch_exit.was_called() assert watch_pre.was_called() if fail_at not in ['pre', 'buildstep']: assert watch_prepub.was_called() if fail_at not in ['pre', 'prepub', 'buildstep']: assert watch_post.was_called()
def test_cancel_build(request, fail_at): """ Verifies that exit plugins are executed when the build is canceled """ phase_duration = 10 sigterm_timeout = 2 phase_timeout = {'pre': 0, 'prepub': 0, 'build': 0, 'post': 0, 'exit': 0} phase_timeout[fail_at] = phase_duration this_file = inspect.getfile(PreRaises) mock_docker() fake_builder = MockInsideBuilder(timeout=phase_timeout['build']) flexmock(InsideBuilder).new_instances(fake_builder) watch_pre = WatcherWithPause(phase_timeout['pre']) watch_prepub = WatcherWithPause(phase_timeout['prepub']) watch_post = WatcherWithPause(phase_timeout['post']) watch_exit = WatcherWithPause(phase_timeout['exit']) fake_logger = FakeLogger() existing_logger = atomic_reactor.plugin.logger def restore_logger(): atomic_reactor.plugin.logger = existing_logger request.addfinalizer(restore_logger) atomic_reactor.plugin.logger = fake_logger workflow = DockerBuildWorkflow(MOCK_SOURCE, 'test-image', prebuild_plugins=[{'name': 'pre_watched', 'args': { 'watcher': watch_pre }}], prepublish_plugins=[{'name': 'prepub_watched', 'args': { 'watcher': watch_prepub, }}], postbuild_plugins=[{'name': 'post_watched', 'args': { 'watcher': watch_post }}], exit_plugins=[{'name': 'exit_watched', 'args': { 'watcher': watch_exit }}], plugin_files=[this_file]) pid = os.getpid() thread = threading.Thread( target=lambda: ( sleep(sigterm_timeout), os.kill(pid, signal.SIGTERM))) thread.start() if fail_at == 'build': with pytest.raises(BuildCanceledException): workflow.build_docker_image() else: workflow.build_docker_image() if fail_at not in ['exit', 'build']: assert ("plugin '%s_watched' raised an exception:" % fail_at + " BuildCanceledException('Build was canceled',)",) in fake_logger.warnings assert watch_exit.was_called() assert watch_pre.was_called() if fail_at not in ['pre', 'build']: assert watch_prepub.was_called() if fail_at not in ['pre', 'prepub', 'build']: assert watch_post.was_called()