コード例 #1
0
    def test_create_original_to_rebuilt_nvrs_map(self, get_original_build):
        get_original_build.side_effect = ["original_1", "original_2"]
        self.handler.event = BotasErrataShippedEvent("test_msg_id",
                                                     self.botas_advisory)
        self.botas_advisory._builds = {
            "product_name": {
                "builds": [{
                    "some_name-2-12345": {
                        "nvr": "some_name-2-12345"
                    }
                }, {
                    "some_name_two-2-2": {
                        "nvr": "some_name_two-2-2"
                    }
                }]
            }
        }
        self.get_blocking_advisories.return_value = {
            "some_name-1-1", "some_name-2-1"
        }
        expected_map = {
            "original_1": "some_name-2-12345",
            "original_2": "some_name_two-2-2",
            "some_name-2-1": "some_name-2-12345"
        }

        mapping = self.handler._create_original_to_rebuilt_nvrs_map()

        self.assertEqual(get_original_build.call_count, 2)
        self.assertEqual(mapping, expected_map)
コード例 #2
0
    def test_handle(self, allow_build, handle_auto_rebuild, prepare_builds,
                    start_to_build_images):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        db_event = Event.get_or_create_from_event(db.session, event)
        allow_build.return_value = True
        handle_auto_rebuild.return_value = [{"bundle": 1}, {"bundle": 2}]
        prepare_builds.return_value = [
            ArtifactBuild.create(db.session,
                                 db_event,
                                 "ed0",
                                 "image",
                                 1234,
                                 original_nvr="some_name-2-12345",
                                 rebuilt_nvr="some_name-2-12346"),
            ArtifactBuild.create(db.session,
                                 db_event,
                                 "ed0",
                                 "image",
                                 12345,
                                 original_nvr="some_name_2-2-2",
                                 rebuilt_nvr="some_name_2-2-210")
        ]

        self.handler.handle(event)

        self.handler._prepare_builds.assert_called_once()
        self.assertEqual(self.handler._prepare_builds.call_args[0][0],
                         db_event)
        self.assertEqual(self.handler._prepare_builds.call_args[0][1],
                         [{
                             "bundle": 1
                         }, {
                             "bundle": 2
                         }])
コード例 #3
0
    def test_handle_set_dry_run(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory,
                                        dry_run=True)
        self.handler.handle(event)

        self.assertTrue(self.handler._force_dry_run)
        self.assertTrue(self.handler.dry_run)
コード例 #4
0
    def test_handle_isnt_allowed_by_internal_policy(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)

        self.handler.handle(event)
        db_event = Event.get(db.session, message_id='test_msg_id')

        self.assertEqual(db_event.state, EventState.SKIPPED.value)
        self.assertTrue(db_event.state_reason.startswith(
            "This image rebuild is not allowed by internal policy."))
コード例 #5
0
    def test_handle_no_digests_error(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.pyxis().get_digests_by_nvrs.return_value = set()
        self.botas_advisory._builds = {}

        self.handler.handle(event)
        db_event = Event.get(db.session, message_id='test_msg_id')

        self.assertEqual(db_event.state, EventState.SKIPPED.value)
        self.assertTrue(
            db_event.state_reason.startswith("There are no digests for NVRs:"))
コード例 #6
0
    def test_handle_no_digests_error(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.pyxis().get_manifest_list_digest_by_nvr.return_value = None
        self.botas_advisory._builds = {}

        self.handler.handle(event)
        db_event = Event.get(db.session, message_id='test_msg_id')

        self.assertEqual(db_event.state, EventState.SKIPPED.value)
        self.assertTrue(
            db_event.state_reason.startswith(
                "None of the original images have digest"))
コード例 #7
0
    def test_get_original_nvrs(self, get_build):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.botas_advisory._builds = {
            "product_name": {
                "builds": [{"nvr": "some_name-2-2"},
                           {"nvr": "some_name_two-2-2"}]
            }
        }
        get_build.return_value = "some_name-1-0"

        self.handler.handle(event)

        self.pyxis().get_digests_by_nvrs.assert_called_with({"some_name-1-0"})
コード例 #8
0
    def parse(self, topic, msg):
        msg_id = msg.get('msg_id')
        inner_msg = msg.get('msg')
        errata_id = int(inner_msg.get('errata_id'))

        errata = Errata()
        advisory = ErrataAdvisory.from_advisory_id(errata, errata_id)
        # If advisory created by BOTAS and it's shipped,
        # then return BotasErrataShippedEvent event
        if advisory.state == "SHIPPED_LIVE" and \
           advisory.reporter.startswith('botas'):
            event = BotasErrataShippedEvent(msg_id, advisory)
        else:
            event = ErrataAdvisoryStateChangedEvent(msg_id, advisory)
        return event
コード例 #9
0
    def test_handle_get_bundle_paths(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.pyxis().get_digests_by_nvrs.return_value = {'nvr1'}
        bundles = [
            {
                "bundle_path": "some_path",
                "bundle_path_digest": "sha256:123123",
                "channel_name": "streams-1.5.x",
                "related_images": [
                    {
                        "image": "registry/amq7/amq-streams-r-operator@sha256:111",
                        "name": "strimzi-cluster-operator",
                        "digest": "sha256:111"
                    },
                ],
                "version": "1.5.3"
            },
            {
                "bundle_path": "some_path_2",
                "channel_name": "streams-1.5.x",
                "related_images": [
                    {
                        "image": "registry/amq7/amq-streams-r-operator@sha256:555",
                        "name": "strimzi-cluster-operator",
                        "digest": "sha256:555"
                    },
                ],
                "version": "1.5.4"
            },
        ]
        self.pyxis().filter_bundles_by_related_image_digests.return_value = bundles
        self.botas_advisory._builds = {}

        self.handler.handle(event)
        db_event = Event.get(db.session, message_id='test_msg_id')

        # should be called only with the first digest, because second one
        # doesn't have 'bundle_path_digest'
        self.pyxis().get_images_by_digests.assert_called_once_with({"sha256:123123"})
        self.assertEqual(db_event.state, EventState.SKIPPED.value)
        self.assertTrue(
            db_event.state_reason.startswith("Skipping the rebuild of"))
コード例 #10
0
    def test_get_original_nvrs(self, get_build):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.botas_advisory._builds = {
            "product_name": {
                "builds": [{
                    "nvr": "some_name-2-2"
                }, {
                    "nvr": "some_name_two-2-2"
                }]
            }
        }
        get_build.return_value = "some_name-1-0"

        self.handler.handle(event)
        self.pyxis().get_manifest_list_digest_by_nvr.assert_has_calls(
            [
                call("some_name-1-0"),
                call("some_name_two-2-2"),
            ],
            any_order=True)
コード例 #11
0
    def parse(self, topic, msg):
        msg_id = msg.get('msg_id')
        inner_msg = msg.get('msg')
        errata_id = int(inner_msg.get('errata_id'))
        new_state = inner_msg.get('to')

        errata = Errata()
        advisory = ErrataAdvisory.from_advisory_id(errata, errata_id)
        # When there is message delay, state change messages can arrive after
        # advisory has been changed to a different state other than the one
        # in message, so we override advisory state with the state in message
        advisory.state = new_state

        # If advisory created by BOTAS and it's shipped,
        # then return BotasErrataShippedEvent event
        if advisory.state == "SHIPPED_LIVE" and advisory.reporter.startswith(
                'botas'):
            event = BotasErrataShippedEvent(msg_id, advisory)
        else:
            event = ErrataAdvisoryStateChangedEvent(msg_id, advisory)
        return event
コード例 #12
0
    def test_get_pullspecs_mapping(self):
        event = ManualBundleRebuild(
            "test_msg_id",
            container_images=[],
            bundle_images=["bundle_image_1", "bundle_image_2"])
        event2 = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        db_event = Event.get_or_create_from_event(db.session, event2)
        build = ArtifactBuild.create(db.session,
                                     db_event,
                                     "ed0",
                                     "image",
                                     1234,
                                     rebuilt_nvr="bundle_image_1")
        build.bundle_pullspec_overrides = {
            "pullspec_replacements": [{
                "new": "some_pullspec",
                "original": "original_pullspec",
                "pinned": True
            }, {
                "new": "new_pullspec",
                "original": "original_pullspec",
                "pinned": True,
                "_old": "old_pullspec"
            }]
        }
        self.handler.event = event
        db.session.commit()

        with self.assertLogs("freshmaker", "WARNING") as log:
            pullspec_map = self.handler._get_pullspecs_mapping()

        expected_map = {"old_pullspec": "new_pullspec"}
        self.assertTrue(
            "Can't find build for a bundle image \"bundle_image_2\"" in
            log.output[0])
        self.assertEqual(pullspec_map, expected_map)
コード例 #13
0
 def test_can_handle_botas_adisory(self):
     handler = HandleBotasAdvisory()
     event = BotasErrataShippedEvent("123", self.botas_advisory)
     self.assertTrue(handler.can_handle(event))
コード例 #14
0
    def test_multiple_bundles_to_single_related_image(self, mock_koji,
                                                      get_published):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.botas_advisory._builds = {
            "product_name": {
                "builds": [{
                    "foo-1-2.123": {
                        "nvr": "foo-1-2.123"
                    }
                }, {
                    "bar-2-2.134": {
                        "nvr": "bar-2-2.134"
                    }
                }]
            }
        }

        published_nvrs = {"foo-1-2.123": "foo-1-2", "bar-2-2.134": "bar-2-2"}
        get_published.side_effect = lambda x: published_nvrs[x]

        digests_by_nvrs = {
            "foo-1-2": "sha256:111",
            "bar-2-2": "sha256:222",
            "foo-1-2.123": "sha256:333",
            "bar-2-2.134": "sha256:444",
        }

        def gmldbn(nvr, must_be_published=True):
            return digests_by_nvrs[nvr]

        self.pyxis().get_manifest_list_digest_by_nvr.side_effect = gmldbn

        bundles_by_related_digest = {
            "sha256:111": [
                {
                    "bundle_path":
                    "bundle-a/path",
                    "bundle_path_digest":
                    "sha256:123123",
                    "channel_name":
                    "streams-1.5.x",
                    "csv_name":
                    "amq-streams.1.5.3",
                    "related_images": [
                        {
                            "image": "foo@sha256:111",
                            "name": "foo",
                            "digest": "sha256:111"
                        },
                    ],
                    "version":
                    "1.5.3"
                },
                {
                    "bundle_path":
                    "bundle-b/path",
                    "bundle_path_digest":
                    "sha256:023023",
                    "channel_name":
                    "4.5",
                    "csv_name":
                    "amq-streams.2.4.2",
                    "related_images": [
                        {
                            "image": "foo@sha256:111",
                            "name": "foo",
                            "digest": "sha256:111"
                        },
                    ],
                    "version":
                    "2.4.2"
                },
            ],
            "sha256:222": []
        }
        self.pyxis().get_bundles_by_related_image_digest.side_effect = \
            lambda x, _: bundles_by_related_digest[x]

        bundle_images = {
            "sha256:123123": [{
                "brew": {
                    "build": "foo-a-bundle-2.1-2",
                    "nvra": "foo-a-bundle-2.1-2.amd64",
                    "package": "foo-a-bundle",
                },
                "repositories": [{
                    "content_advisory_ids": [],
                    "manifest_list_digest": "sha256:12322",
                    "manifest_schema2_digest": "sha256:123123",
                    "published": True,
                    "registry": "registry.example.com",
                    "repository": "foo/foo-a-operator-bundle",
                    "tags": [{
                        "name": "2"
                    }, {
                        "name": "2.1"
                    }],
                }],
            }],
            "sha256:023023": [{
                "brew": {
                    "build": "foo-b-bundle-3.1-2",
                    "nvra": "foo-b-bundle-3.1-2.amd64",
                    "package": "foo-b-bundle",
                },
                "repositories": [{
                    "content_advisory_ids": [],
                    "manifest_list_digest": "sha256:12345",
                    "manifest_schema2_digest": "sha256:023023",
                    "published": True,
                    "registry": "registry.example.com",
                    "repository": "foo/foo-b-operator-bundle",
                    "tags": [{
                        "name": "3"
                    }, {
                        "name": "3.1"
                    }],
                }],
            }]
        }
        self.pyxis(
        ).get_images_by_digest.side_effect = lambda x: bundle_images[x]

        def _fake_get_auto_rebuild_tags(registry, repository):
            if repository == "foo/foo-a-operator-bundle":
                return ["2", "latest"]
            if repository == "foo/foo-b-operator-bundle":
                return ["3", "latest"]

        self.pyxis(
        ).get_auto_rebuild_tags.side_effect = _fake_get_auto_rebuild_tags

        koji_builds = {
            "foo-a-bundle-2.1-2": {
                "build_id": 123,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry.example.com/foo/foo-container@sha256:111",
                                    "original":
                                    "registry.exampl.com/foo/foo-container:0.1",
                                    "pinned": True,
                                }],
                            }
                        },
                    }
                },
                "name": "foo-a-bundle",
                "nvr": "foo-a-bundle-2.1-2",
            },
            "foo-b-bundle-3.1-2": {
                "build_id": 234,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry.example.com/foo/foo-container@sha256:111",
                                    "original":
                                    "registry.exampl.com/foo/foo-container:0.1",
                                    "pinned": True,
                                }],
                            }
                        },
                    }
                },
                "name": "foo-b-bundle",
                "nvr": "foo-b-bundle-3.1-2",
            }
        }
        mock_koji.return_value.get_build.side_effect = lambda x: koji_builds[x]
        self.handler._prepare_builds = MagicMock()
        self.handler.start_to_build_images = MagicMock()

        self.handler.handle(event)
        db_event = Event.get(db.session, message_id='test_msg_id')

        self.pyxis().get_images_by_digest.assert_has_calls(
            [call("sha256:123123"),
             call("sha256:023023")], any_order=True)
        self.assertEqual(db_event.state, EventState.BUILDING.value)
コード例 #15
0
    def test_handle_auto_rebuild(self):
        nvr_to_digest = {
            "original_1": "original_1_digest",
            "some_name-1-12345": "some_name-1-12345_digest",
            "original_2": "original_2_digest",
            "some_name_2-2-2": "some_name_2-2-2_digest",
        }
        bundles = [{
            "bundle_path_digest": "original_1_digest"
        }, {
            "bundle_path_digest": "some_name-1-12345_digest"
        }, {
            "bundle_path_digest": "original_2_digest"
        }, {
            "bundle_path_digest": "some_name_2-2-2_digest"
        }]
        bundles_with_related_images = {
            "original_1_digest": [
                {
                    "bundle_path_digest":
                    "bundle_with_related_images_1_digest",
                    "csv_name": "image.1.2.3",
                    "version": "1.2.3",
                },
            ],
            "original_2_digest": [
                {
                    "bundle_path_digest":
                    "bundle_with_related_images_2_digest",
                    "csv_name": "image.1.2.4",
                    "version": "1.2.4",
                },
            ]
        }
        image_by_digest = {
            "bundle_with_related_images_1_digest": {
                "brew": {
                    "build": "bundle1_nvr-1-1"
                }
            },
            "bundle_with_related_images_2_digest": {
                "brew": {
                    "build": "bundle2_nvr-1-1"
                }
            },
        }
        builds = {
            "bundle1_nvr-1-1": {
                "task_id": 1,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry/repo/operator1@original_1_digest",
                                    "original":
                                    "registry/repo/operator1:v2.2.0",
                                    "pinned": True,
                                }]
                            },
                        }
                    }
                }
            },
            "bundle2_nvr-1-1": {
                "task_id": 2,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry/repo/operator2@original_2_digest",
                                    "original":
                                    "registry/repo/operator2:v2.2.0",
                                    "pinned": True,
                                }]
                            },
                        }
                    }
                }
            }
        }

        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        db_event = Event.get_or_create_from_event(db.session, event)
        self.handler.event = event
        self.handler._create_original_to_rebuilt_nvrs_map = \
            MagicMock(return_value={"original_1": "some_name-1-12345",
                                    "original_2": "some_name_2-2-2"})

        def gmldbn(nvr, must_be_published=True):
            return nvr_to_digest[nvr]

        self.pyxis().get_manifest_list_digest_by_nvr.side_effect = gmldbn
        self.pyxis().get_operator_indices.return_value = []
        self.pyxis().get_latest_bundles.return_value = bundles
        # return bundles for original images
        self.pyxis(
        ).get_bundles_by_related_image_digest.side_effect = lambda x, y: bundles_with_related_images[
            x]
        self.pyxis(
        ).get_images_by_digest.side_effect = lambda x: [image_by_digest[x]]
        self.handler.image_has_auto_rebuild_tag = MagicMock(return_value=True)
        get_build = self.patcher.patch(
            "freshmaker.kojiservice.KojiService.get_build")
        get_build.side_effect = lambda x: builds[x]

        now = datetime(year=2020, month=12, day=25, hour=0, minute=0, second=0)
        with freezegun.freeze_time(now):
            bundles_to_rebuild = self.handler._handle_auto_rebuild(db_event)

        self.assertNotEqual(db_event.state, EventState.SKIPPED.value)
        get_build.assert_has_calls(
            [call("bundle1_nvr-1-1"),
             call("bundle2_nvr-1-1")], any_order=True)
        bundles_by_digest = {
            "bundle_with_related_images_1_digest": {
                "auto_rebuild":
                True,
                "images": [{
                    "brew": {
                        "build": "bundle1_nvr-1-1"
                    }
                }],
                "nvr":
                "bundle1_nvr-1-1",
                "osbs_pinning":
                True,
                "pullspec_replacements": [{
                    "new":
                    "registry/repo/operator1@some_name-1-12345_digest",
                    "original":
                    "registry/repo/operator1:v2.2.0",
                    "pinned":
                    True,
                    "_old":
                    "registry/repo/operator1@original_1_digest"
                }],
                "update": {
                    "metadata": {
                        "name": "image.1.2.3-0.1608854400.p",
                        "annotations": {
                            "olm.substitutesFor": "1.2.3"
                        },
                    },
                    "spec": {
                        "version": "1.2.3+0.1608854400.p"
                    },
                },
            },
            "bundle_with_related_images_2_digest": {
                "auto_rebuild":
                True,
                "images": [{
                    "brew": {
                        "build": "bundle2_nvr-1-1"
                    }
                }],
                "nvr":
                "bundle2_nvr-1-1",
                "osbs_pinning":
                True,
                "pullspec_replacements": [{
                    "new":
                    "registry/repo/operator2@some_name_2-2-2_digest",
                    "original":
                    "registry/repo/operator2:v2.2.0",
                    "pinned":
                    True,
                    "_old":
                    "registry/repo/operator2@original_2_digest"
                }],
                "update": {
                    "metadata": {
                        "name": "image.1.2.4-0.1608854400.p",
                        "annotations": {
                            "olm.substitutesFor": "1.2.4"
                        },
                    },
                    "spec": {
                        "version": "1.2.4+0.1608854400.p"
                    },
                },
            },
        }
        self.assertCountEqual(bundles_to_rebuild,
                              list(bundles_by_digest.values()))
コード例 #16
0
    def test_handle(self):
        event = BotasErrataShippedEvent("test_msg_id", self.botas_advisory)
        self.handler.allow_build = MagicMock(return_value=True)
        self.handler._create_original_to_rebuilt_nvrs_map = \
            MagicMock(return_value={"original_1": "some_name-1-12345",
                                    "original_2": "some_name_2-2-2"})
        nvr_to_digest = {
            "original_1": "original_1_digest",
            "some_name-1-12345": "some_name-1-12345_digest",
            "original_2": "original_2_digest",
            "some_name_2-2-2": "some_name_2-2-2_digest",
        }
        bundles = [{
            "bundle_path_digest": "original_1_digest"
        }, {
            "bundle_path_digest": "some_name-1-12345_digest"
        }, {
            "bundle_path_digest": "original_2_digest"
        }, {
            "bundle_path_digest": "some_name_2-2-2_digest"
        }]
        bundles_with_related_images = {
            "original_1_digest": [
                {
                    "bundle_path_digest":
                    "bundle_with_related_images_1_digest",
                    "csv_name": "image.1.2.3",
                    "version": "1.2.3",
                },
            ],
            "original_2_digest": [
                {
                    "bundle_path_digest":
                    "bundle_with_related_images_2_digest",
                    "csv_name": "image.1.2.4",
                    "version": "1.2.4",
                },
            ]
        }

        image_by_digest = {
            "bundle_with_related_images_1_digest": {
                "brew": {
                    "build": "bundle1_nvr-1-1"
                }
            },
            "bundle_with_related_images_2_digest": {
                "brew": {
                    "build": "bundle2_nvr-1-1"
                }
            },
        }
        self.pyxis(
        ).get_manifest_list_digest_by_nvr.side_effect = lambda x: nvr_to_digest[
            x]
        self.pyxis().get_operator_indices.return_value = []
        self.pyxis().get_latest_bundles.return_value = bundles
        # return bundles for original images
        self.pyxis(
        ).get_bundles_by_related_image_digest.side_effect = lambda x, y: bundles_with_related_images[
            x]
        self.pyxis(
        ).get_images_by_digest.side_effect = lambda x: [image_by_digest[x]]
        self.handler.image_has_auto_rebuild_tag = MagicMock(return_value=True)
        get_build = self.patcher.patch(
            "freshmaker.kojiservice.KojiService.get_build")
        builds = {
            "bundle1_nvr-1-1": {
                "task_id": 1,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry/repo/operator1@original_1_digest",
                                    "original":
                                    "registry/repo/operator1:v2.2.0",
                                    "pinned": True,
                                }]
                            },
                        }
                    }
                }
            },
            "bundle2_nvr-1-1": {
                "task_id": 2,
                "extra": {
                    "image": {
                        "operator_manifests": {
                            "related_images": {
                                "created_by_osbs":
                                True,
                                "pullspecs": [{
                                    "new":
                                    "registry/repo/operator2@original_2_digest",
                                    "original":
                                    "registry/repo/operator2:v2.2.0",
                                    "pinned": True,
                                }]
                            },
                        }
                    }
                }
            }
        }
        get_build.side_effect = lambda x: builds[x]
        db_event = Event.get_or_create_from_event(db.session, event)
        self.handler._prepare_builds = MagicMock(return_value=[
            ArtifactBuild.create(db.session,
                                 db_event,
                                 "ed0",
                                 "image",
                                 1234,
                                 original_nvr="some_name-2-12345",
                                 rebuilt_nvr="some_name-2-12346"),
            ArtifactBuild.create(db.session,
                                 db_event,
                                 "ed0",
                                 "image",
                                 12345,
                                 original_nvr="some_name_2-2-2",
                                 rebuilt_nvr="some_name_2-2-210")
        ])
        self.handler.start_to_build_images = MagicMock()
        db.session.commit()

        now = datetime(year=2020, month=12, day=25, hour=0, minute=0, second=0)
        with freezegun.freeze_time(now):
            self.handler.handle(event)

        self.assertEqual(db_event.state, EventState.BUILDING.value)
        get_build.assert_has_calls(
            [call("bundle1_nvr-1-1"),
             call("bundle2_nvr-1-1")], any_order=True)
        bundles_by_digest = {
            "bundle_with_related_images_1_digest": {
                "append": {
                    "spec": {
                        "skips": ["1.2.3"]
                    }
                },
                "auto_rebuild":
                True,
                "images": [{
                    "brew": {
                        "build": "bundle1_nvr-1-1"
                    }
                }],
                "nvr":
                "bundle1_nvr-1-1",
                "osbs_pinning":
                True,
                "pullspecs": [{
                    "new": "registry/repo/operator1@some_name-1-12345_digest",
                    "original": "registry/repo/operator1:v2.2.0",
                    "pinned": True,
                }],
                "update": {
                    "metadata": {
                        "name": "image.1.2.3+0.1608854400.patched",
                        "substitutes-for": "1.2.3",
                    },
                    "spec": {
                        "version": "1.2.3+0.1608854400.patched"
                    },
                },
            },
            "bundle_with_related_images_2_digest": {
                "append": {
                    "spec": {
                        "skips": ["1.2.4"]
                    }
                },
                "auto_rebuild":
                True,
                "images": [{
                    "brew": {
                        "build": "bundle2_nvr-1-1"
                    }
                }],
                "nvr":
                "bundle2_nvr-1-1",
                "osbs_pinning":
                True,
                "pullspecs": [{
                    "new": "registry/repo/operator2@some_name_2-2-2_digest",
                    "original": "registry/repo/operator2:v2.2.0",
                    "pinned": True,
                }],
                "update": {
                    "metadata": {
                        "name": "image.1.2.4+0.1608854400.patched",
                        "substitutes-for": "1.2.4",
                    },
                    "spec": {
                        "version": "1.2.4+0.1608854400.patched"
                    },
                },
            },
        }
        self.handler._prepare_builds.assert_called_with(
            db_event, bundles_by_digest, {
                'bundle_with_related_images_1_digest',
                'bundle_with_related_images_2_digest'
            })